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