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