Fork of daniellemaywood.uk/gleam — Wasm codegen work
79 kB
2062 lines
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2025 The Gleam contributors
3
4use super::{
5 INDENT, bit_array_segment_int_value_to_bytes,
6 expression::{self, Generator, Ordering, Scope, float, float_from_value},
7};
8use crate::{
9 ast::{AssignmentKind, Endianness, SrcSpan, TypedClause, TypedExpr, TypedPattern},
10 docvec,
11 exhaustiveness::{
12 BitArrayMatchedValue, BitArrayTest, Body, BoundValue, CompiledCase, Decision,
13 FallbackCheck, MatchTest, Offset, ReadAction, ReadSize, ReadType, RuntimeCheck,
14 SizeOperator, SizeTest, Variable, VariableUsage,
15 },
16 javascript::{
17 TypeVariant,
18 expression::{eco_string_int, string},
19 maybe_escape_property,
20 },
21 pretty::{Document, Documentable, break_, concat, join, line, nil},
22 strings::{convert_string_escape_chars, length_utf16},
23};
24use ecow::{EcoString, eco_format};
25use itertools::Itertools;
26use num_bigint::BigInt;
27use std::{collections::HashMap, sync::OnceLock};
28
29pub const ASSIGNMENT_VAR: &str = "$";
30
31pub fn case<'a>(
32 compiled_case: &'a CompiledCase,
33 clauses: &'a [TypedClause],
34 subjects: &'a [TypedExpr],
35 expression_generator: &mut Generator<'_, 'a>,
36) -> Document<'a> {
37 let scope_position = expression_generator.scope_position.clone();
38 let mut variables = Variables::new(expression_generator, VariableAssignment::Declare);
39 let assignments = variables.assign_case_subjects(compiled_case, subjects);
40 let mut printer = CasePrinter {
41 variables,
42 assignments: &assignments,
43 kind: DecisionKind::Case { clauses },
44 };
45
46 let decision = match &compiled_case.tree {
47 // Printing needs extra care if we're dealing with a sort of "degenerate"
48 // tree that immediately starts with a guard node.
49 // Code generation for guard nodes require defining variables outside of
50 // the safe scope of the generated `if` statement. So if we were to just
51 // generate code like usual we run the risk of leaking variables in the
52 // outer scope:
53 //
54 // ```case
55 // case 11 {
56 // n if n == 10 -> todo
57 // _ -> todo
58 // }
59 //
60 // let n = 12
61 // ```
62 //
63 // That case would have us generate something like this:
64 //
65 // ```js
66 // let n = 11
67 // if (n === 10) { todo } else { todo }
68 //
69 // // If we don't wrap it in a block that `n = 11` definition that was
70 // // introduced would end up clashing with the `let n = 12` that comes
71 // // later!
72 // ```
73 //
74 // So in this special case we have to wrap everything in a block.
75 tree @ Decision::Guard { .. } if !scope_position.is_tail() => break_block(
76 printer
77 .inside_new_scope(|this| this.decision(tree))
78 .into_doc(),
79 ),
80
81 tree @ (Decision::Run { .. }
82 | Decision::Guard { .. }
83 | Decision::Switch { .. }
84 | Decision::Fail) => printer.decision(tree).into_doc(),
85 };
86 docvec![
87 subjects
88 .first()
89 .map(|subject| expression_generator.source_map_tracker(subject.location().start))
90 .unwrap_or_else(nil),
91 assignments_to_doc(&mut *expression_generator, assignments),
92 decision
93 ]
94 .force_break()
95}
96
97/// The generated code for a decision tree.
98enum CaseBody<'a> {
99 /// A JavaScript `if`` statement by itself. This can be merged with any
100 /// preceding `else` statements to form an `else if` construct.
101 If {
102 check: Document<'a>,
103 body: Document<'a>,
104 },
105 /// A sequence of statements. This must be wrapped as the body of an `if` or
106 /// `else` statement.
107 Statements(Document<'a>),
108
109 /// A JavaScript `if` statement followed by a single `else` clause. In some
110 /// cases this can be flattened to reduce the size of the generated decision
111 /// tree.
112 IfElse {
113 check: Document<'a>,
114 if_body: Document<'a>,
115 else_body: Document<'a>,
116 /// The decision in the tree that is used to generate the code for the
117 /// `else` clause of this statement. If this is the same as another `if`-
118 /// `else` statement, the two can be merged into one.
119 fallback_decision: &'a Decision,
120 },
121
122 /// A JavaScript `if` statement followed by more than one `else` clause. This
123 /// can sometimes be merged with preceding `else` statements in the same way
124 /// that `if` can.
125 IfElseChain(Document<'a>),
126}
127
128impl<'a> CaseBody<'a> {
129 fn into_doc(self) -> Document<'a> {
130 match self {
131 CaseBody::If { check, body } => docvec![
132 "if (",
133 break_("", "")
134 .append(check)
135 .nest(INDENT)
136 .append(break_("", ""))
137 .group(),
138 ") ",
139 break_block(body)
140 ],
141 // If we have some code like the following:
142 // ```javascript
143 // if (some_condition) {
144 //
145 // } else {
146 // fallback()
147 // }
148 // ```
149 //
150 // Here, the body of the `if` statement is empty. This can happen
151 // sometimes when generating decision trees for `let assert`.
152 //
153 // Instead, we can write this more concisely:
154 // ```javascript
155 // if (!some_condition) {
156 // fallback()
157 // }
158 // ```
159 CaseBody::IfElse {
160 check,
161 if_body,
162 else_body,
163 ..
164 } if if_body.is_empty() => docvec![
165 "if (!(",
166 break_("", "")
167 .append(check)
168 .nest(INDENT)
169 .append(break_("", ""))
170 .group(),
171 ")) ",
172 else_body,
173 ],
174 CaseBody::IfElse {
175 check,
176 if_body,
177 else_body,
178 ..
179 } => docvec![
180 "if (",
181 break_("", "")
182 .append(check)
183 .nest(INDENT)
184 .append(break_("", ""))
185 .group(),
186 ") ",
187 break_block(if_body),
188 " else ",
189 else_body,
190 ],
191 CaseBody::Statements(document) | CaseBody::IfElseChain(document) => document,
192 }
193 }
194
195 /// Convert this value into the required document to put directly after an
196 /// `else` keyword.
197 fn document_after_else(self) -> Document<'a> {
198 match self {
199 // `if` and `if-else` statements can come directly after an `else` keyword
200 CaseBody::If { .. } | CaseBody::IfElse { .. } => self.into_doc(),
201 CaseBody::IfElseChain(document) => document,
202 // Lists of statements must be wrapped in a block
203 CaseBody::Statements(document) => break_block(document),
204 }
205 }
206
207 fn is_empty(&self) -> bool {
208 match self {
209 CaseBody::If { .. } | CaseBody::IfElse { .. } => false,
210 CaseBody::Statements(document) | CaseBody::IfElseChain(document) => document.is_empty(),
211 }
212 }
213}
214
215struct CasePrinter<'module, 'generator, 'a, 'assignments> {
216 variables: Variables<'generator, 'module, 'a>,
217 assignments: &'assignments Vec<SubjectAssignment<'a>>,
218 kind: DecisionKind<'a>,
219}
220
221/// Information specific to the different kinds of decision trees: `case`
222/// expressions and `let assert` statements.
223enum DecisionKind<'a> {
224 Case {
225 clauses: &'a [TypedClause],
226 },
227 LetAssert {
228 kind: &'a AssignmentKind<TypedExpr>,
229 subject_location: SrcSpan,
230 pattern_location: SrcSpan,
231 subject: EcoString,
232 },
233}
234
235enum BodyExpression<'a> {
236 /// This happens when a case expression branch returns the same value that
237 /// is being matched on. So instead of rebuilding it from scratch we can
238 /// return the case subject directly. For example:
239 /// `Ok(1) -> Ok(1)`
240 /// `a -> a`
241 /// `[1, ..rest] -> [1, ..rest]`
242 ///
243 Variable(Document<'a>),
244
245 /// This happens when a case expression has a complex body that is not just
246 /// returning the matched subject. For example:
247 /// `Ok(1) -> Ok(2)`
248 /// `_ -> [1, 2, 3]`
249 /// `1 -> "wibble"`
250 ///
251 Expressions(Document<'a>),
252}
253
254/// Code generation for decision trees can look a bit daunting at a first glance
255/// so let's go over the big idea to hopefully make it easier to understand why
256/// the code is organised the way it is :)
257///
258/// > It might be helpful to go over the `exhaustiveness` module first and get
259/// > familiar with the structure of the decision tree!
260///
261/// A decision tree has nodes that perform checks on pattern variables until
262/// it reaches a body with an expression to run. This will be turned into a
263/// series of if-else checks.
264///
265/// While on the surface it might sound pretty straightforward, the code generation
266/// needs to take care of a couple of tricky aspects: when a check succeeds it's
267/// not just allowing us to move to the next check, but it also introduces new
268/// variables in the scope that we can reference. Let's look at an example:
269///
270/// ```gleam
271/// case value {
272/// [1, ..rest] -> rest
273/// _ -> []
274/// }
275/// ```
276///
277/// Here we will first need to check that the list is not empty:
278///
279/// ```js
280/// if (value instanceof $NonEmptyList) {
281/// // ...
282/// } else {
283/// return [];
284/// }
285/// ```
286///
287/// Once that check succeeds we know that now we can access two new values: the
288/// first element of the list and the rest of the list! So we need to keep track
289/// of that in case further checks need to use those values; and in the example
290/// above they actually do! The second check we need to perform will be on the
291/// first item of the list, so we need to actually create a variable for it:
292///
293/// ```js
294/// if (value instanceof $NonEmptyList) {
295/// let $ = value.head;
296/// if ($ === 1) {
297/// // ...
298/// } else {
299/// // ...
300/// }
301/// } else {
302/// return [];
303/// }
304/// ```
305///
306/// So, as we're generating code for each check and move further down the decision
307/// tree, we will have to keep track of all the variables that we've discovered
308/// after each successful check.
309///
310/// In order to do that we'll be using a `Variables` data structure to hold all
311/// this information about the current scope.
312///
313impl<'a> CasePrinter<'_, '_, 'a, '_> {
314 fn decision(&mut self, decision: &'a Decision) -> CaseBody<'a> {
315 match decision {
316 Decision::Fail => {
317 if let DecisionKind::LetAssert {
318 kind,
319 subject_location,
320 pattern_location,
321 subject,
322 } = &self.kind
323 {
324 CaseBody::Statements(self.assignment_no_match(
325 subject.to_doc(),
326 kind,
327 *subject_location,
328 *pattern_location,
329 ))
330 } else {
331 unreachable!("Invalid decision tree reached code generation")
332 }
333 }
334 Decision::Run { body } => {
335 let location = match self.kind {
336 DecisionKind::Case { clauses } => clauses
337 .get(body.clause_index)
338 .expect("invalid clause index")
339 .location(),
340 DecisionKind::LetAssert {
341 subject_location,
342 pattern_location,
343 ..
344 } => match self.variables.variable_assignment {
345 // When the variables are being declared, they
346 // correspond to the pattern.
347 VariableAssignment::Declare => pattern_location,
348 // When the variables are being assigned, they
349 // correspond to the subject.
350 VariableAssignment::Reassign => subject_location,
351 },
352 };
353 let source_map_tracker = self
354 .variables
355 .expression_generator
356 .source_map_tracker(location.start);
357 let bindings = docvec![
358 source_map_tracker,
359 self.variables.bindings_doc(&body.bindings)
360 ];
361 let body = self.body_expression(body.clause_index);
362 let body = match body {
363 BodyExpression::Variable(variable) => variable,
364 BodyExpression::Expressions(body) => join_with_line(bindings, body),
365 };
366 CaseBody::Statements(body)
367 }
368 Decision::Switch {
369 var,
370 choices,
371 fallback,
372 fallback_check,
373 } => self.switch(var, choices, fallback, fallback_check),
374 Decision::Guard {
375 guard,
376 if_true,
377 if_false,
378 } => self.decision_guard(*guard, if_true, if_false),
379 }
380 }
381
382 fn body_expression(&mut self, clause_index: usize) -> BodyExpression<'a> {
383 // If we are not in a `case` expression, there is no additional code to
384 // execute when a branch matches; we only assign variables bound in the
385 // pattern.
386 let DecisionKind::Case { clauses } = &self.kind else {
387 return BodyExpression::Expressions(nil());
388 };
389
390 let clause = &clauses.get(clause_index).expect("invalid clause index");
391 let body = &clause.then;
392
393 if let Some(subject_index) = clause.returned_subject() {
394 let variable = self
395 .assignments
396 .get(subject_index)
397 .expect("case with no subjects")
398 .name();
399
400 BodyExpression::Variable(
401 self.variables
402 .expression_generator
403 .wrap_return(variable.to_doc()),
404 )
405 } else {
406 BodyExpression::Expressions(
407 self.variables
408 .expression_generator
409 .expression_flattening_blocks(body),
410 )
411 }
412 }
413
414 fn switch(
415 &mut self,
416 var: &'a Variable,
417 choices: &'a [(RuntimeCheck, Decision)],
418 fallback: &'a Decision,
419 fallback_check: &'a FallbackCheck,
420 ) -> CaseBody<'a> {
421 // If there's just a single choice we can just generate the code for
422 // it: no need to do any checking, we know it must match!
423 if choices.is_empty() {
424 // However, if the choice had an associated check (that is, it was
425 // not just a simple catch all) we need to keep track of all the
426 // variables brought into scope by the (always) successfull check.
427 if let FallbackCheck::RuntimeCheck { check } = fallback_check {
428 self.variables.record_check_assignments(var, check);
429 }
430
431 // We can't use `inside_new_scope` here without care: the
432 // code we generate goes directly into the enclosing scope
433 // (there's no wrapping if-else block). User variables bound
434 // in the branch go out of scope at its end, so their counters
435 // are reset and later references resolve to the outer bindings.
436 // Compiler synthesised variables (the `$` case subjects, `_pipe`,
437 // `_block`, ...) can't be referenced by user code and their
438 // declarations leak into this scope. Restoring only the user-variable
439 // counters leaves the synthesised counters advanced, so later code
440 // can't redeclare one of them.
441 let old_user_variables = match &self.kind {
442 DecisionKind::Case { .. } => self
443 .variables
444 .expression_generator
445 .current_scope
446 .user_variables()
447 .clone(),
448 DecisionKind::LetAssert { .. } => Default::default(),
449 };
450 let old_names = self.variables.scoped_variable_names.clone();
451 let old_segments = self.variables.segment_values.clone();
452 let old_segment_names = self.variables.scoped_segment_names.clone();
453
454 let result = self.decision(fallback);
455
456 match &self.kind {
457 DecisionKind::Case { .. } => {
458 // Restore the user variables that were in scope before the
459 // branch. The synthesised counters are left advanced, and
460 // the high-water marks keep any user variable that leaked
461 // out of the branch from being redeclared by a later `let`.
462 self.variables
463 .expression_generator
464 .current_scope
465 .restore_user_variables(&old_user_variables);
466 }
467 DecisionKind::LetAssert { .. } => {}
468 }
469
470 self.variables.scoped_variable_names = old_names;
471 self.variables.segment_values = old_segments;
472 self.variables.scoped_segment_names = old_segment_names;
473 return result;
474 }
475
476 // Otherwise we'll have to generate a series of if-else to check which
477 // pattern is going to match!
478 let mut assignments = vec![];
479 if !self.variables.is_bound_in_scope(var) {
480 // If the variable we need to perform a check on is not already bound
481 // in scope we will be binding it to a new made up name. This way we
482 // can also reference this exact name in further checks instead of
483 // recomputing the value each time.
484 let name = self.variables.next_local_var(&ASSIGNMENT_VAR.into());
485 let value = self.variables.get_value(var);
486 self.variables.bind(name.clone(), var);
487 assignments.push(let_doc(name, value.to_doc()))
488 };
489
490 // Variable storing the character code for the first character of a string.
491 // This is only declared if multiple patterns match on just the first
492 // character, as it allows us to avoid calling `.startsWith` multiple times
493 // and just call `.charCodeAt(0)` once, which is much faster.
494 let first_character_variable = if multiple_single_character_prefix_checks(choices) {
495 let name = self.variables.next_local_var(&ASSIGNMENT_VAR.into());
496 let string = self.variables.get_value(var);
497 let first_character = docvec![string, ".charCodeAt(0)"];
498 assignments.push(let_doc(name.clone(), first_character));
499 Some(name)
500 } else {
501 None
502 };
503
504 let mut if_ = CaseBody::Statements(nil());
505 for (i, (check, decision)) in choices.iter().enumerate() {
506 self.variables.record_check_assignments(var, check);
507
508 // For each check we generate:
509 // - the document to perform such check
510 // - the body to run if the check is successful
511 // - the assignments we need to bring all the bit array segments
512 // referenced by this check
513 let (check_doc, body, mut segment_assignments) = self.inside_new_scope(|this| {
514 let segment_assignments = this.variables.bit_array_segment_assignments(check);
515
516 // If the pattern matches on a single character, use the character
517 // code instead of `.startsWith`.
518 let check_doc = if let Some(code) = single_character_prefix_code(check) {
519 let first_character = if let Some(variable) = &first_character_variable {
520 variable.to_doc()
521 } else {
522 // This is the only single-character match in this `case`
523 // expression, so we don't bind it to a variable and just
524 // call `.charCodeAt` inline. This is still faster than
525 // `.startsWith`.
526 let string = this.variables.get_value(var);
527 docvec![string, ".charCodeAt(0)"]
528 };
529
530 docvec![first_character, " === ", code]
531 } else {
532 this.variables.runtime_check(var, check)
533 };
534
535 let body = this.decision(decision);
536 (check_doc, body, segment_assignments)
537 });
538 assignments.append(&mut segment_assignments);
539
540 let (check_doc, body) = match body {
541 // If we have a statement like this:
542 // ```javascript
543 // if (x) {
544 // if (y) {
545 // ...
546 // }
547 // }
548 // ```
549 //
550 // We can transform it into:
551 // ```javascript
552 // if (x && y) {
553 // ...
554 // }
555 // ```
556 CaseBody::If { check, body } => {
557 (docvec![check_doc, break_(" &&", " && "), check], body)
558 }
559
560 // The following code is a pretty common pattern in the code
561 // generated by decision trees:
562 //
563 // ```javascript
564 // if (something) {
565 // if (something_else) {
566 // do_thing()
567 // } else {
568 // do_fallback()
569 // }
570 // } else {
571 // do_fallback()
572 // }
573 // ```
574 //
575 // Here, the `do_fallback()` branch is repeated, which we want
576 // to avoid if possible. In this case, we can transform the above
577 // code into the following:
578 //
579 // ```javascript
580 // if (something && something_else) {
581 // do_thing()
582 // } else {
583 // do_fallback()
584 // }
585 // ```
586 //
587 // This only works if both `else` branches run the same code,
588 // otherwise we would be losing information.
589 // It also only works if the inner statement has only a single
590 // `else` clause, and not multiple `else if`s.
591 CaseBody::IfElse {
592 check,
593 if_body,
594 fallback_decision: decision,
595 ..
596 } if decision == fallback => {
597 (docvec![check_doc, break_(" &&", " && "), check], if_body)
598 }
599
600 if_else @ CaseBody::IfElse { .. } => (check_doc, if_else.into_doc()),
601
602 CaseBody::Statements(document) | CaseBody::IfElseChain(document) => {
603 (check_doc, document)
604 }
605 };
606
607 if_ = match if_ {
608 // The first statement will always be an `if`
609 _ if i == 0 => CaseBody::If {
610 check: check_doc,
611 body,
612 },
613 // If this is the second check, the `if` becomes `else if`
614 CaseBody::If { .. } | CaseBody::IfElse { .. } => CaseBody::IfElseChain(docvec![
615 if_.into_doc(),
616 " else if (",
617 break_("", "")
618 .append(check_doc)
619 .nest(INDENT)
620 .append(break_("", ""))
621 .group(),
622 ") ",
623 break_block(body)
624 ]),
625 CaseBody::IfElseChain(document) | CaseBody::Statements(document) => {
626 CaseBody::IfElseChain(docvec![
627 document,
628 " else if (",
629 break_("", "")
630 .append(check_doc)
631 .nest(INDENT)
632 .append(break_("", ""))
633 .group(),
634 ") ",
635 break_block(body)
636 ])
637 }
638 };
639 }
640
641 // In case there's some new variables we can extract after the
642 // successful final check we store those. But we don't need to perform
643 // the check itself: the type system ensures that, if we ever get here,
644 // the check is going to match no matter what!
645 if let FallbackCheck::RuntimeCheck { check } = fallback_check {
646 self.variables.record_check_assignments(var, check);
647 }
648
649 let else_body = self.inside_new_scope(|this| this.decision(fallback));
650 let document = if else_body.is_empty() {
651 if_
652 } else if let CaseBody::If {
653 check,
654 body: if_body,
655 } = if_
656 {
657 CaseBody::IfElse {
658 check,
659 if_body,
660 else_body: else_body.document_after_else(),
661 fallback_decision: fallback,
662 }
663 } else {
664 CaseBody::IfElseChain(docvec![
665 if_.into_doc(),
666 " else ",
667 else_body.document_after_else()
668 ])
669 };
670
671 if assignments.is_empty() {
672 document
673 } else {
674 CaseBody::Statements(join_with_line(
675 join(assignments, line()),
676 document.into_doc(),
677 ))
678 }
679 }
680
681 fn inside_new_scope<A, F>(&mut self, run: F) -> A
682 where
683 F: Fn(&mut Self) -> A,
684 {
685 // Since we use reassignment for `let assert`, we can't reset the scope
686 // as it loses data about the assigned variables.
687 let old_scope = match &self.kind {
688 DecisionKind::Case { .. } => self.variables.expression_generator.current_scope.clone(),
689 DecisionKind::LetAssert { .. } => Scope::default(),
690 };
691
692 let old_names = self.variables.scoped_variable_names.clone();
693 let old_segments = self.variables.segment_values.clone();
694 let old_segment_names = self.variables.scoped_segment_names.clone();
695 let output = run(self);
696
697 match &self.kind {
698 DecisionKind::Case { .. } => {
699 self.variables.expression_generator.current_scope = old_scope
700 }
701 DecisionKind::LetAssert { .. } => {}
702 }
703
704 self.variables.scoped_variable_names = old_names;
705 self.variables.segment_values = old_segments;
706 self.variables.scoped_segment_names = old_segment_names;
707 output
708 }
709
710 fn decision_guard(
711 &mut self,
712 guard: usize,
713 if_true: &'a Body,
714 if_false: &'a Decision,
715 ) -> CaseBody<'a> {
716 let DecisionKind::Case { clauses } = &self.kind else {
717 unreachable!("Guards cannot appear in let assert decision trees")
718 };
719
720 let guard = clauses
721 .get(guard)
722 .expect("invalid clause index")
723 .guard
724 .as_ref()
725 .expect("missing guard");
726
727 // Before generating the if-else condition we want to generate all the
728 // assignments that will be needed by the guard condition so we can rest
729 // assured they are in scope and the guard check can use those.
730 let guard_variables = guard.referenced_variables();
731 let (check_bindings, if_true_bindings): (Vec<_>, Vec<_>) = if_true
732 .bindings
733 .iter()
734 .partition(|(variable, _)| guard_variables.contains(variable));
735
736 let (check_bindings, check, if_true) = self.inside_new_scope(|this| {
737 // check_bindings and if_true generation have to be in this scope so that pattern-bound
738 // variables used in guards don't leak into other case branches (if_false).
739 let check_bindings = this.variables.bindings_ref_doc(&check_bindings);
740 let check = this.variables.expression_generator.guard(guard);
741 // All the other bindings that are not needed by the guard check will
742 // end up directly in the body of the if clause.
743 let if_true_bindings = this.variables.bindings_ref_doc(&if_true_bindings);
744 let if_true_body = this.body_expression(if_true.clause_index);
745 let if_true = match if_true_body {
746 BodyExpression::Variable(variable) => variable,
747 BodyExpression::Expressions(if_true_body) => {
748 join_with_line(if_true_bindings, if_true_body)
749 }
750 };
751 (check_bindings, check, if_true)
752 });
753
754 let if_false_body = self.inside_new_scope(|this| this.decision(if_false));
755
756 // We can now piece everything together into a case body!
757 let if_ = if if_false_body.is_empty() {
758 CaseBody::If {
759 check,
760 body: if_true,
761 }
762 } else {
763 CaseBody::IfElse {
764 check,
765 if_body: if_true,
766 else_body: if_false_body.document_after_else(),
767 fallback_decision: if_false,
768 }
769 };
770
771 if check_bindings.is_empty() {
772 if_
773 } else {
774 CaseBody::Statements(join_with_line(check_bindings, if_.into_doc()))
775 }
776 }
777
778 fn assignment_no_match(
779 &mut self,
780 subject: Document<'a>,
781 kind: &'a AssignmentKind<TypedExpr>,
782 subject_location: SrcSpan,
783 pattern_location: SrcSpan,
784 ) -> Document<'a> {
785 let AssignmentKind::Assert {
786 location, message, ..
787 } = kind
788 else {
789 unreachable!("inexhaustive let made it to code generation");
790 };
791
792 let generator = &mut self.variables.expression_generator;
793 let message = match message {
794 None => string("Pattern match failed, no pattern matched the value."),
795 Some(message) => generator
796 .not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(message)),
797 };
798 generator.throw_error(
799 "let_assert",
800 &message,
801 *location,
802 [
803 ("value", subject),
804 ("start", location.start.to_doc()),
805 ("end", subject_location.end.to_doc()),
806 ("pattern_start", pattern_location.start.to_doc()),
807 ("pattern_end", pattern_location.end.to_doc()),
808 ],
809 )
810 }
811}
812
813/// Returns the character code for the character being matched for patterns matching
814/// on single-character string prefixes.
815fn single_character_prefix_code(check: &RuntimeCheck) -> Option<u32> {
816 match check {
817 // On JavaScript, a single "character" is one that can be represented as
818 // a single UTF-16 codepoint.
819 RuntimeCheck::StringPrefix { prefix, rest } if utf16_no_escape_len(prefix) == 1 => {
820 convert_string_escape_chars(prefix)
821 .chars()
822 .next()
823 .map(|first| first as u32)
824 }
825 RuntimeCheck::Int { .. }
826 | RuntimeCheck::Float { .. }
827 | RuntimeCheck::String { .. }
828 | RuntimeCheck::StringPrefix { .. }
829 | RuntimeCheck::Tuple { .. }
830 | RuntimeCheck::BitArray { .. }
831 | RuntimeCheck::Variant { .. }
832 | RuntimeCheck::NonEmptyList { .. }
833 | RuntimeCheck::EmptyList => None,
834 }
835}
836
837/// Returns whether a `case` expression contains multiple patterns matching on
838/// the first character of a string.
839fn multiple_single_character_prefix_checks(choices: &[(RuntimeCheck, Decision)]) -> bool {
840 let mut encountered_check = false;
841
842 for (check, _) in choices.iter() {
843 if let RuntimeCheck::StringPrefix { prefix, .. } = check
844 && utf16_no_escape_len(prefix) == 1
845 {
846 if encountered_check {
847 return true;
848 } else {
849 encountered_check = true;
850 }
851 }
852 }
853
854 false
855}
856
857pub fn let_<'a>(
858 compiled_case: &'a CompiledCase,
859 subject: &'a TypedExpr,
860 kind: &'a AssignmentKind<TypedExpr>,
861 expression_generator: &mut Generator<'_, 'a>,
862 pattern: &'a TypedPattern,
863) -> Document<'a> {
864 let scope_position = expression_generator.scope_position.clone();
865 let variable_assignment_kind = match &compiled_case.tree {
866 // If the binding is exhaustive (so no runtime checks need to be done), then
867 // assignments don't need to be separate from declarations.
868 Decision::Switch {
869 choices, fallback, ..
870 } if choices.is_empty() && matches!(**fallback, Decision::Run { .. }) => {
871 VariableAssignment::Declare
872 }
873 Decision::Run { .. }
874 | Decision::Guard { .. }
875 | Decision::Switch { .. }
876 | Decision::Fail => VariableAssignment::Reassign,
877 };
878 let mut variables = Variables::new(expression_generator, variable_assignment_kind);
879
880 let assignment = variables.assign_let_subject(compiled_case, subject);
881 let assignment_name = assignment.name();
882 let assignments = vec![assignment];
883 let pattern_location = pattern.location();
884 let decision = CasePrinter {
885 variables,
886 assignments: &assignments,
887 kind: DecisionKind::LetAssert {
888 kind,
889 subject_location: subject.location(),
890 pattern_location,
891 subject: assignment_name.clone(),
892 },
893 }
894 .decision(&compiled_case.tree);
895
896 let assignments_doc = assignments_to_doc(expression_generator, assignments);
897
898 let beginning_assignments = match variable_assignment_kind {
899 // If the decision tree will generate declarations, don't declare them here.
900 VariableAssignment::Declare => nil(),
901 // If the decision tree will only assign, declare the variables here.
902 VariableAssignment::Reassign => {
903 // When we generate `let assert` statements, we want to produce code like
904 // this:
905 // ```javascript
906 // let some_var;
907 // let other_var;
908 // if (condition_to_check_pattern) {
909 // some_var = x;
910 // other_var = y;
911 // }
912 // ```
913 // This generates the code for binding the initial variables before the
914 // check so the scoping of them is correct.
915 //
916 // We must generate this after we generate the code for the decision tree
917 // itself as we might be re-binding variables which are used in the checks
918 // to determine whether the pattern matches or not.
919 concat(pattern.bound_variables().into_iter().map(|bound_variable| {
920 docvec![
921 "let ",
922 expression_generator.local_var(&bound_variable.name()),
923 ";",
924 line()
925 ]
926 }))
927 }
928 };
929
930 let doc = docvec![assignments_doc, beginning_assignments, decision.into_doc()];
931
932 match scope_position {
933 expression::Position::Expression(_) | expression::Position::Statement => doc,
934 expression::Position::Tail => docvec![doc, line(), "return ", assignment_name, ";"],
935 expression::Position::Assign(variable) => {
936 docvec![doc, line(), variable, " = ", assignment_name, ";"]
937 }
938 }
939}
940
941#[derive(Copy, Clone)]
942enum VariableAssignment {
943 Declare,
944 Reassign,
945}
946
947/// This is a useful piece of state that is kept separate from the generator
948/// itself so we can reuse it both with `case`s and `let`s without rewriting
949/// everything from scratch.
950///
951struct Variables<'generator, 'module, 'a> {
952 expression_generator: &'generator mut Generator<'module, 'a>,
953
954 /// Whether to bind variables using `let` as we do in `case` expressions,
955 /// or to reassign them as we do in `let assert` statements.
956 variable_assignment: VariableAssignment,
957
958 /// All the pattern variables will be assigned a specific value: being bound
959 /// to a constructor field, tuple element and so on. Pattern variables never
960 /// end up in the generated code but we replace them with their actual value.
961 /// We store those values as `EcoString`s in this map; the key is the pattern
962 /// variable's unique id.
963 ///
964 variable_values: HashMap<usize, EcoString>,
965
966 /// The same happens for bit array segments. Unlike pattern variables, we
967 /// identify those using their names and store their value as a `Document`.
968 segment_values: HashMap<EcoString, Document<'a>>,
969
970 /// When we discover new variables after a runtime check we don't immediately
971 /// generate assignments for each of them, because that could lead to wasted
972 /// work. Let's consider the following check:
973 ///
974 /// ```txt
975 /// a is Wibble(3, c, 1) -> c
976 /// a is _ -> 1
977 /// ```
978 ///
979 /// If we generated variables for it as soon as we enter its corresponding
980 /// branch we would find ourselves with this piece of code:
981 ///
982 /// ```js
983 /// if (a instanceof Wibble) {
984 /// let a$0 = wibble.0;
985 /// let a$1 = wibble.1;
986 /// let a$2 = wibble.2;
987 ///
988 /// // and now we go on checking these new variables
989 /// }
990 /// ```
991 ///
992 /// However, by extracting all the fields immediately we might end up doing
993 /// wasted work: as soon as we find out that `a$0 != 3` we don't even need
994 /// to check the other fields, we know the pattern can't match! So we
995 /// extracted two fields we're not even checking.
996 ///
997 /// To avoid this situation, we only bind a variable to a name right before
998 /// we're checking it so we're sure we're never generating useless bindings.
999 /// The previous example would become something like this:
1000 ///
1001 /// ```js
1002 /// if (a instanceof Wibble) {
1003 /// let a$0 = wibble.0;
1004 /// if (a$0 === 3) {
1005 /// let a$2 = wibble.2
1006 /// // further checks
1007 /// } else {
1008 /// return 1;
1009 /// }
1010 /// }
1011 /// ```
1012 ///
1013 /// In this map we store the name a variable is bound to in the current
1014 /// scope. For example here we know that `wibble.0` is bound to the name
1015 /// `a$0`.
1016 ///
1017 scoped_variable_names: HashMap<usize, EcoString>,
1018
1019 /// Once again, this is the same as `scoped_variable_names` with the
1020 /// difference that a segment is identified by its name.
1021 ///
1022 scoped_segment_names: HashMap<EcoString, EcoString>,
1023}
1024
1025impl<'generator, 'module, 'a> Variables<'generator, 'module, 'a> {
1026 fn new(
1027 expression_generator: &'generator mut Generator<'module, 'a>,
1028 variable_assignment: VariableAssignment,
1029 ) -> Self {
1030 Variables {
1031 expression_generator,
1032 variable_assignment,
1033 variable_values: HashMap::new(),
1034 scoped_variable_names: HashMap::new(),
1035 segment_values: HashMap::new(),
1036 scoped_segment_names: HashMap::new(),
1037 }
1038 }
1039
1040 /// Give a unique name to each of the subjects of a case expression and keep
1041 /// track of each of those names in case it needs to be referenced later.
1042 ///
1043 fn assign_case_subjects(
1044 &mut self,
1045 compiled_case: &'a CompiledCase,
1046 subjects: &'a [TypedExpr],
1047 ) -> Vec<SubjectAssignment<'a>> {
1048 let assignments = subjects
1049 .iter()
1050 .map(|subject| assign_subject(self.expression_generator, subject, Ordering::Strict))
1051 .collect_vec();
1052
1053 for (variable, assignment) in compiled_case
1054 .subject_variables
1055 .iter()
1056 .zip(assignments.iter())
1057 {
1058 // We need to record the fact that each subject corresponds to a
1059 // pattern variable.
1060 self.set_value(variable, assignment.name());
1061 self.bind(assignment.name(), variable);
1062 }
1063
1064 assignments
1065 }
1066
1067 /// Give a unique name to the subject of a let expression (if it needs one
1068 /// and it's not already a variable) and keep track of that name in case it
1069 /// needs to be referenced later.
1070 ///
1071 fn assign_let_subject(
1072 &mut self,
1073 compiled_case: &'a CompiledCase,
1074 subject: &'a TypedExpr,
1075 ) -> SubjectAssignment<'a> {
1076 let variable = compiled_case
1077 .subject_variables
1078 .first()
1079 .expect("decision tree with no subjects");
1080 let assignment = assign_subject(self.expression_generator, subject, Ordering::Loose);
1081 self.set_value(variable, assignment.name());
1082 self.bind(assignment.name(), variable);
1083 assignment
1084 }
1085
1086 fn local_var(&mut self, name: &EcoString) -> EcoString {
1087 self.expression_generator.local_var(name)
1088 }
1089
1090 fn next_local_var(&mut self, name: &EcoString) -> EcoString {
1091 self.expression_generator.next_local_var(name)
1092 }
1093
1094 /// Records that a given pattern `variable` has been assigned a runtime
1095 /// `value`. For example if we had something like this:
1096 ///
1097 /// ```txt
1098 /// a is Wibble(1, b) -> todo
1099 /// ```
1100 ///
1101 /// After a successful `is Wibble` check, we know we'd end up with two
1102 /// additional checks that look like this:
1103 ///
1104 /// ```txt
1105 /// a0 is 1, a1 is b -> todo
1106 /// ```
1107 ///
1108 /// But what's the runtime value of `a0` and `a1`? To get those we'd have to
1109 /// extract the two fields from `a`, so they would have a value that looks
1110 /// like this: `a[0]` and `a[1]`; these values are set with this `set_value`
1111 /// function as we discover them.
1112 ///
1113 fn set_value(&mut self, variable: &Variable, value: EcoString) {
1114 let _ = self.variable_values.insert(variable.id, value);
1115 }
1116
1117 /// This is conceptually the same as set value, but it's for bit array
1118 /// segments instead of pattern variables.
1119 fn set_segment_value(
1120 &mut self,
1121 bit_array: &Variable,
1122 segment_name: EcoString,
1123 read_action: &ReadAction,
1124 ) {
1125 let value = self.read_action_to_doc(bit_array, read_action);
1126 let _ = self.segment_values.insert(segment_name, value);
1127 }
1128
1129 /// During the code generation process we might end up having to generate
1130 /// code to materialises one of the pattern variables and gives it a name to
1131 /// be used to avoid repeating it every single time.
1132 ///
1133 /// For example if a pattern variable is referencing the fifth element in a
1134 /// list it's runtime value would look something like this:
1135 /// `list.tail.tail.tail.tail.head`; if we where to perform additional
1136 /// checks on this value, it would be quite wasteful to recompute it every
1137 /// single time. Imagine this piece of code:
1138 ///
1139 /// ```gleam
1140 /// case list {
1141 /// [_, _, _, _, 1] -> todo
1142 /// [_, _, _, _, 2] -> todo
1143 /// // ...
1144 /// _ -> todo
1145 /// }
1146 /// ```
1147 ///
1148 /// The corresponding check would end up looking something like this:
1149 ///
1150 /// ```js
1151 /// if (list.tail.tail.tail.tail.head === 1) {}
1152 /// else if (list.tail.tail.tail.tail.head === 2) {}
1153 /// // ...
1154 /// else {}
1155 /// ```
1156 ///
1157 /// So before a check we might want to bind a pattern variable to a name so
1158 /// we can use that to reference it in the check:
1159 ///
1160 /// ```js
1161 /// let $ = list.tail.tail.tail.tail.head;
1162 /// if ($ === 1) {}
1163 /// else if ($ === 2) {}
1164 /// // ...
1165 /// else {}
1166 /// ```
1167 ///
1168 /// This makes for neater code! These bindings are kept track of with this
1169 /// function.
1170 ///
1171 fn bind(&mut self, name: EcoString, variable: &Variable) {
1172 let _ = self.scoped_variable_names.insert(variable.id, name);
1173 }
1174
1175 /// This has the exact same purpose as `bind` but works with bit array
1176 /// segments instead of pattern variables introduced during the decision
1177 /// tree compilation.
1178 ///
1179 fn bind_segment(&mut self, bound_to_variable: EcoString, segment: EcoString) {
1180 let _ = self.scoped_segment_names.insert(segment, bound_to_variable);
1181 }
1182
1183 fn bindings_doc(&mut self, bindings: &'a [(EcoString, BoundValue)]) -> Document<'a> {
1184 let bindings =
1185 (bindings.iter()).map(|(variable, value)| self.body_binding_doc(variable, value));
1186 join(bindings, line())
1187 }
1188
1189 fn bindings_ref_doc(&mut self, bindings: &[&'a (EcoString, BoundValue)]) -> Document<'a> {
1190 let bindings =
1191 (bindings.iter()).map(|(variable, value)| self.body_binding_doc(variable, value));
1192 join(bindings, line())
1193 }
1194
1195 fn body_binding_doc(
1196 &mut self,
1197 variable_name: &'a EcoString,
1198 value: &'a BoundValue,
1199 ) -> Document<'a> {
1200 let local_variable_name = self.next_local_var(variable_name);
1201 let assigned_value = match value {
1202 BoundValue::Variable(variable) => self.get_value(variable).to_doc(),
1203 BoundValue::LiteralString(value) => string(value),
1204 BoundValue::LiteralFloat(value) => float(value),
1205 BoundValue::LiteralInt(value) => eco_string_int(eco_format!("{value}")),
1206 BoundValue::BitArraySlice {
1207 bit_array,
1208 read_action,
1209 } => self
1210 .get_segment_value(variable_name)
1211 .unwrap_or_else(|| self.read_action_to_doc(bit_array, read_action)),
1212 };
1213
1214 match self.variable_assignment {
1215 VariableAssignment::Declare => let_doc(local_variable_name.clone(), assigned_value),
1216 VariableAssignment::Reassign => {
1217 reassignment_doc(local_variable_name.clone(), assigned_value)
1218 }
1219 }
1220 }
1221
1222 /// Generates the document to perform a (possibly negated) runtime check on
1223 /// the given variable.
1224 ///
1225 fn runtime_check(
1226 &mut self,
1227 variable: &Variable,
1228 runtime_check: &'a RuntimeCheck,
1229 ) -> Document<'a> {
1230 let value = self.get_value(variable);
1231
1232 let equality = " === ";
1233
1234 match runtime_check {
1235 RuntimeCheck::String { value: expected } => docvec![value, equality, string(expected)],
1236 RuntimeCheck::Float {
1237 float_value: expected,
1238 } => docvec![value, equality, float_from_value(expected.value())],
1239 RuntimeCheck::Int {
1240 int_value: expected,
1241 } => docvec![value, equality, expected.clone()],
1242 RuntimeCheck::StringPrefix { prefix, .. } => {
1243 docvec![value, ".startsWith(", string(prefix), ")"]
1244 }
1245
1246 RuntimeCheck::BitArray { test } => match test {
1247 // In this case we need to check that the remaining part of the
1248 // bit array has a whole number of bytes.
1249 BitArrayTest::CatchAllIsBytes { size_so_far } => {
1250 if size_so_far.is_zero() {
1251 docvec![value, ".bitSize % 8", equality, "0"]
1252 } else {
1253 let size_so_far = self.offset_to_doc(size_so_far, true);
1254 let remaining_bits = docvec![value, ".bitSize - ", size_so_far];
1255 docvec!["(", remaining_bits, ") % 8", equality, "0"]
1256 }
1257 }
1258
1259 BitArrayTest::ReadSizeIsNotNegative { size } => {
1260 docvec![self.read_size_to_doc(size), " >= 0"]
1261 }
1262
1263 BitArrayTest::SegmentIsFiniteFloat {
1264 read_action:
1265 ReadAction {
1266 from: start,
1267 size,
1268 endianness,
1269 ..
1270 },
1271 } => {
1272 let start_doc = self.offset_to_doc(start, false);
1273 let end = match (start.constant_bits(), size.constant_bits()) {
1274 (Some(start), _) if start == BigInt::ZERO => self
1275 .read_size_to_doc(size)
1276 .expect("unexpected catch all size"),
1277 (Some(start), Some(end)) => (start + end).to_doc(),
1278 (_, _) => docvec![start_doc.clone(), " + ", self.read_size_to_doc(size)],
1279 };
1280 let check = self.bit_array_slice_to_float(value, start_doc, end, endianness);
1281
1282 docvec!["Number.isFinite(", check, ")"]
1283 }
1284
1285 // Here we need to make sure that the bit array has a specific
1286 // size.
1287 BitArrayTest::Size(SizeTest { operator, size }) => {
1288 let operator = match operator {
1289 SizeOperator::GreaterEqual => " >= ",
1290 SizeOperator::Equal => equality,
1291 };
1292 let size = self.offset_to_doc(size, false);
1293 docvec![value, ".bitSize", operator, size]
1294 }
1295
1296 // Finally, here we need to check that a given portion of the
1297 // bit array matches a given value.
1298 BitArrayTest::Match(MatchTest {
1299 value: expected,
1300 read_action,
1301 }) => match expected {
1302 BitArrayMatchedValue::LiteralString {
1303 value: _,
1304 encoding: _,
1305 bytes: expected,
1306 } => self.literal_string_segment_bytes_check(value, expected, read_action),
1307 BitArrayMatchedValue::LiteralFloat(expected) => {
1308 self.literal_float_segment_bytes_check(value, expected, read_action)
1309 }
1310 BitArrayMatchedValue::LiteralInt {
1311 value: expected, ..
1312 } => self.literal_int_segment_bytes_check(value, expected.clone(), read_action),
1313 BitArrayMatchedValue::Variable(..)
1314 | BitArrayMatchedValue::Discard(..)
1315 | BitArrayMatchedValue::Assign { .. } => {
1316 panic!("unreachable")
1317 }
1318 },
1319 },
1320
1321 // When checking on a tuple there's always going to be a single choice
1322 // and the code generation will always skip generating the check for it
1323 // as the type system ensures it must match.
1324 RuntimeCheck::Tuple { .. } => unreachable!("tried generating runtime check for tuple"),
1325
1326 // Some variants like `Bool` and `Result` are special cased and checked
1327 // in a different way from all other variants.
1328 RuntimeCheck::Variant { match_, .. } if variable.type_.is_bool() => {
1329 match match_.used_name().as_str() {
1330 "True" => value.to_doc(),
1331 _ => docvec!["!", value],
1332 }
1333 }
1334
1335 RuntimeCheck::Variant {
1336 match_,
1337 index,
1338 fields,
1339 ..
1340 } => {
1341 if variable.type_.is_result() && match_.module().is_none() {
1342 if *index == 0 {
1343 self.expression_generator.tracker.ok_used = true;
1344 } else {
1345 self.expression_generator.tracker.error_used = true;
1346 }
1347 }
1348
1349 let qualification = match_
1350 .module()
1351 .map(|module| eco_format!("${module}."))
1352 .unwrap_or_default();
1353
1354 // If this variant has no fields, register it as being used
1355 // so that we know to import it.
1356 if fields.is_empty()
1357 && let Some((package, module, type_name)) =
1358 variable.type_.named_type_name_and_package()
1359 {
1360 _ = self
1361 .expression_generator
1362 .tracker
1363 .variants_used_in_instanceof
1364 .insert(TypeVariant {
1365 package,
1366 module,
1367 type_name,
1368 name: match_.variant_name(),
1369 });
1370 }
1371
1372 docvec![value, " instanceof ", qualification, match_.used_name()]
1373 }
1374
1375 RuntimeCheck::NonEmptyList { .. } => {
1376 self.expression_generator.tracker.list_non_empty_class_used = true;
1377 docvec![value, " instanceof $NonEmpty"]
1378 }
1379
1380 RuntimeCheck::EmptyList => {
1381 self.expression_generator.tracker.list_empty_class_used = true;
1382 docvec![value, " instanceof $Empty"]
1383 }
1384 }
1385 }
1386
1387 /// Turns a read action into a document that can be used to extract the
1388 /// corresponding value from the given bit array and assign it to a
1389 /// variable.
1390 ///
1391 fn read_action_to_doc(
1392 &mut self,
1393 bit_array: &Variable,
1394 read_action: &ReadAction,
1395 ) -> Document<'a> {
1396 let ReadAction {
1397 from,
1398 size,
1399 type_,
1400 endianness,
1401 signed,
1402 } = read_action;
1403 let bit_array = self.get_value(bit_array);
1404 let from_bits = from.constant_bits();
1405
1406 // There's two special cases we need to take care of:
1407 match (size, &from_bits) {
1408 // If we're reading a single byte as un unsigned int from a byte aligned
1409 // offset then we can optimise this call as a `.byteAt` call!
1410 (ReadSize::ConstantBits(size), Some(from_bits))
1411 if type_.is_int()
1412 && *size == BigInt::from(8)
1413 && !signed
1414 && from_bits.clone() % 8 == BigInt::ZERO =>
1415 {
1416 let from_byte: BigInt = from_bits / 8;
1417 return docvec![bit_array, ".byteAt(", from_byte, ")"];
1418 }
1419
1420 // If we're reading all the remaining bits/bytes of an array we'll
1421 // take the remaining slice.
1422 (ReadSize::RemainingBits | ReadSize::RemainingBytes, _) => {
1423 return self.bit_array_slice(bit_array, from);
1424 }
1425
1426 _ => (),
1427 }
1428
1429 // Otherwise we'll take a regular slice out of the bit array, depending
1430 // on the type of the segment.
1431 let (start, end) =
1432 if let (ReadSize::ConstantBits(size), Some(from_bits)) = (size, from_bits) {
1433 // If both the start and and are known at compile time we can use
1434 // those directly in the slice call and perform no addition at
1435 // runtime.
1436 let start = from_bits.clone().to_doc();
1437 let end = (from_bits + size).to_doc();
1438 (start, end)
1439 } else {
1440 // Otherwise we'll have to sum the variable part and the constant
1441 // one to tell how long the slice should be.
1442 let size = self.read_size_to_doc(size).expect("no variable size");
1443 let start = self.offset_to_doc(from, false);
1444 let end = if from.is_zero() {
1445 size
1446 } else {
1447 docvec![start.clone(), " + ", size]
1448 };
1449 (start, end)
1450 };
1451
1452 match type_ {
1453 ReadType::Int => {
1454 self.bit_array_slice_to_int(bit_array, start, end, endianness, *signed)
1455 }
1456 ReadType::Float => self.bit_array_slice_to_float(bit_array, start, end, endianness),
1457 ReadType::BitArray => self.bit_array_slice_with_end(bit_array, from, end),
1458 ReadType::String | ReadType::UtfCodepoint => {
1459 panic!("invalid slice type made it to code generation: {type_:#?}")
1460 }
1461 }
1462 }
1463
1464 fn offset_to_doc(&mut self, offset: &Offset, parenthesise: bool) -> Document<'a> {
1465 if offset.is_zero() {
1466 return "0".to_doc();
1467 }
1468
1469 let mut pieces = vec![];
1470 if offset.constant != BigInt::ZERO {
1471 pieces.push(eco_string_int(offset.constant.to_string().into()));
1472 }
1473
1474 for (variable, times) in offset
1475 .variables
1476 .iter()
1477 .sorted_by(|(one, _), (other, _)| one.name().cmp(other.name()))
1478 {
1479 let mut variable = match variable {
1480 VariableUsage::PatternSegment(segment_name, _) => self
1481 .get_segment_value(segment_name)
1482 .expect("segment referenced in a check before being created"),
1483 VariableUsage::OutsideVariable(name) => self.local_var(name).to_doc(),
1484 };
1485 if *times != 1 {
1486 variable = variable.append(" * ").append(*times)
1487 }
1488 pieces.push(variable.to_doc())
1489 }
1490
1491 for calculation in offset.calculations.iter() {
1492 let left = self.offset_to_doc(&calculation.left, true);
1493 let right = self.offset_to_doc(&calculation.right, true);
1494
1495 let calculation = self.expression_generator.bin_op_with_doc_operands(
1496 calculation.operator.to_bin_op(),
1497 left,
1498 right,
1499 &crate::type_::int(),
1500 );
1501
1502 if parenthesise {
1503 pieces.push(calculation.surround("(", ")"))
1504 } else {
1505 pieces.push(calculation)
1506 }
1507 }
1508
1509 if pieces.len() > 1 && parenthesise {
1510 docvec!["(", join(pieces, " + ".to_doc()), ")"]
1511 } else {
1512 join(pieces, " + ".to_doc())
1513 }
1514 }
1515
1516 /// If the read size has a constant value (that is, it's not a "read all the
1517 /// remaining bits/bytes") this returns a document representing that size.
1518 ///
1519 fn read_size_to_doc(&mut self, size: &ReadSize) -> Option<Document<'a>> {
1520 match size {
1521 ReadSize::ConstantBits(value) => Some(value.clone().to_doc()),
1522 ReadSize::VariableBits { variable, unit } => {
1523 let variable = self.local_var(variable.name());
1524 Some(if *unit == 1 {
1525 variable.to_doc()
1526 } else {
1527 docvec![variable, " * ", *unit as i64]
1528 })
1529 }
1530 ReadSize::RemainingBits | ReadSize::RemainingBytes => None,
1531
1532 ReadSize::BinaryOperator {
1533 left,
1534 right,
1535 operator,
1536 } => {
1537 let left = if self.read_size_must_be_wrapped(left) {
1538 self.read_size_to_doc(left)?.surround("(", ")")
1539 } else {
1540 self.read_size_to_doc(left)?
1541 };
1542 let right = if self.read_size_must_be_wrapped(right) {
1543 self.read_size_to_doc(right)?.surround("(", ")")
1544 } else {
1545 self.read_size_to_doc(right)?
1546 };
1547
1548 Some(self.expression_generator.bin_op_with_doc_operands(
1549 operator.to_bin_op(),
1550 left,
1551 right,
1552 &crate::type_::int(),
1553 ))
1554 }
1555 }
1556 }
1557
1558 fn read_size_must_be_wrapped(&self, size: &ReadSize) -> bool {
1559 match size {
1560 ReadSize::ConstantBits(_) | ReadSize::RemainingBits | ReadSize::RemainingBytes => false,
1561
1562 ReadSize::VariableBits { unit, .. } => *unit != 1,
1563 ReadSize::BinaryOperator { .. } => true,
1564 }
1565 }
1566
1567 /// Generates the document that calls the `bitArraySliceToInt` function, with
1568 /// the given arguments.
1569 ///
1570 fn bit_array_slice_to_int(
1571 &mut self,
1572 bit_array: impl Documentable<'a>,
1573 start: impl Documentable<'a>,
1574 end: impl Documentable<'a>,
1575 endianness: &Endianness,
1576 signed: bool,
1577 ) -> Document<'a> {
1578 self.expression_generator
1579 .tracker
1580 .bit_array_slice_to_int_used = true;
1581
1582 let endianness = match endianness {
1583 Endianness::Big => "true",
1584 Endianness::Little => "false",
1585 };
1586 let signed = if signed { "true" } else { "false" };
1587 let arguments = join(
1588 [
1589 bit_array.to_doc(),
1590 start.to_doc(),
1591 end.to_doc(),
1592 endianness.to_doc(),
1593 signed.to_doc(),
1594 ],
1595 ", ".to_doc(),
1596 );
1597 docvec!["bitArraySliceToInt(", arguments, ")"]
1598 }
1599
1600 /// Generates the document that calls the `bitArraySliceToFloat` function,
1601 /// with the given arguments.
1602 ///
1603 fn bit_array_slice_to_float(
1604 &mut self,
1605 bit_array: impl Documentable<'a>,
1606 start: impl Documentable<'a>,
1607 end: impl Documentable<'a>,
1608 endianness: &Endianness,
1609 ) -> Document<'a> {
1610 self.expression_generator
1611 .tracker
1612 .bit_array_slice_to_float_used = true;
1613
1614 let endianness = match endianness {
1615 Endianness::Big => "true",
1616 Endianness::Little => "false",
1617 };
1618 let arguments = join(
1619 [
1620 bit_array.to_doc(),
1621 start.to_doc(),
1622 end.to_doc(),
1623 endianness.to_doc(),
1624 ],
1625 ", ".to_doc(),
1626 );
1627 docvec!["bitArraySliceToFloat(", arguments, ")"]
1628 }
1629
1630 /// Generates the document that calls the `bitArraySlice` function, with
1631 /// an end argument as well. If you need to take a slice that starts at a
1632 /// given offset and read the entire array you can use `bit_array_slice`.
1633 ///
1634 fn bit_array_slice_with_end(
1635 &mut self,
1636 bit_array: impl Documentable<'a>,
1637 from: &Offset,
1638 end: impl Documentable<'a>,
1639 ) -> Document<'a> {
1640 self.expression_generator.tracker.bit_array_slice_used = true;
1641 let from = self.offset_to_doc(from, false);
1642 docvec!["bitArraySlice(", bit_array, ", ", from, ", ", end, ")"]
1643 }
1644
1645 /// Generates the document that calls the `bitArraySlice` function, starting
1646 /// at a given offset. This will read the entire remaining bit of the array,
1647 /// if you know that the slice should end at a given offset you can use
1648 /// `bit_array_slice_with_end` instead.
1649 ///
1650 fn bit_array_slice(&mut self, bit_array: impl Documentable<'a>, from: &Offset) -> Document<'a> {
1651 self.expression_generator.tracker.bit_array_slice_used = true;
1652 let from = self.offset_to_doc(from, false);
1653 docvec!["bitArraySlice(", bit_array, ", ", from, ")"]
1654 }
1655
1656 /// This generates all the checks that need to be performed to make sure a
1657 /// bit array segment (obtained with the read action passed as argument)
1658 /// matches with a literal string.
1659 ///
1660 fn literal_string_segment_bytes_check(
1661 &mut self,
1662 // A string representing the bit array value we read bits from.
1663 bit_array: EcoString,
1664 // The bytes of the literal string we should be matching on.
1665 string_bytes: &Vec<u8>,
1666 read_action: &ReadAction,
1667 ) -> Document<'a> {
1668 let ReadAction {
1669 from: start,
1670 endianness,
1671 signed,
1672 ..
1673 } = read_action;
1674 let mut checks = vec![];
1675
1676 let equality = " === ";
1677
1678 let bytes = string_bytes.as_slice();
1679
1680 if let Some(mut from_byte) = start.constant_bytes() {
1681 // If the string starts at a compile-time known byte, then we can
1682 // optimise this by reading all the subsequent bytes and checking
1683 // they have a specific value.
1684 for byte in bytes {
1685 let byte_access = docvec![bit_array.clone(), ".byteAt(", from_byte.clone(), ")"];
1686 checks.push(docvec![byte_access, equality, byte]);
1687 from_byte += 1;
1688 }
1689 } else {
1690 let mut start = start.clone();
1691
1692 // If the string doesn't start at a byte aligned offset then we'll
1693 // have to take slices out of it to check that each byte matches.
1694 for byte in bytes {
1695 let start_doc = self.offset_to_doc(&start, false);
1696 let end = start.add_constant(8);
1697 let end_doc = self.offset_to_doc(&end, false);
1698 let byte_access = self
1699 .bit_array_slice_to_int(&bit_array, start_doc, end_doc, endianness, *signed);
1700 checks.push(docvec![byte_access, equality, byte]);
1701 start = end;
1702 }
1703 }
1704
1705 // Otherwise the check succeeds if all the byte checks succeed.
1706 join(checks, break_(" &&", " && ")).nest(INDENT).group()
1707 }
1708
1709 /// This generates all the checks that need to be performed to make sure a
1710 /// bit array segment (obtained with the read action passed as argument)
1711 /// matches with a literal int.
1712 ///
1713 fn literal_int_segment_bytes_check(
1714 &mut self,
1715 // A string representing the bit array value we read bits from.
1716 bit_array: EcoString,
1717 literal_int: BigInt,
1718 read_action: &ReadAction,
1719 ) -> Document<'a> {
1720 let ReadAction {
1721 from: start,
1722 size,
1723 endianness,
1724 signed,
1725 ..
1726 } = read_action;
1727
1728 let equality = " === ";
1729
1730 if let (Some(mut from_byte), Some(size)) = (start.constant_bytes(), size.constant_bytes()) {
1731 // If the number starts at a byte-aligned offset and is made of a
1732 // whole number of bytes then we can optimise this by checking that
1733 // all the bytes starting at the given offset match the int bytes.
1734 let mut checks = vec![];
1735 for byte in bit_array_segment_int_value_to_bytes(literal_int, size * 8, *endianness) {
1736 let byte_access = docvec![bit_array.clone(), ".byteAt(", from_byte.clone(), ")"];
1737 checks.push(docvec![byte_access, equality, byte]);
1738 from_byte += 1;
1739 }
1740
1741 join(checks, break_(" &&", " && ")).nest(INDENT).group()
1742 } else {
1743 // Otherwise we have to take an int slice out of the bit array and
1744 // check it matches the expected value.
1745 let start_doc = self.offset_to_doc(start, false);
1746 let end = match (start.constant_bits(), size.constant_bits()) {
1747 (Some(start), _) if start == BigInt::ZERO => self
1748 .read_size_to_doc(size)
1749 .expect("unexpected catch all size"),
1750 (Some(start), Some(end)) => (start + end).to_doc(),
1751 (_, _) => docvec![start_doc.clone(), " + ", self.read_size_to_doc(size)],
1752 };
1753 let check = self.bit_array_slice_to_int(bit_array, start_doc, end, endianness, *signed);
1754 docvec![check, equality, literal_int]
1755 }
1756 }
1757
1758 /// This generates all the checks that need to be performed to make sure a
1759 /// bit array segment (obtained with the read action passed as argument)
1760 /// matches with a literal float.
1761 ///
1762 fn literal_float_segment_bytes_check(
1763 &mut self,
1764 // A string representing the bit array value we read bits from.
1765 bit_array: EcoString,
1766 expected: &EcoString,
1767 read_action: &ReadAction,
1768 ) -> Document<'a> {
1769 let ReadAction {
1770 from: start,
1771 size,
1772 endianness,
1773 ..
1774 } = read_action;
1775
1776 let equality = " === ";
1777
1778 // Unlike literal integers and strings, for now we don't try and apply any
1779 // optimisation in the way we match on those: we take an entire slice,
1780 // convert it to a float and check if it matches the expected value.
1781 let start_doc = self.offset_to_doc(start, false);
1782 let end = match (start.constant_bits(), size.constant_bits()) {
1783 (Some(start), _) if start == BigInt::ZERO => self
1784 .read_size_to_doc(size)
1785 .expect("unexpected catch all size"),
1786 (Some(start), Some(end)) => (start + end).to_doc(),
1787 (_, _) => docvec![start_doc.clone(), " + ", self.read_size_to_doc(size)],
1788 };
1789 let check = self.bit_array_slice_to_float(bit_array, start_doc, end, endianness);
1790 docvec![check, equality, expected]
1791 }
1792
1793 #[must_use]
1794 fn is_bound_in_scope(&self, variable: &Variable) -> bool {
1795 self.scoped_variable_names.contains_key(&variable.id)
1796 }
1797
1798 #[must_use]
1799 fn segment_is_bound_in_scope(&self, segment_name: &EcoString) -> bool {
1800 self.scoped_segment_names.contains_key(segment_name)
1801 }
1802
1803 /// In case the check introduces new variables, this will record their
1804 /// actual value to be used by later checks and assignments.
1805 ///
1806 fn record_check_assignments(&mut self, variable: &Variable, check: &RuntimeCheck) {
1807 let value = self.get_value(variable);
1808 match check {
1809 RuntimeCheck::Int { .. }
1810 | RuntimeCheck::Float { .. }
1811 | RuntimeCheck::String { .. }
1812 | RuntimeCheck::EmptyList => (),
1813
1814 RuntimeCheck::BitArray { test } => {
1815 for (segment_name, read_action) in test.referenced_segment_patterns() {
1816 self.set_segment_value(variable, segment_name.clone(), read_action)
1817 }
1818 }
1819
1820 RuntimeCheck::StringPrefix { rest, prefix } => {
1821 let prefix_size = utf16_no_escape_len(prefix);
1822 self.set_value(rest, eco_format!("{value}.slice({prefix_size})"));
1823 }
1824
1825 RuntimeCheck::Tuple { elements, .. } => {
1826 for (i, element) in elements.iter().enumerate() {
1827 self.set_value(element, eco_format!("{value}[{i}]"));
1828 }
1829 }
1830
1831 RuntimeCheck::Variant { fields, labels, .. } => {
1832 for (i, field) in fields.iter().enumerate() {
1833 let access = match labels.get(&i) {
1834 Some(label) => eco_format!("{value}.{}", maybe_escape_property(label)),
1835 None => eco_format!("{value}[{i}]"),
1836 };
1837 self.set_value(field, access);
1838 }
1839 }
1840
1841 RuntimeCheck::NonEmptyList { first, rest } => {
1842 self.set_value(first, eco_format!("{value}.head"));
1843 self.set_value(rest, eco_format!("{value}.tail"));
1844 }
1845 }
1846 }
1847
1848 /// A runtime check might need to reference some bit array segments in its
1849 /// check (for example if a bit array length depends on a previous segment).
1850 /// This function returns a vector with all the assignments needed to bring
1851 /// the referenced segments into scope, so they're available to use for the
1852 /// runtime check.
1853 ///
1854 fn bit_array_segment_assignments(&mut self, check: &RuntimeCheck) -> Vec<Document<'a>> {
1855 let mut check_assignments = vec![];
1856 for (segment, _) in check.referenced_segment_patterns() {
1857 // If the segment was already bound to a variable in this scope we
1858 // don't need to generate any further assignment for it. We will just
1859 // reuse that existing variable when we need to access this segment
1860 if self.segment_is_bound_in_scope(segment) {
1861 continue;
1862 }
1863
1864 let variable_name = self.next_local_var(segment);
1865 let segment_value = self
1866 .get_segment_value(segment)
1867 .expect("segment referenced in a check before being created");
1868 self.bind_segment(variable_name.clone(), segment.clone());
1869 check_assignments.push(let_doc(variable_name, segment_value))
1870 }
1871 check_assignments
1872 }
1873
1874 /// Returns a string representing the value of a pattern variable: it might
1875 /// be the code needed to obtain such variable (for example accessing a
1876 /// list item `wibble.head`), or it could be a name this variable was bound
1877 /// to in the current scope to avoid doing any repeated work!
1878 ///
1879 fn get_value(&self, variable: &Variable) -> EcoString {
1880 // If the pattern variable was already assigned to a variable that is
1881 // in scope we use that variable name!
1882 if let Some(name) = self.scoped_variable_names.get(&variable.id) {
1883 return name.clone();
1884 }
1885
1886 // Otherwise we fallback to using its value directly.
1887 self.variable_values
1888 .get(&variable.id)
1889 .expect("pattern variable used before assignment")
1890 .clone()
1891 }
1892
1893 fn get_segment_value(&self, segment_name: &EcoString) -> Option<Document<'a>> {
1894 // If the segment was already assigned to a variable that is in scope
1895 // we use that variable name!
1896 if let Some(name) = self.scoped_segment_names.get(segment_name) {
1897 return Some(name.clone().to_doc());
1898 }
1899
1900 // Otherwise we fallback to using its value directly.
1901 self.segment_values.get(segment_name).cloned()
1902 }
1903}
1904
1905/// When going over the subjects of a case expression/let we might end up in two
1906/// situation: the subject might be a variable or it could be a more complex
1907/// expression (like a function call, a complex expression, ...).
1908///
1909/// ```gleam
1910/// case a_variable { ... }
1911/// case a_function_call(wobble) { ... }
1912/// ```
1913///
1914/// When checking on a case we might end up repeating the subjects multiple times
1915/// (as they need to appear in various checks), this means that if we ended up
1916/// doing the simple thing of just repeating the subject as it is, we might end
1917/// up dramatically changing the meaning of the program when the subject is a
1918/// complex expression! Imagine this example:
1919///
1920/// ```gleam
1921/// case wibble("a") {
1922/// 1 -> todo
1923/// 2 -> todo
1924/// _ -> todo
1925/// }
1926/// ```
1927///
1928/// If we just repeated the subject every time we need to check it, the decision
1929/// tree would end up looking something like this:
1930///
1931/// ```js
1932/// if (wibble("a") === 1) {}
1933/// else if (wibble("a") === 2) {}
1934/// else {}
1935/// ```
1936///
1937/// It would be quite bad as we would end up running the same function multiple
1938/// times instead of just once!
1939///
1940/// So we need to split each subject in two categories: if it is a simple
1941/// variable already, it's no big deal and we can repeat that name as many times
1942/// as we want; however, if it's anything else we first need to bind that subject
1943/// to a variable we can then reference multiple times.
1944///
1945enum SubjectAssignment<'a> {
1946 /// The subject is a complex expression with a `value` that has to be
1947 /// assigned to a variable with the given `name` as repeating the `value`
1948 /// multiple times could possibly change the meaning of the program.
1949 BindToVariable {
1950 name: EcoString,
1951 value: Document<'a>,
1952 location: SrcSpan,
1953 },
1954 /// The subject is already a simple variable with the given name, we will
1955 /// keep using that name to reference it.
1956 AlreadyAVariable { name: EcoString },
1957}
1958
1959impl SubjectAssignment<'_> {
1960 fn name(&self) -> EcoString {
1961 match self {
1962 SubjectAssignment::BindToVariable {
1963 name,
1964 value: _,
1965 location: _,
1966 }
1967 | SubjectAssignment::AlreadyAVariable { name } => name.clone(),
1968 }
1969 }
1970}
1971
1972fn assign_subject<'a>(
1973 expression_generator: &mut Generator<'_, 'a>,
1974 subject: &'a TypedExpr,
1975 ordering: Ordering,
1976) -> SubjectAssignment<'a> {
1977 static ASSIGNMENT_VAR_ECO_STR: OnceLock<EcoString> = OnceLock::new();
1978
1979 // If the value is a variable we don't need to assign it to a new
1980 // variable, we can use the value expression safely without worrying about
1981 // performing computation or side effects multiple times.
1982 if let TypedExpr::Var {
1983 name, constructor, ..
1984 } = subject
1985 && constructor.is_local_variable()
1986 {
1987 SubjectAssignment::AlreadyAVariable {
1988 name: expression_generator.local_var(name),
1989 }
1990 } else {
1991 // If it's not a variable we need to assign it to a variable
1992 // to avoid rendering the subject expression multiple times
1993 let name = expression_generator
1994 .next_local_var(ASSIGNMENT_VAR_ECO_STR.get_or_init(|| ASSIGNMENT_VAR.into()));
1995 let value = expression_generator
1996 .not_in_tail_position(Some(ordering), |this| this.wrap_expression(subject));
1997
1998 SubjectAssignment::BindToVariable {
1999 value,
2000 name,
2001 location: subject.location(),
2002 }
2003 }
2004}
2005
2006fn assignments_to_doc<'a>(
2007 expression_generator: &mut Generator<'_, 'a>,
2008 assignments: Vec<SubjectAssignment<'a>>,
2009) -> Document<'a> {
2010 let mut assignments_docs = vec![];
2011 for assignment in assignments.into_iter() {
2012 let SubjectAssignment::BindToVariable {
2013 name,
2014 value,
2015 location,
2016 } = assignment
2017 else {
2018 continue;
2019 };
2020 assignments_docs.push(docvec![
2021 expression_generator.source_map_tracker(location.start),
2022 let_doc(name, value),
2023 line()
2024 ])
2025 }
2026 assignments_docs.to_doc()
2027}
2028
2029/// Appends the second document to the first one separating the two with a newline.
2030/// However, if the second document is empty the empty line is not added.
2031///
2032fn join_with_line<'a>(one: Document<'a>, other: Document<'a>) -> Document<'a> {
2033 if one.is_empty() {
2034 other
2035 } else if other.is_empty() {
2036 one
2037 } else {
2038 docvec![one, line(), other]
2039 }
2040}
2041
2042fn reassignment_doc(variable_name: EcoString, value: Document<'_>) -> Document<'_> {
2043 docvec![variable_name, " = ", value, ";"]
2044}
2045
2046fn let_doc(variable_name: EcoString, value: Document<'_>) -> Document<'_> {
2047 docvec!["let ", variable_name, " = ", value, ";"]
2048}
2049
2050/// Calculates the length of str as utf16 without escape characters.
2051///
2052fn utf16_no_escape_len(str: &EcoString) -> usize {
2053 length_utf16(&convert_string_escape_chars(str))
2054}
2055
2056pub fn break_block(doc: Document<'_>) -> Document<'_> {
2057 "{".to_doc()
2058 .append(line().append(doc).nest(INDENT))
2059 .append(line())
2060 .append("}")
2061 .force_break()
2062}