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