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