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