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