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