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