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