Fork of daniellemaywood.uk/gleam — Wasm codegen work
89 kB
2371 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 BoundValue::StringSlice { subject, prefix } => {
1293 let subject_value = self.get_value(subject);
1294 let prefix_size = utf16_no_escape_len(prefix);
1295 docvec![arena, subject_value, ".slice(", prefix_size, ")"]
1296 }
1297 };
1298
1299 match self.variable_assignment {
1300 VariableAssignment::Declare => let_doc(arena, local_variable_name, assigned_value),
1301 VariableAssignment::Reassign => {
1302 reassignment_doc(arena, local_variable_name, assigned_value)
1303 }
1304 }
1305 }
1306
1307 /// Generates the document to perform a (possibly negated) runtime check on
1308 /// the given variable.
1309 ///
1310 fn runtime_check(
1311 &mut self,
1312 arena: &'doc DocumentArena<'a, 'doc>,
1313 variable: &Variable,
1314 runtime_check: &'a RuntimeCheck,
1315 ) -> Document<'a, 'doc> {
1316 let value = self.get_value(variable);
1317
1318 match runtime_check {
1319 RuntimeCheck::String { value: expected } => {
1320 docvec![
1321 arena,
1322 value,
1323 SPACE_TRIPLE_EQUAL_SPACE_DOCUMENT,
1324 string(arena, expected)
1325 ]
1326 }
1327 RuntimeCheck::Float {
1328 float_value: expected,
1329 } => docvec![
1330 arena,
1331 value,
1332 SPACE_TRIPLE_EQUAL_SPACE_DOCUMENT,
1333 float_from_value(arena, expected.value())
1334 ],
1335 RuntimeCheck::Int {
1336 int_value: expected,
1337 } => docvec![
1338 arena,
1339 value,
1340 SPACE_TRIPLE_EQUAL_SPACE_DOCUMENT,
1341 expected.clone()
1342 ],
1343 RuntimeCheck::StringPrefix { prefix, .. } => {
1344 docvec![
1345 arena,
1346 value,
1347 DOT_STARTS_WITH_OPEN_PAREN_DOCUMENT,
1348 string(arena, prefix),
1349 CLOSE_PAREN_DOCUMENT
1350 ]
1351 }
1352
1353 RuntimeCheck::BitArray { test } => match test {
1354 // In this case we need to check that the remaining part of the
1355 // bit array has a whole number of bytes.
1356 BitArrayTest::CatchAllIsBytes { size_so_far } => {
1357 if size_so_far.is_zero() {
1358 docvec![
1359 arena,
1360 value,
1361 DOT_BIT_SIZE_MODULO_8_DOCUMENT,
1362 SPACE_TRIPLE_EQUAL_SPACE_DOCUMENT,
1363 ZERO_DOCUMENT,
1364 ]
1365 } else {
1366 let size_so_far = self.offset_to_doc(arena, size_so_far, true);
1367 let remaining_bits =
1368 docvec![arena, value, DOT_BIT_SIZE_MINUS_SPACE_DOCUMENT, size_so_far];
1369 docvec![
1370 arena,
1371 OPEN_PAREN_DOCUMENT,
1372 remaining_bits,
1373 CLOSE_PAREN_MODULO_8_DOCUMENT,
1374 SPACE_TRIPLE_EQUAL_SPACE_DOCUMENT,
1375 ZERO_DOCUMENT
1376 ]
1377 }
1378 }
1379
1380 BitArrayTest::ReadSizeIsNotNegative { size } => {
1381 docvec![
1382 arena,
1383 self.read_size_to_doc(arena, size).expect("empty size"),
1384 SPACE_GT_EQ_ZERO_DOCUMENT
1385 ]
1386 }
1387
1388 BitArrayTest::SegmentIsFiniteFloat {
1389 read_action:
1390 ReadAction {
1391 from: start,
1392 size,
1393 endianness,
1394 ..
1395 },
1396 } => {
1397 let start_doc = self.offset_to_doc(arena, start, false);
1398 let end = match (start.constant_bits(), size.constant_bits()) {
1399 (Some(start), _) if start == BigInt::ZERO => self
1400 .read_size_to_doc(arena, size)
1401 .expect("unexpected catch all size"),
1402 (Some(start), Some(end)) => (start + end).to_doc(arena),
1403 (_, _) => {
1404 docvec![
1405 arena,
1406 start_doc,
1407 SPACE_PLUS_SPACE_DOCUMENT,
1408 self.read_size_to_doc(arena, size).expect("empty size")
1409 ]
1410 }
1411 };
1412 let check =
1413 self.bit_array_slice_to_float(arena, value, start_doc, end, endianness);
1414
1415 docvec![
1416 arena,
1417 GLOBAL_THIS_DOT_NUMBER_DOT_IS_FINITE_OPEN_PAREN_DOCUMENT,
1418 check,
1419 CLOSE_PAREN_DOCUMENT
1420 ]
1421 }
1422
1423 // Here we need to make sure that the bit array has a specific
1424 // size.
1425 BitArrayTest::Size(SizeTest { operator, size }) => {
1426 let operator = match operator {
1427 SizeOperator::GreaterEqual => SPACE_GT_EQ_SPACE_DOCUMENT,
1428 SizeOperator::Equal => SPACE_TRIPLE_EQUAL_SPACE_DOCUMENT,
1429 };
1430 let size = self.offset_to_doc(arena, size, false);
1431 docvec![arena, value, DOT_BIT_SIZE_DOCUMENT, operator, size]
1432 }
1433
1434 // Finally, here we need to check that a given portion of the
1435 // bit array matches a given value.
1436 BitArrayTest::Match(MatchTest {
1437 value: expected,
1438 read_action,
1439 }) => match expected {
1440 BitArrayMatchedValue::LiteralString {
1441 value: _,
1442 encoding: _,
1443 bytes: expected,
1444 } => {
1445 self.literal_string_segment_bytes_check(arena, value, expected, read_action)
1446 }
1447 BitArrayMatchedValue::LiteralFloat(expected) => {
1448 self.literal_float_segment_bytes_check(arena, value, expected, read_action)
1449 }
1450 BitArrayMatchedValue::LiteralInt {
1451 value: expected, ..
1452 } => self.literal_int_segment_bytes_check(
1453 arena,
1454 value,
1455 expected.clone(),
1456 read_action,
1457 ),
1458 BitArrayMatchedValue::Variable(..)
1459 | BitArrayMatchedValue::Discard(..)
1460 | BitArrayMatchedValue::Assign { .. } => {
1461 panic!("unreachable")
1462 }
1463 },
1464 },
1465
1466 // When checking on a tuple there's always going to be a single choice
1467 // and the code generation will always skip generating the check for it
1468 // as the type system ensures it must match.
1469 RuntimeCheck::Tuple { .. } => unreachable!("tried generating runtime check for tuple"),
1470
1471 // Some variants like `Bool` and `Result` are special cased and checked
1472 // in a different way from all other variants.
1473 RuntimeCheck::Variant { match_, .. } if variable.type_.is_bool() => {
1474 match match_.used_name().as_str() {
1475 "True" => value.to_doc(arena),
1476 _ => docvec![arena, EXCLAMATION_MARK_DOCUMENT, value],
1477 }
1478 }
1479
1480 RuntimeCheck::Variant {
1481 match_,
1482 index,
1483 fields,
1484 ..
1485 } => {
1486 if variable.type_.is_result() && match_.module().is_none() {
1487 if *index == 0 {
1488 self.expression_generator.tracker.ok_used = true;
1489 } else {
1490 self.expression_generator.tracker.error_used = true;
1491 }
1492 }
1493
1494 let qualification = match_
1495 .module()
1496 .map(|module| eco_format!("${module}."))
1497 .unwrap_or_default();
1498
1499 // If this variant has no fields, register it as being used
1500 // so that we know to import it.
1501 if fields.is_empty()
1502 && let Some((package, module, type_name)) =
1503 variable.type_.named_type_name_and_package()
1504 {
1505 _ = self
1506 .expression_generator
1507 .tracker
1508 .variants_used_in_instanceof
1509 .insert(TypeVariant {
1510 package,
1511 module,
1512 type_name,
1513 name: match_.variant_name(),
1514 });
1515 }
1516
1517 docvec![
1518 arena,
1519 value,
1520 SPACE_INSTANCE_OF_SPACE_DOCUMENT,
1521 qualification,
1522 match_.used_name()
1523 ]
1524 }
1525
1526 RuntimeCheck::NonEmptyList { .. } => {
1527 self.expression_generator.tracker.list_non_empty_class_used = true;
1528 docvec![arena, value, SPACE_INSTANCE_OF_NON_EMPTY_DOCUMENT]
1529 }
1530
1531 RuntimeCheck::EmptyList => {
1532 self.expression_generator.tracker.list_empty_class_used = true;
1533 docvec![arena, value, SPACE_INSTANCE_OF_EMPTY_DOCUMENT]
1534 }
1535 }
1536 }
1537
1538 /// Turns a read action into a document that can be used to extract the
1539 /// corresponding value from the given bit array and assign it to a
1540 /// variable.
1541 ///
1542 fn read_action_to_doc(
1543 &mut self,
1544 arena: &'doc DocumentArena<'a, 'doc>,
1545 bit_array: &Variable,
1546 read_action: &ReadAction,
1547 ) -> Document<'a, 'doc> {
1548 let ReadAction {
1549 from,
1550 size,
1551 type_,
1552 endianness,
1553 signed,
1554 } = read_action;
1555 let bit_array = self.get_value(bit_array);
1556 let from_bits = from.constant_bits();
1557
1558 // There's two special cases we need to take care of:
1559 match (size, &from_bits) {
1560 // If we're reading a single byte as un unsigned int from a byte aligned
1561 // offset then we can optimise this call as a `.byteAt` call!
1562 (ReadSize::ConstantBits(size), Some(from_bits))
1563 if type_.is_int()
1564 && *size == BigInt::from(8)
1565 && !signed
1566 && from_bits.clone() % 8 == BigInt::ZERO =>
1567 {
1568 let from_byte: BigInt = from_bits / 8;
1569 return docvec![
1570 arena,
1571 bit_array,
1572 DOT_BYTE_AT_OPEN_PAREN_DOCUMENT,
1573 from_byte,
1574 CLOSE_PAREN_DOCUMENT
1575 ];
1576 }
1577
1578 // If we're reading all the remaining bits/bytes of an array we'll
1579 // take the remaining slice.
1580 (ReadSize::RemainingBits | ReadSize::RemainingBytes, _) => {
1581 return self.bit_array_slice(arena, bit_array, from);
1582 }
1583
1584 _ => (),
1585 }
1586
1587 // Otherwise we'll take a regular slice out of the bit array, depending
1588 // on the type of the segment.
1589 let (start, end) =
1590 if let (ReadSize::ConstantBits(size), Some(from_bits)) = (size, from_bits) {
1591 // If both the start and and are known at compile time we can use
1592 // those directly in the slice call and perform no addition at
1593 // runtime.
1594 let start = from_bits.clone().to_doc(arena);
1595 let end = (from_bits + size).to_doc(arena);
1596 (start, end)
1597 } else {
1598 // Otherwise we'll have to sum the variable part and the constant
1599 // one to tell how long the slice should be.
1600 let size = self
1601 .read_size_to_doc(arena, size)
1602 .expect("no variable size");
1603 let start = self.offset_to_doc(arena, from, false);
1604 let end = if from.is_zero() {
1605 size
1606 } else {
1607 docvec![arena, start, SPACE_PLUS_SPACE_DOCUMENT, size]
1608 };
1609 (start, end)
1610 };
1611
1612 match type_ {
1613 ReadType::Int => {
1614 self.bit_array_slice_to_int(arena, bit_array, start, end, endianness, *signed)
1615 }
1616 ReadType::Float => {
1617 self.bit_array_slice_to_float(arena, bit_array, start, end, endianness)
1618 }
1619 ReadType::BitArray => self.bit_array_slice_with_end(arena, bit_array, from, end),
1620 ReadType::String | ReadType::UtfCodepoint => {
1621 panic!("invalid slice type made it to code generation: {type_:#?}")
1622 }
1623 }
1624 }
1625
1626 fn offset_to_doc(
1627 &mut self,
1628 arena: &'doc DocumentArena<'a, 'doc>,
1629 offset: &Offset,
1630 parenthesise: bool,
1631 ) -> Document<'a, 'doc> {
1632 if offset.is_zero() {
1633 return ZERO_DOCUMENT;
1634 }
1635
1636 let mut pieces = vec![];
1637 if offset.constant != BigInt::ZERO {
1638 pieces.push(eco_string_int(arena, offset.constant.to_string().into()));
1639 }
1640
1641 for (variable, times) in offset
1642 .variables
1643 .iter()
1644 .sorted_by(|(one, _), (other, _)| one.name().cmp(other.name()))
1645 {
1646 let mut variable = match variable {
1647 VariableUsage::PatternSegment(segment_name, _) => self
1648 .get_segment_value(arena, segment_name)
1649 .expect("segment referenced in a check before being created"),
1650 VariableUsage::OutsideVariable(name) => self.local_var(name).to_doc(arena),
1651 };
1652 if *times != 1 {
1653 variable = variable
1654 .append(arena, SPACE_TIMES_SPACE_DOCUMENT)
1655 .append(arena, *times);
1656 }
1657 pieces.push(variable.to_doc(arena));
1658 }
1659
1660 for calculation in offset.calculations.iter() {
1661 let left = self.offset_to_doc(arena, &calculation.left, true);
1662 let right = self.offset_to_doc(arena, &calculation.right, true);
1663
1664 let calculation = self.expression_generator.bin_op_with_doc_operands(
1665 arena,
1666 calculation.operator.to_bin_op(),
1667 left,
1668 right,
1669 &crate::type_::int(),
1670 );
1671
1672 if parenthesise {
1673 pieces.push(calculation.surround(arena, OPEN_PAREN_DOCUMENT, CLOSE_PAREN_DOCUMENT));
1674 } else {
1675 pieces.push(calculation);
1676 }
1677 }
1678
1679 if pieces.len() > 1 && parenthesise {
1680 docvec![
1681 arena,
1682 OPEN_PAREN_DOCUMENT,
1683 arena.join(pieces, SPACE_PLUS_SPACE_DOCUMENT),
1684 CLOSE_PAREN_DOCUMENT
1685 ]
1686 } else {
1687 arena.join(pieces, SPACE_PLUS_SPACE_DOCUMENT)
1688 }
1689 }
1690
1691 /// If the read size has a constant value (that is, it's not a "read all the
1692 /// remaining bits/bytes") this returns a document representing that size.
1693 /// Otherwise it returns an empty document.
1694 ///
1695 fn read_size_to_doc(
1696 &mut self,
1697 arena: &'doc DocumentArena<'a, 'doc>,
1698 size: &ReadSize,
1699 ) -> Option<Document<'a, 'doc>> {
1700 match size {
1701 ReadSize::ConstantBits(value) => Some(value.clone().to_doc(arena)),
1702 ReadSize::VariableBits { variable, unit } => {
1703 let variable = self.local_var(variable.name());
1704 Some(if *unit == 1 {
1705 variable.to_doc(arena)
1706 } else {
1707 docvec![arena, variable, SPACE_TIMES_SPACE_DOCUMENT, *unit as i64]
1708 })
1709 }
1710 ReadSize::RemainingBits | ReadSize::RemainingBytes => None,
1711
1712 ReadSize::BinaryOperator {
1713 left,
1714 right,
1715 operator,
1716 } => {
1717 let left = if self.read_size_must_be_wrapped(left) {
1718 self.read_size_to_doc(arena, left)?.surround(
1719 arena,
1720 OPEN_PAREN_DOCUMENT,
1721 CLOSE_PAREN_DOCUMENT,
1722 )
1723 } else {
1724 self.read_size_to_doc(arena, left)?
1725 };
1726 let right = if self.read_size_must_be_wrapped(right) {
1727 self.read_size_to_doc(arena, right)?.surround(
1728 arena,
1729 OPEN_PAREN_DOCUMENT,
1730 CLOSE_PAREN_DOCUMENT,
1731 )
1732 } else {
1733 self.read_size_to_doc(arena, right)?
1734 };
1735
1736 Some(self.expression_generator.bin_op_with_doc_operands(
1737 arena,
1738 operator.to_bin_op(),
1739 left,
1740 right,
1741 &crate::type_::int(),
1742 ))
1743 }
1744 }
1745 }
1746
1747 fn read_size_must_be_wrapped(&self, size: &ReadSize) -> bool {
1748 match size {
1749 ReadSize::ConstantBits(_) | ReadSize::RemainingBits | ReadSize::RemainingBytes => false,
1750
1751 ReadSize::VariableBits { unit, .. } => *unit != 1,
1752 ReadSize::BinaryOperator { .. } => true,
1753 }
1754 }
1755
1756 /// Generates the document that calls the `bitArraySliceToInt` function, with
1757 /// the given arguments.
1758 ///
1759 fn bit_array_slice_to_int(
1760 &mut self,
1761 arena: &'doc DocumentArena<'a, 'doc>,
1762 bit_array: impl Documentable<'a, 'doc>,
1763 start: impl Documentable<'a, 'doc>,
1764 end: impl Documentable<'a, 'doc>,
1765 endianness: &Endianness,
1766 signed: bool,
1767 ) -> Document<'a, 'doc> {
1768 self.expression_generator
1769 .tracker
1770 .bit_array_slice_to_int_used = true;
1771
1772 let endianness = match endianness {
1773 Endianness::Big => TRUE_LOWERCASE_DOCUMENT,
1774 Endianness::Little => FALSE_LOWERCASE_DOCUMENT,
1775 };
1776 let signed = if signed {
1777 TRUE_LOWERCASE_DOCUMENT
1778 } else {
1779 FALSE_LOWERCASE_DOCUMENT
1780 };
1781 let arguments = arena.join(
1782 [
1783 bit_array.to_doc(arena),
1784 start.to_doc(arena),
1785 end.to_doc(arena),
1786 endianness.to_doc(arena),
1787 signed.to_doc(arena),
1788 ],
1789 COMMA_SPACE_DOCUMENT,
1790 );
1791 docvec![
1792 arena,
1793 BIT_ARRAY_SLICE_TO_INT_OPEN_PAREN_DOCUMENT,
1794 arguments,
1795 CLOSE_PAREN_DOCUMENT
1796 ]
1797 }
1798
1799 /// Generates the document that calls the `bitArraySliceToFloat` function,
1800 /// with the given arguments.
1801 ///
1802 fn bit_array_slice_to_float(
1803 &mut self,
1804 arena: &'doc DocumentArena<'a, 'doc>,
1805 bit_array: impl Documentable<'a, 'doc>,
1806 start: impl Documentable<'a, 'doc>,
1807 end: impl Documentable<'a, 'doc>,
1808 endianness: &Endianness,
1809 ) -> Document<'a, 'doc> {
1810 self.expression_generator
1811 .tracker
1812 .bit_array_slice_to_float_used = true;
1813
1814 let endianness = match endianness {
1815 Endianness::Big => TRUE_LOWERCASE_DOCUMENT,
1816 Endianness::Little => FALSE_LOWERCASE_DOCUMENT,
1817 };
1818 let arguments = arena.join(
1819 [
1820 bit_array.to_doc(arena),
1821 start.to_doc(arena),
1822 end.to_doc(arena),
1823 endianness.to_doc(arena),
1824 ],
1825 COMMA_SPACE_DOCUMENT,
1826 );
1827 docvec![
1828 arena,
1829 BIT_ARRAY_SLICE_TO_FLOAT_OPEN_PAREN_DOCUMENT,
1830 arguments,
1831 CLOSE_PAREN_DOCUMENT
1832 ]
1833 }
1834
1835 /// Generates the document that calls the `bitArraySlice` function, with
1836 /// an end argument as well. If you need to take a slice that starts at a
1837 /// given offset and read the entire array you can use `bit_array_slice`.
1838 ///
1839 fn bit_array_slice_with_end(
1840 &mut self,
1841 arena: &'doc DocumentArena<'a, 'doc>,
1842 bit_array: impl Documentable<'a, 'doc>,
1843 from: &Offset,
1844 end: impl Documentable<'a, 'doc>,
1845 ) -> Document<'a, 'doc> {
1846 self.expression_generator.tracker.bit_array_slice_used = true;
1847 let from = self.offset_to_doc(arena, from, false);
1848 docvec![
1849 arena,
1850 BIT_ARRAY_SLICE_OPEN_PAREN_DOCUMENT,
1851 bit_array,
1852 COMMA_SPACE_DOCUMENT,
1853 from,
1854 COMMA_SPACE_DOCUMENT,
1855 end,
1856 CLOSE_PAREN_DOCUMENT
1857 ]
1858 }
1859
1860 /// Generates the document that calls the `bitArraySlice` function, starting
1861 /// at a given offset. This will read the entire remaining bit of the array,
1862 /// if you know that the slice should end at a given offset you can use
1863 /// `bit_array_slice_with_end` instead.
1864 ///
1865 fn bit_array_slice(
1866 &mut self,
1867 arena: &'doc DocumentArena<'a, 'doc>,
1868 bit_array: impl Documentable<'a, 'doc>,
1869 from: &Offset,
1870 ) -> Document<'a, 'doc> {
1871 self.expression_generator.tracker.bit_array_slice_used = true;
1872 let from = self.offset_to_doc(arena, from, false);
1873 docvec![
1874 arena,
1875 BIT_ARRAY_SLICE_OPEN_PAREN_DOCUMENT,
1876 bit_array,
1877 COMMA_SPACE_DOCUMENT,
1878 from,
1879 CLOSE_PAREN_DOCUMENT
1880 ]
1881 }
1882
1883 /// This generates all the checks that need to be performed to make sure a
1884 /// bit array segment (obtained with the read action passed as argument)
1885 /// matches with a literal string.
1886 ///
1887 fn literal_string_segment_bytes_check(
1888 &mut self,
1889 arena: &'doc DocumentArena<'a, 'doc>,
1890 // A string representing the bit array value we read bits from.
1891 bit_array: EcoString,
1892 // The bytes of the literal string we should be matching on.
1893 string_bytes: &Vec<u8>,
1894 read_action: &ReadAction,
1895 ) -> Document<'a, 'doc> {
1896 let ReadAction {
1897 from: start,
1898 endianness,
1899 signed,
1900 ..
1901 } = read_action;
1902 let mut checks = vec![];
1903
1904 let equality = SPACE_TRIPLE_EQUAL_SPACE_DOCUMENT;
1905
1906 let bytes = string_bytes.as_slice();
1907
1908 if let Some(mut from_byte) = start.constant_bytes() {
1909 // If the string starts at a compile-time known byte, then we can
1910 // optimise this by reading all the subsequent bytes and checking
1911 // they have a specific value.
1912 for byte in bytes {
1913 let byte_access = docvec![
1914 arena,
1915 bit_array.clone(),
1916 DOT_BYTE_AT_OPEN_PAREN_DOCUMENT,
1917 from_byte.clone(),
1918 CLOSE_PAREN_DOCUMENT
1919 ];
1920 checks.push(docvec![arena, byte_access, equality, *byte]);
1921 from_byte += 1;
1922 }
1923 } else {
1924 let mut start = start.clone();
1925
1926 // If the string doesn't start at a byte aligned offset then we'll
1927 // have to take slices out of it to check that each byte matches.
1928 for byte in bytes {
1929 let start_doc = self.offset_to_doc(arena, &start, false);
1930 let end = start.add_constant(8);
1931 let end_doc = self.offset_to_doc(arena, &end, false);
1932 let byte_access = self.bit_array_slice_to_int(
1933 arena, &bit_array, start_doc, end_doc, endianness, *signed,
1934 );
1935 checks.push(docvec![arena, byte_access, equality, *byte]);
1936 start = end;
1937 }
1938 }
1939
1940 // Otherwise the check succeeds if all the byte checks succeed.
1941 arena
1942 .join(checks, SPACE_DOUBLE_AMPERSAND_BREAK_DOCUMENT)
1943 .nest(arena, INDENT)
1944 .group(arena)
1945 }
1946
1947 /// This generates all the checks that need to be performed to make sure a
1948 /// bit array segment (obtained with the read action passed as argument)
1949 /// matches with a literal int.
1950 ///
1951 fn literal_int_segment_bytes_check(
1952 &mut self,
1953 arena: &'doc DocumentArena<'a, 'doc>,
1954 // A string representing the bit array value we read bits from.
1955 bit_array: EcoString,
1956 literal_int: BigInt,
1957 read_action: &ReadAction,
1958 ) -> Document<'a, 'doc> {
1959 let ReadAction {
1960 from: start,
1961 size,
1962 endianness,
1963 signed,
1964 ..
1965 } = read_action;
1966
1967 if let (Some(mut from_byte), Some(size)) = (start.constant_bytes(), size.constant_bytes()) {
1968 // If the number starts at a byte-aligned offset and is made of a
1969 // whole number of bytes then we can optimise this by checking that
1970 // all the bytes starting at the given offset match the int bytes.
1971 let mut checks = vec![];
1972 for byte in bit_array_segment_int_value_to_bytes(literal_int, size * 8, *endianness) {
1973 let byte_access = docvec![
1974 arena,
1975 bit_array.clone(),
1976 DOT_BYTE_AT_OPEN_PAREN_DOCUMENT,
1977 from_byte.clone(),
1978 CLOSE_PAREN_DOCUMENT
1979 ];
1980 checks.push(docvec![
1981 arena,
1982 byte_access,
1983 SPACE_TRIPLE_EQUAL_SPACE_DOCUMENT,
1984 byte
1985 ]);
1986 from_byte += 1;
1987 }
1988
1989 arena
1990 .join(checks, SPACE_DOUBLE_AMPERSAND_BREAK_DOCUMENT)
1991 .nest(arena, INDENT)
1992 .group(arena)
1993 } else {
1994 // Otherwise we have to take an int slice out of the bit array and
1995 // check it matches the expected value.
1996 let start_doc = self.offset_to_doc(arena, start, false);
1997 let end = match (start.constant_bits(), size.constant_bits()) {
1998 (Some(start), _) if start == BigInt::ZERO => self
1999 .read_size_to_doc(arena, size)
2000 .expect("unexpected catch all size"),
2001 (Some(start), Some(end)) => (start + end).to_doc(arena),
2002 (_, _) => docvec![
2003 arena,
2004 start_doc,
2005 SPACE_PLUS_SPACE_DOCUMENT,
2006 self.read_size_to_doc(arena, size).expect("empty size")
2007 ],
2008 };
2009 let check =
2010 self.bit_array_slice_to_int(arena, bit_array, start_doc, end, endianness, *signed);
2011 docvec![arena, check, SPACE_TRIPLE_EQUAL_SPACE_DOCUMENT, literal_int]
2012 }
2013 }
2014
2015 /// This generates all the checks that need to be performed to make sure a
2016 /// bit array segment (obtained with the read action passed as argument)
2017 /// matches with a literal float.
2018 ///
2019 fn literal_float_segment_bytes_check(
2020 &mut self,
2021 arena: &'doc DocumentArena<'a, 'doc>,
2022 // A string representing the bit array value we read bits from.
2023 bit_array: EcoString,
2024 expected: &EcoString,
2025 read_action: &ReadAction,
2026 ) -> Document<'a, 'doc> {
2027 let ReadAction {
2028 from: start,
2029 size,
2030 endianness,
2031 ..
2032 } = read_action;
2033
2034 let equality = SPACE_TRIPLE_EQUAL_SPACE_DOCUMENT;
2035
2036 // Unlike literal integers and strings, for now we don't try and apply any
2037 // optimisation in the way we match on those: we take an entire slice,
2038 // convert it to a float and check if it matches the expected value.
2039 let start_doc = self.offset_to_doc(arena, start, false);
2040 let end = match (start.constant_bits(), size.constant_bits()) {
2041 (Some(start), _) if start == BigInt::ZERO => self
2042 .read_size_to_doc(arena, size)
2043 .expect("unexpected catch all size"),
2044 (Some(start), Some(end)) => (start + end).to_doc(arena),
2045 (_, _) => docvec![
2046 arena,
2047 start_doc,
2048 SPACE_PLUS_SPACE_DOCUMENT,
2049 self.read_size_to_doc(arena, size).expect("empty size")
2050 ],
2051 };
2052 let check = self.bit_array_slice_to_float(arena, bit_array, start_doc, end, endianness);
2053 docvec![arena, check, equality, expected]
2054 }
2055
2056 #[must_use]
2057 fn is_bound_in_scope(&self, variable: &Variable) -> bool {
2058 self.scoped_variable_names.contains_key(&variable.id)
2059 }
2060
2061 #[must_use]
2062 fn segment_is_bound_in_scope(&self, segment_name: &EcoString) -> bool {
2063 self.scoped_segment_names.contains_key(segment_name)
2064 }
2065
2066 /// In case the check introduces new variables, this will record their
2067 /// actual value to be used by later checks and assignments.
2068 ///
2069 fn record_check_assignments(
2070 &mut self,
2071 arena: &'doc DocumentArena<'a, 'doc>,
2072 variable: &Variable,
2073 check: &RuntimeCheck,
2074 ) {
2075 let value = self.get_value(variable);
2076 match check {
2077 RuntimeCheck::Int { .. }
2078 | RuntimeCheck::Float { .. }
2079 | RuntimeCheck::String { .. }
2080 | RuntimeCheck::EmptyList => (),
2081
2082 RuntimeCheck::BitArray { test } => {
2083 for (segment_name, read_action) in test.referenced_segment_patterns() {
2084 self.set_segment_value(arena, variable, segment_name.clone(), read_action);
2085 }
2086 }
2087
2088 RuntimeCheck::StringPrefix { rest, prefix } => {
2089 let prefix_size = utf16_no_escape_len(prefix);
2090 self.set_value(rest, eco_format!("{value}.slice({prefix_size})"));
2091 }
2092
2093 RuntimeCheck::Tuple { elements, .. } => {
2094 for (i, element) in elements.iter().enumerate() {
2095 self.set_value(element, eco_format!("{value}[{i}]"));
2096 }
2097 }
2098
2099 RuntimeCheck::Variant { fields, labels, .. } => {
2100 for (i, field) in fields.iter().enumerate() {
2101 let access = match labels.get(&i) {
2102 Some(label) => eco_format!("{value}.{}", maybe_escape_property(label)),
2103 None => eco_format!("{value}[{i}]"),
2104 };
2105 self.set_value(field, access);
2106 }
2107 }
2108
2109 RuntimeCheck::NonEmptyList { first, rest } => {
2110 self.set_value(first, eco_format!("{value}.head"));
2111 self.set_value(rest, eco_format!("{value}.tail"));
2112 }
2113 }
2114 }
2115
2116 /// A runtime check might need to reference some bit array segments in its
2117 /// check (for example if a bit array length depends on a previous segment).
2118 /// This function returns a vector with all the assignments needed to bring
2119 /// the referenced segments into scope, so they're available to use for the
2120 /// runtime check.
2121 ///
2122 fn bit_array_segment_assignments(
2123 &mut self,
2124 arena: &'doc DocumentArena<'a, 'doc>,
2125 check: &RuntimeCheck,
2126 ) -> Vec<Document<'a, 'doc>> {
2127 let mut check_assignments = vec![];
2128 for (segment, _) in check.referenced_segment_patterns() {
2129 // If the segment was already bound to a variable in this scope we
2130 // don't need to generate any further assignment for it. We will just
2131 // reuse that existing variable when we need to access this segment
2132 if self.segment_is_bound_in_scope(segment) {
2133 continue;
2134 }
2135
2136 let variable_name = self.next_local_var(segment);
2137 let segment_value = self
2138 .get_segment_value(arena, segment)
2139 .expect("segment referenced in a check before being created");
2140 self.bind_segment(variable_name.clone(), segment.clone());
2141 check_assignments.push(let_doc(arena, variable_name, segment_value));
2142 }
2143 check_assignments
2144 }
2145
2146 /// Returns a string representing the value of a pattern variable: it might
2147 /// be the code needed to obtain such variable (for example accessing a
2148 /// list item `wibble.head`), or it could be a name this variable was bound
2149 /// to in the current scope to avoid doing any repeated work!
2150 ///
2151 fn get_value(&self, variable: &Variable) -> EcoString {
2152 // If the pattern variable was already assigned to a variable that is
2153 // in scope we use that variable name!
2154 if let Some(name) = self.scoped_variable_names.get(&variable.id) {
2155 return name.clone();
2156 }
2157
2158 // Otherwise we fallback to using its value directly.
2159 self.variable_values
2160 .get(&variable.id)
2161 .expect("pattern variable used before assignment")
2162 .clone()
2163 }
2164
2165 fn get_segment_value(
2166 &self,
2167 arena: &'doc DocumentArena<'a, 'doc>,
2168 segment_name: &EcoString,
2169 ) -> Option<Document<'a, 'doc>> {
2170 // If the segment was already assigned to a variable that is in scope
2171 // we use that variable name!
2172 if let Some(name) = self.scoped_segment_names.get(segment_name) {
2173 return Some(name.clone().to_doc(arena));
2174 }
2175
2176 // Otherwise we fallback to using its value directly.
2177 self.segment_values.get(segment_name).cloned()
2178 }
2179}
2180
2181/// When going over the subjects of a case expression/let we might end up in two
2182/// situation: the subject might be a variable or it could be a more complex
2183/// expression (like a function call, a complex expression, ...).
2184///
2185/// ```gleam
2186/// case a_variable { ... }
2187/// case a_function_call(wobble) { ... }
2188/// ```
2189///
2190/// When checking on a case we might end up repeating the subjects multiple times
2191/// (as they need to appear in various checks), this means that if we ended up
2192/// doing the simple thing of just repeating the subject as it is, we might end
2193/// up dramatically changing the meaning of the program when the subject is a
2194/// complex expression! Imagine this example:
2195///
2196/// ```gleam
2197/// case wibble("a") {
2198/// 1 -> todo
2199/// 2 -> todo
2200/// _ -> todo
2201/// }
2202/// ```
2203///
2204/// If we just repeated the subject every time we need to check it, the decision
2205/// tree would end up looking something like this:
2206///
2207/// ```js
2208/// if (wibble("a") === 1) {}
2209/// else if (wibble("a") === 2) {}
2210/// else {}
2211/// ```
2212///
2213/// It would be quite bad as we would end up running the same function multiple
2214/// times instead of just once!
2215///
2216/// So we need to split each subject in two categories: if it is a simple
2217/// variable already, it's no big deal and we can repeat that name as many times
2218/// as we want; however, if it's anything else we first need to bind that subject
2219/// to a variable we can then reference multiple times.
2220///
2221enum SubjectAssignment<'a, 'doc> {
2222 /// The subject is a complex expression with a `value` that has to be
2223 /// assigned to a variable with the given `name` as repeating the `value`
2224 /// multiple times could possibly change the meaning of the program.
2225 BindToVariable {
2226 name: EcoString,
2227 value: Document<'a, 'doc>,
2228 location: SrcSpan,
2229 },
2230 /// The subject is already a simple variable with the given name, we will
2231 /// keep using that name to reference it.
2232 AlreadyAVariable { name: EcoString },
2233}
2234
2235impl SubjectAssignment<'_, '_> {
2236 fn name(&self) -> EcoString {
2237 match self {
2238 SubjectAssignment::BindToVariable {
2239 name,
2240 value: _,
2241 location: _,
2242 }
2243 | SubjectAssignment::AlreadyAVariable { name } => name.clone(),
2244 }
2245 }
2246}
2247
2248fn assign_subject<'a, 'doc>(
2249 arena: &'doc DocumentArena<'a, 'doc>,
2250 expression_generator: &mut Generator<'_, 'a, 'doc>,
2251 subject: &'a TypedExpr,
2252 ordering: Ordering,
2253) -> SubjectAssignment<'a, 'doc> {
2254 static ASSIGNMENT_VAR_ECO_STR: OnceLock<EcoString> = OnceLock::new();
2255
2256 // If the value is a variable we don't need to assign it to a new
2257 // variable, we can use the value expression safely without worrying about
2258 // performing computation or side effects multiple times.
2259 if let TypedExpr::Var {
2260 name, constructor, ..
2261 } = subject
2262 && constructor.is_local_variable()
2263 {
2264 SubjectAssignment::AlreadyAVariable {
2265 name: expression_generator.local_var(name),
2266 }
2267 } else {
2268 // If it's not a variable we need to assign it to a variable
2269 // to avoid rendering the subject expression multiple times
2270 let name = expression_generator
2271 .next_local_var(ASSIGNMENT_VAR_ECO_STR.get_or_init(|| ASSIGNMENT_VAR.into()));
2272 let value = expression_generator
2273 .not_in_tail_position(Some(ordering), |this| this.wrap_expression(arena, subject));
2274
2275 SubjectAssignment::BindToVariable {
2276 value,
2277 name,
2278 location: subject.location(),
2279 }
2280 }
2281}
2282
2283fn assignments_to_doc<'a, 'doc>(
2284 arena: &'doc DocumentArena<'a, 'doc>,
2285 expression_generator: &mut Generator<'_, 'a, 'doc>,
2286 assignments: Vec<SubjectAssignment<'a, 'doc>>,
2287) -> Document<'a, 'doc> {
2288 arena.concat(assignments.into_iter().filter_map(|assignment| {
2289 let SubjectAssignment::BindToVariable {
2290 name,
2291 value,
2292 location,
2293 } = assignment
2294 else {
2295 return None;
2296 };
2297
2298 Some(docvec![
2299 arena,
2300 expression_generator.source_map_tracker(arena, location.start),
2301 let_doc(arena, name, value),
2302 LINE_DOCUMENT
2303 ])
2304 }))
2305}
2306
2307/// Appends the second document to the first one separating the two with a newline.
2308/// However, if the second document is empty the empty line is not added.
2309///
2310fn join_with_line<'a, 'doc>(
2311 arena: &'doc DocumentArena<'a, 'doc>,
2312 one: Document<'a, 'doc>,
2313 other: Document<'a, 'doc>,
2314) -> Document<'a, 'doc> {
2315 if one.is_empty() {
2316 other
2317 } else if other.is_empty() {
2318 one
2319 } else {
2320 docvec![arena, one, LINE_DOCUMENT, other]
2321 }
2322}
2323
2324fn reassignment_doc<'a, 'doc>(
2325 arena: &'doc DocumentArena<'a, 'doc>,
2326 variable_name: EcoString,
2327 value: Document<'a, 'doc>,
2328) -> Document<'a, 'doc> {
2329 docvec![
2330 arena,
2331 variable_name,
2332 SPACE_EQUAL_SPACE_DOCUMENT,
2333 value,
2334 SEMICOLON_DOCUMENT
2335 ]
2336}
2337
2338fn let_doc<'a, 'doc>(
2339 arena: &'doc DocumentArena<'a, 'doc>,
2340 variable_name: EcoString,
2341 value: Document<'a, 'doc>,
2342) -> Document<'a, 'doc> {
2343 docvec![
2344 arena,
2345 LET_SPACE_DOCUMENT,
2346 variable_name,
2347 SPACE_EQUAL_SPACE_DOCUMENT,
2348 value,
2349 SEMICOLON_DOCUMENT
2350 ]
2351}
2352
2353/// Calculates the length of str as utf16 without escape characters.
2354///
2355fn utf16_no_escape_len(str: &EcoString) -> usize {
2356 length_utf16(&convert_string_escape_chars(str))
2357}
2358
2359pub fn break_block<'a, 'doc>(
2360 arena: &'doc DocumentArena<'a, 'doc>,
2361 doc: Document<'a, 'doc>,
2362) -> Document<'a, 'doc> {
2363 docvec![
2364 arena,
2365 OPEN_CURLY_DOCUMENT,
2366 docvec![arena, LINE_DOCUMENT, doc].nest(arena, INDENT),
2367 LINE_DOCUMENT,
2368 CLOSE_CURLY_DOCUMENT
2369 ]
2370 .force_break(arena)
2371}