Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

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