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