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