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