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 / pattern.rs
38 kB 1045 lines
1use num_bigint::BigInt; 2use std::sync::OnceLock; 3 4use super::*; 5use crate::{ 6 analyse::Inferred, 7 strings::convert_string_escape_chars, 8 type_::{FieldMap, PatternConstructor}, 9}; 10 11pub static ASSIGNMENT_VAR: &str = "$"; 12 13#[derive(Debug)] 14enum Index<'a> { 15 Int(usize), 16 String(&'a str), 17 ByteAt(OffsetBits), 18 BitArraySliceToInt { 19 start: OffsetBits, 20 end: OffsetBits, 21 endianness: Endianness, 22 is_signed: bool, 23 }, 24 BitArraySliceToFloat { 25 start: OffsetBits, 26 end: OffsetBits, 27 endianness: Endianness, 28 }, 29 BitArraySlice(OffsetBits, Option<OffsetBits>), 30 StringPrefixSlice(usize), 31} 32 33#[derive(Debug)] 34pub(crate) struct Generator<'module_ctx, 'expression_gen, 'a> { 35 pub expression_generator: &'expression_gen mut expression::Generator<'module_ctx, 'a>, 36 path: Vec<Index<'a>>, 37 checks: Vec<Check<'a>>, 38 assignments: Vec<Assignment<'a>>, 39} 40 41#[derive(Debug)] 42pub enum BitArrayTailSpreadType { 43 /// The tail of the bit array pattern is for all remaining bits 44 Bits, 45 46 /// The tail of the bit array pattern is for all remaining whole bytes. This 47 /// requires an additional runtime check that the number of remaining bits 48 /// is a multiple of 8, as otherwise the pattern doesn't match. 49 Bytes, 50} 51 52#[derive(Debug, Clone)] 53/// Represents the offset into a bit array which is needed to extract the value 54/// of a specific pattern. This can either be a constant value, if we know it at 55/// compile-time, or a sum of some variables. For example: 56/// ```gleam 57/// case todo { 58/// <<_:size(8), extract_this>> -> todo 59/// // ^ This has a known offset of 8 60/// <<x:size(8), y:size(x), z:size(y)>> 61/// // ^ This starts at offset x + 8, and ends at offset x + y + 8 62/// } 63/// ``` 64pub struct OffsetBits { 65 /// The size of the known offset. The total offset will be at least this size. 66 /// If this field is 0, the offset is entirely composed of variable sizes. 67 constant: usize, 68 /// Any variables which must be added to the known offset at runtime. 69 /// Empty if we know the size at compile-time. 70 variables: Vec<EcoString>, 71 /// Sometimes we need to divide an offset by a certain number, such as 8, because 72 /// sizes can be specified using bits or bytes in different circumstances. 73 /// If the size is constant, we can just divide that. However, if the size needs 74 /// to be computed at runtime, we must add that division as part of the calculation. 75 /// If this field is 1, we ignore it. 76 divide_by: usize, 77} 78 79impl OffsetBits { 80 fn increment(&mut self, size: BitArraySize) { 81 match size { 82 BitArraySize::Literal(size) => self.constant += size, 83 BitArraySize::Variable(name) => self.variables.push(name), 84 } 85 } 86 87 fn is_whole_number_of_bytes(&self) -> bool { 88 self.variables.is_empty() && self.constant % 8 == 0 89 } 90 91 fn divide(mut self, by: usize) -> Self { 92 if self.variables.is_empty() { 93 self.constant = self.constant / by; 94 } else { 95 self.divide_by = self.divide_by * by; 96 } 97 98 self 99 } 100 101 fn to_doc(&self) -> Document<'static> { 102 let doc = if self.variables.is_empty() { 103 self.constant.to_doc() 104 } else if self.constant == 0 { 105 join( 106 self.variables 107 .iter() 108 .map(|variable| variable.clone().to_doc()), 109 " + ".to_doc(), 110 ) 111 .group() 112 } else { 113 docvec![ 114 join( 115 self.variables 116 .iter() 117 .map(|variable| variable.clone().to_doc()), 118 " + ".to_doc() 119 ), 120 " + ", 121 self.constant, 122 ] 123 .group() 124 }; 125 126 match self.divide_by { 127 1 => doc, 128 _ => doc.append(" / ".to_doc().append(self.divide_by)).group(), 129 } 130 } 131} 132 133struct Offset { 134 bits: OffsetBits, 135 tail_spread_type: Option<BitArrayTailSpreadType>, 136} 137 138impl Offset { 139 pub fn new() -> Self { 140 Self { 141 bits: OffsetBits { 142 constant: 0, 143 variables: Vec::new(), 144 divide_by: 1, 145 }, 146 tail_spread_type: None, 147 } 148 } 149 pub fn set_open_ended(&mut self, tail_spread_type: BitArrayTailSpreadType) { 150 self.tail_spread_type = Some(tail_spread_type); 151 } 152} 153 154#[derive(Debug, Clone)] 155enum BitArraySize { 156 Literal(usize), 157 Variable(EcoString), 158} 159 160impl BitArraySize { 161 fn is_constant_value(&self, value: usize) -> bool { 162 match self { 163 BitArraySize::Literal(size) => *size == value, 164 BitArraySize::Variable(_) => false, 165 } 166 } 167} 168 169#[derive(Debug)] 170struct SizedBitArraySegmentDetails { 171 size: BitArraySize, 172 endianness: Endianness, 173 is_signed: bool, 174} 175 176impl<'module_ctx, 'expression_gen, 'a> Generator<'module_ctx, 'expression_gen, 'a> { 177 pub fn new( 178 expression_generator: &'expression_gen mut expression::Generator<'module_ctx, 'a>, 179 ) -> Self { 180 Self { 181 path: vec![], 182 checks: vec![], 183 assignments: vec![], 184 expression_generator, 185 } 186 } 187 188 fn next_local_var(&mut self, name: &EcoString) -> EcoString { 189 self.expression_generator.next_local_var(name) 190 } 191 192 fn push_string(&mut self, s: &'a str) { 193 self.path.push(Index::String(s)); 194 } 195 196 fn push_int(&mut self, i: usize) { 197 self.path.push(Index::Int(i)); 198 } 199 200 fn push_string_prefix_slice(&mut self, i: usize) { 201 self.path.push(Index::StringPrefixSlice(i)); 202 } 203 204 fn push_byte_at(&mut self, i: OffsetBits) { 205 self.path.push(Index::ByteAt(i)); 206 } 207 208 fn push_bit_array_slice_to_int( 209 &mut self, 210 start: OffsetBits, 211 end: OffsetBits, 212 endianness: Endianness, 213 is_signed: bool, 214 ) { 215 self.expression_generator 216 .tracker 217 .bit_array_slice_to_int_used = true; 218 219 self.path.push(Index::BitArraySliceToInt { 220 start, 221 end, 222 endianness, 223 is_signed, 224 }); 225 } 226 227 fn push_bit_array_slice_to_float( 228 &mut self, 229 start: OffsetBits, 230 end: OffsetBits, 231 endianness: Endianness, 232 ) { 233 self.expression_generator 234 .tracker 235 .bit_array_slice_to_float_used = true; 236 237 self.path.push(Index::BitArraySliceToFloat { 238 start, 239 end, 240 endianness, 241 }); 242 } 243 244 fn push_bit_array_slice(&mut self, start: OffsetBits, end: Option<OffsetBits>) { 245 self.expression_generator.tracker.bit_array_slice_used = true; 246 self.path.push(Index::BitArraySlice(start, end)); 247 } 248 249 fn push_string_times(&mut self, s: &'a str, times: usize) { 250 for _ in 0..times { 251 self.push_string(s); 252 } 253 } 254 255 fn pop(&mut self) { 256 let _ = self.path.pop(); 257 } 258 259 fn pop_times(&mut self, times: usize) { 260 for _ in 0..times { 261 self.pop(); 262 } 263 } 264 265 fn apply_path_to_subject(&self, subject: Document<'a>) -> Document<'a> { 266 self.path 267 .iter() 268 .fold(subject, |acc, segment| match segment { 269 Index::Int(i) => acc.append(eco_format!("[{i}]").to_doc()), 270 // TODO: escape string if needed 271 Index::String(s) => acc.append(docvec![".", maybe_escape_property_doc(s)]), 272 Index::ByteAt(i) => acc.append(docvec![".byteAt(", i, ")"]), 273 Index::BitArraySliceToInt { 274 start, 275 end, 276 endianness, 277 is_signed, 278 } => docvec![ 279 "bitArraySliceToInt(", 280 acc, 281 ", ", 282 start, 283 ", ", 284 end, 285 ", ", 286 bool(endianness.is_big()), 287 ", ", 288 bool(*is_signed), 289 ")" 290 ], 291 Index::BitArraySliceToFloat { 292 start, 293 end, 294 endianness, 295 } => docvec![ 296 "bitArraySliceToFloat(", 297 acc, 298 ", ", 299 start, 300 ", ", 301 end, 302 ", ", 303 bool(endianness.is_big()), 304 ")" 305 ], 306 Index::BitArraySlice(start, end) => match end { 307 Some(end) => { 308 docvec!["bitArraySlice(", acc, ", ", start, ", ", end, ")"] 309 } 310 None => docvec!["bitArraySlice(", acc, ", ", start, ")"], 311 }, 312 Index::StringPrefixSlice(i) => docvec!(acc, ".slice(", i, ")"), 313 }) 314 } 315 316 pub fn take_compiled(&mut self) -> CompiledPattern<'a> { 317 CompiledPattern { 318 checks: std::mem::take(&mut self.checks), 319 assignments: std::mem::take(&mut self.assignments), 320 } 321 } 322 323 pub fn traverse_pattern( 324 &mut self, 325 subject: &Document<'a>, 326 pattern: &'a TypedPattern, 327 ) -> Result<(), Error> { 328 match pattern { 329 Pattern::String { value, .. } => { 330 self.push_equality_check(subject.clone(), expression::string(value)); 331 Ok(()) 332 } 333 Pattern::Int { value, .. } => { 334 self.push_equality_check(subject.clone(), expression::int(value)); 335 Ok(()) 336 } 337 Pattern::Float { value, .. } => { 338 self.push_equality_check(subject.clone(), expression::float(value)); 339 Ok(()) 340 } 341 342 Pattern::Discard { .. } => Ok(()), 343 344 Pattern::Variable { name, .. } => { 345 self.push_assignment(subject.clone(), name); 346 Ok(()) 347 } 348 349 Pattern::Assign { name, pattern, .. } => { 350 self.push_assignment(subject.clone(), name); 351 self.traverse_pattern(subject, pattern) 352 } 353 354 Pattern::List { elements, tail, .. } => { 355 self.push_list_length_check(subject.clone(), elements.len(), tail.is_some()); 356 for pattern in elements { 357 self.push_string("head"); 358 self.traverse_pattern(subject, pattern)?; 359 self.pop(); 360 self.push_string("tail"); 361 } 362 self.pop_times(elements.len()); 363 if let Some(pattern) = tail { 364 self.push_string_times("tail", elements.len()); 365 self.traverse_pattern(subject, pattern)?; 366 self.pop_times(elements.len()); 367 } 368 Ok(()) 369 } 370 371 Pattern::Tuple { elements, .. } => { 372 // We don't check the length because type system ensures it's a 373 // tuple of the correct size 374 for (index, pattern) in elements.iter().enumerate() { 375 self.push_int(index); 376 self.traverse_pattern(subject, pattern)?; 377 self.pop(); 378 } 379 Ok(()) 380 } 381 382 Pattern::Constructor { 383 type_, 384 constructor: Inferred::Known(PatternConstructor { name, .. }), 385 .. 386 } if type_.is_bool() && name == "True" => { 387 self.push_booly_check(subject.clone(), true); 388 Ok(()) 389 } 390 391 Pattern::Constructor { 392 type_, 393 constructor: Inferred::Known(PatternConstructor { name, .. }), 394 .. 395 } if type_.is_bool() && name == "False" => { 396 self.push_booly_check(subject.clone(), false); 397 Ok(()) 398 } 399 400 Pattern::Constructor { 401 type_, 402 constructor: Inferred::Known(PatternConstructor { .. }), 403 .. 404 } if type_.is_nil() => { 405 self.push_booly_check(subject.clone(), false); 406 Ok(()) 407 } 408 409 Pattern::Constructor { 410 constructor: Inferred::Unknown, 411 .. 412 } => { 413 panic!("JavaScript generation performed with uninferred pattern constructor"); 414 } 415 416 Pattern::StringPrefix { 417 left_side_string, 418 right_side_assignment, 419 left_side_assignment, 420 .. 421 } => { 422 self.push_string_prefix_check(subject.clone(), left_side_string); 423 if let AssignName::Variable(right) = right_side_assignment { 424 self.push_string_prefix_slice(utf16_no_escape_len(left_side_string)); 425 self.push_assignment(subject.clone(), right); 426 // After pushing the assignment we need to pop the prefix slicing we used to 427 // check the condition. 428 self.pop(); 429 } 430 if let Some((left, _)) = left_side_assignment { 431 // "wibble" as prefix <> rest 432 // ^^^^^^^^^ In case the left prefix of the pattern matching is given an 433 // alias we bind it to a local variable so that it can be 434 // correctly referenced inside the case branch. 435 // let prefix = "wibble"; 436 // ^^^^^^^^^^^^^^^^^^^^^ we're adding this assignment inside the if clause 437 // the case branch gets translated into. 438 // 439 // We also want to push this assignment without using push_assignment, since we 440 // do _not_ want to access the current path on the static string! 441 let var = self.next_local_var(left).to_doc(); 442 self.assignments.push(Assignment { 443 subject: expression::string(left_side_string), 444 var, 445 }); 446 } 447 Ok(()) 448 } 449 450 Pattern::Constructor { 451 constructor: 452 Inferred::Known(PatternConstructor { 453 field_map, 454 name: record_name, 455 .. 456 }), 457 arguments, 458 name, 459 type_, 460 module, 461 .. 462 } => { 463 match module { 464 _ if type_.is_result() => { 465 self.push_result_check(subject.clone(), record_name == "Ok") 466 } 467 Some((m, _)) => { 468 self.push_variant_check(subject.clone(), docvec!["$", m, ".", name]) 469 } 470 None => self.push_variant_check(subject.clone(), name.to_doc()), 471 } 472 473 for (index, arg) in arguments.iter().enumerate() { 474 match field_map { 475 None => self.push_int(index), 476 Some(FieldMap { fields, .. }) => { 477 let find = |(key, &val)| { 478 if val as usize == index { 479 Some(key) 480 } else { 481 None 482 } 483 }; 484 let label = fields.iter().find_map(find); 485 match label { 486 Some(label) => self.push_string(label), 487 None => self.push_int(index), 488 } 489 } 490 } 491 self.traverse_pattern(subject, &arg.value)?; 492 self.pop(); 493 } 494 Ok(()) 495 } 496 497 Pattern::BitArray { segments, .. } => { 498 use BitArrayOption as Opt; 499 500 let mut offset = Offset::new(); 501 for segment in segments { 502 if segment.type_ == crate::type_::int() 503 || segment.type_ == crate::type_::float() 504 { 505 let details = self.sized_bit_array_segment_details(segment)?; 506 507 match (segment.value.as_ref(), details.size) { 508 (Pattern::Int { int_value, .. }, BitArraySize::Literal(size)) 509 if size <= SAFE_INT_SEGMENT_MAX_SIZE 510 && size % 8 == 0 511 && offset.bits.is_whole_number_of_bytes() => 512 { 513 let bytes = bit_array_segment_int_value_to_bytes( 514 (*int_value).clone(), 515 BigInt::from(size), 516 details.endianness, 517 )?; 518 519 for byte in bytes { 520 self.push_byte_at(offset.bits.clone().divide(8)); 521 self.push_equality_check(subject.clone(), docvec![byte]); 522 self.pop(); 523 offset.bits.increment(BitArraySize::Literal(8)); 524 } 525 } 526 527 (Pattern::Discard { .. }, size) => { 528 offset.bits.increment(size); 529 } 530 531 (_, size) => { 532 let start = offset.bits.clone(); 533 let mut end = offset.bits.clone(); 534 end.increment(size.clone()); 535 536 if segment.type_ == crate::type_::int() { 537 if size.is_constant_value(8) 538 && !details.is_signed 539 && offset.bits.is_whole_number_of_bytes() 540 { 541 self.push_byte_at(offset.bits.clone().divide(8)); 542 } else { 543 self.push_bit_array_slice_to_int( 544 start, 545 end, 546 details.endianness, 547 details.is_signed, 548 ); 549 } 550 } else { 551 self.push_bit_array_slice_to_float( 552 start, 553 end, 554 details.endianness, 555 ); 556 } 557 558 self.traverse_pattern(subject, &segment.value)?; 559 self.pop(); 560 offset.bits.increment(size); 561 } 562 } 563 } else { 564 match segment.options.as_slice() { 565 [Opt::Bits { .. }] => { 566 self.push_bit_array_slice(offset.bits.clone(), None); 567 self.traverse_pattern(subject, &segment.value)?; 568 self.pop(); 569 offset.set_open_ended(BitArrayTailSpreadType::Bits); 570 Ok(()) 571 } 572 573 [Opt::Bits { .. }, Opt::Size { value: size, .. }] 574 | [Opt::Size { value: size, .. }, Opt::Bits { .. }] => match &**size { 575 Pattern::Int { value, .. } => { 576 let start = offset.bits.clone(); 577 let increment = value.parse::<usize>().expect( 578 "part of an Int node should always parse as integer", 579 ); 580 offset.bits.increment(BitArraySize::Literal(increment)); 581 let end = offset.bits.clone(); 582 583 self.push_bit_array_slice(start, Some(end)); 584 self.traverse_pattern(subject, &segment.value)?; 585 self.pop(); 586 Ok(()) 587 } 588 589 Pattern::VarUsage { name, .. } => { 590 let name = self.expression_generator.local_var(name); 591 let start = offset.bits.clone(); 592 593 offset.bits.increment(BitArraySize::Variable(name)); 594 let end = offset.bits.clone(); 595 596 self.push_bit_array_slice(start, Some(end)); 597 self.traverse_pattern(subject, &segment.value)?; 598 self.pop(); 599 Ok(()) 600 } 601 602 _ => Err(Error::Unsupported { 603 feature: "This bit array size option in patterns".into(), 604 location: segment.location, 605 }), 606 }, 607 608 [Opt::Bytes { .. }] => { 609 self.push_bit_array_slice(offset.bits.clone(), None); 610 self.traverse_pattern(subject, &segment.value)?; 611 self.pop(); 612 offset.set_open_ended(BitArrayTailSpreadType::Bytes); 613 Ok(()) 614 } 615 616 [Opt::Bytes { .. }, Opt::Size { value: size, .. }] 617 | [Opt::Size { value: size, .. }, Opt::Bytes { .. }] => match &**size { 618 Pattern::Int { value, .. } => { 619 let start = offset.bits.clone(); 620 let increment = value.parse::<usize>().expect( 621 "part of an Int node should always parse as integer", 622 ) * 8; 623 offset.bits.increment(BitArraySize::Literal(increment)); 624 let end = offset.bits.clone(); 625 626 self.push_bit_array_slice(start, Some(end)); 627 self.traverse_pattern(subject, &segment.value)?; 628 self.pop(); 629 Ok(()) 630 } 631 632 Pattern::VarUsage { name, .. } => { 633 let start = offset.bits.clone(); 634 let mut name = self.expression_generator.local_var(name); 635 name.push_str(" * 8"); 636 offset.bits.increment(BitArraySize::Variable(name)); 637 let end = offset.bits.clone(); 638 639 self.push_bit_array_slice(start, Some(end)); 640 self.traverse_pattern(subject, &segment.value)?; 641 self.pop(); 642 Ok(()) 643 } 644 645 _ => Err(Error::Unsupported { 646 feature: "This bit array size option in patterns".into(), 647 location: segment.location, 648 }), 649 }, 650 651 [Opt::Utf8 { .. }] => match segment.value.as_ref() { 652 Pattern::String { value, .. } => { 653 for byte in convert_string_escape_chars(value).as_bytes() { 654 if offset.bits.is_whole_number_of_bytes() { 655 self.push_byte_at(offset.bits.clone().divide(8)); 656 } else { 657 let mut end = offset.bits.clone(); 658 end.increment(BitArraySize::Literal(8)); 659 660 self.push_bit_array_slice_to_int( 661 offset.bits.clone(), 662 end, 663 Endianness::Big, 664 false, 665 ); 666 } 667 self.push_equality_check(subject.clone(), byte.to_doc()); 668 self.pop(); 669 offset.bits.increment(BitArraySize::Literal(8)); 670 } 671 672 Ok(()) 673 } 674 675 _ => Err(Error::Unsupported { 676 feature: "This bit array segment option in patterns".into(), 677 location: segment.location, 678 }), 679 }, 680 681 _ => Err(Error::Unsupported { 682 feature: "This bit array segment option in patterns".into(), 683 location: segment.location, 684 }), 685 }?; 686 } 687 } 688 689 self.push_bit_array_bit_size_check( 690 subject.clone(), 691 offset.bits.clone(), 692 offset.tail_spread_type, 693 ); 694 Ok(()) 695 } 696 Pattern::VarUsage { location, .. } => Err(Error::Unsupported { 697 feature: "Bit array matching".into(), 698 location: *location, 699 }), 700 Pattern::Invalid { .. } => panic!("invalid patterns should not reach code generation"), 701 } 702 } 703 704 fn sized_bit_array_segment_details( 705 &mut self, 706 segment: &TypedPatternBitArraySegment, 707 ) -> Result<SizedBitArraySegmentDetails, Error> { 708 use BitArrayOption as Opt; 709 710 if segment 711 .options 712 .iter() 713 .any(|x| matches!(x, Opt::Native { .. })) 714 { 715 return Err(Error::Unsupported { 716 feature: "This bit array segment option".into(), 717 location: segment.location, 718 }); 719 } 720 721 let endianness = if segment 722 .options 723 .iter() 724 .any(|x| matches!(x, Opt::Little { .. })) 725 { 726 Endianness::Little 727 } else { 728 Endianness::Big 729 }; 730 731 let unit = segment 732 .options 733 .iter() 734 .find_map(|option| match option { 735 Opt::Unit { value, .. } => Some(*value), 736 _ => None, 737 }) 738 .unwrap_or(1); 739 740 let size = match segment 741 .options 742 .iter() 743 .find(|x| matches!(x, Opt::Size { .. })) 744 { 745 Some(Opt::Size { value: size, .. }) => match &**size { 746 Pattern::Int { value, .. } => Ok(BitArraySize::Literal( 747 value 748 .parse::<usize>() 749 .expect("part of an Int node should always parse as integer") 750 * unit as usize, 751 )), 752 Pattern::VarUsage { name, .. } => { 753 let mut variable = self.expression_generator.local_var(name); 754 if unit != 1 { 755 variable.push_str(&eco_format!(" * {unit}")); 756 } 757 Ok(BitArraySize::Variable(variable)) 758 } 759 _ => Err(Error::Unsupported { 760 feature: "Non-constant size option in patterns".into(), 761 location: segment.location, 762 }), 763 }, 764 765 _ => { 766 let default_size = if segment.type_ == crate::type_::int() { 767 8usize 768 } else { 769 64usize 770 }; 771 772 Ok(BitArraySize::Literal(default_size)) 773 } 774 }?; 775 776 let is_signed = segment 777 .options 778 .iter() 779 .any(|x| matches!(x, Opt::Signed { .. })); 780 781 Ok(SizedBitArraySegmentDetails { 782 size, 783 endianness, 784 is_signed, 785 }) 786 } 787 788 fn push_assignment(&mut self, subject: Document<'a>, name: &'a EcoString) { 789 let var = self.next_local_var(name).to_doc(); 790 let subject = self.apply_path_to_subject(subject); 791 self.assignments.push(Assignment { subject, var }); 792 } 793 794 fn push_string_prefix_check(&mut self, subject: Document<'a>, prefix: &'a str) { 795 self.checks.push(Check::StringPrefix { 796 prefix, 797 subject: self.apply_path_to_subject(subject), 798 }) 799 } 800 801 fn push_booly_check(&mut self, subject: Document<'a>, expected_to_be_truthy: bool) { 802 self.checks.push(Check::Booly { 803 expected_to_be_truthy, 804 subject: self.apply_path_to_subject(subject), 805 }) 806 } 807 808 fn push_equality_check(&mut self, subject: Document<'a>, to: Document<'a>) { 809 self.checks.push(Check::Equal { 810 to, 811 subject: self.apply_path_to_subject(subject), 812 }) 813 } 814 815 fn push_variant_check(&mut self, subject: Document<'a>, kind: Document<'a>) { 816 self.checks.push(Check::Variant { 817 kind, 818 subject: self.apply_path_to_subject(subject), 819 }) 820 } 821 822 fn push_result_check(&mut self, subject: Document<'a>, is_ok: bool) { 823 self.checks.push(Check::Result { 824 is_ok, 825 subject: self.apply_path_to_subject(subject), 826 }) 827 } 828 829 fn push_list_length_check( 830 &mut self, 831 subject: Document<'a>, 832 expected_length: usize, 833 has_tail_spread: bool, 834 ) { 835 self.checks.push(Check::ListLength { 836 expected_length, 837 has_tail_spread, 838 subject: self.apply_path_to_subject(subject), 839 }) 840 } 841 842 fn push_bit_array_bit_size_check( 843 &mut self, 844 subject: Document<'a>, 845 expected_bit_size: OffsetBits, 846 tail_spread_type: Option<BitArrayTailSpreadType>, 847 ) { 848 self.checks.push(Check::BitArrayBitSize { 849 expected_bit_size, 850 tail_spread_type, 851 subject: self.apply_path_to_subject(subject), 852 }) 853 } 854} 855 856#[derive(Debug)] 857pub struct CompiledPattern<'a> { 858 pub checks: Vec<Check<'a>>, 859 pub assignments: Vec<Assignment<'a>>, 860} 861 862#[derive(Debug)] 863pub struct Assignment<'a> { 864 var: Document<'a>, 865 pub subject: Document<'a>, 866} 867 868impl<'a> Assignment<'a> { 869 pub fn into_doc(self) -> Document<'a> { 870 docvec!["let ", self.var, " = ", self.subject, ";"] 871 } 872} 873 874#[derive(Debug)] 875pub enum Check<'a> { 876 Result { 877 subject: Document<'a>, 878 is_ok: bool, 879 }, 880 Variant { 881 subject: Document<'a>, 882 kind: Document<'a>, 883 }, 884 Equal { 885 subject: Document<'a>, 886 to: Document<'a>, 887 }, 888 ListLength { 889 subject: Document<'a>, 890 expected_length: usize, 891 has_tail_spread: bool, 892 }, 893 BitArrayBitSize { 894 subject: Document<'a>, 895 expected_bit_size: OffsetBits, 896 tail_spread_type: Option<BitArrayTailSpreadType>, 897 }, 898 StringPrefix { 899 subject: Document<'a>, 900 prefix: &'a str, 901 }, 902 Booly { 903 subject: Document<'a>, 904 expected_to_be_truthy: bool, 905 }, 906} 907 908impl<'a> Check<'a> { 909 pub fn into_doc(self, match_desired: bool) -> Document<'a> { 910 match self { 911 Check::Booly { 912 expected_to_be_truthy, 913 subject, 914 } => { 915 if expected_to_be_truthy == match_desired { 916 subject 917 } else { 918 docvec!["!", subject] 919 } 920 } 921 922 Check::Variant { subject, kind } => { 923 if match_desired { 924 docvec![subject, " instanceof ", kind] 925 } else { 926 docvec!["!(", subject, " instanceof ", kind, ")"] 927 } 928 } 929 930 Check::Result { subject, is_ok } => { 931 if match_desired == is_ok { 932 docvec![subject, ".isOk()"] 933 } else { 934 docvec!["!", subject, ".isOk()"] 935 } 936 } 937 938 Check::Equal { subject, to } => { 939 let operator = if match_desired { " === " } else { " !== " }; 940 docvec![subject, operator, to] 941 } 942 943 Check::ListLength { 944 subject, 945 expected_length, 946 has_tail_spread, 947 } => { 948 let length_check = if has_tail_spread { 949 eco_format!(".atLeastLength({expected_length})").to_doc() 950 } else { 951 eco_format!(".hasLength({expected_length})").to_doc() 952 }; 953 if match_desired { 954 docvec![subject, length_check,] 955 } else { 956 docvec!["!", subject, length_check,] 957 } 958 } 959 Check::BitArrayBitSize { 960 subject, 961 expected_bit_size, 962 tail_spread_type, 963 } => { 964 let bit_size = docvec![subject.clone(), ".bitSize"]; 965 966 let bit_size_check = match tail_spread_type { 967 Some(BitArrayTailSpreadType::Bits) => { 968 docvec![bit_size, " >= ", expected_bit_size] 969 } 970 Some(BitArrayTailSpreadType::Bytes) => { 971 // When the tail spread is for bytes rather than bits, 972 // check that there is a whole number of bytes left in 973 // the bit array 974 docvec![ 975 "(", 976 bit_size.clone(), 977 " >= ", 978 expected_bit_size, 979 " && (", 980 bit_size, 981 " - ", 982 expected_bit_size, 983 ") % 8 === 0)" 984 ] 985 } 986 None => docvec![bit_size, " == ", expected_bit_size], 987 }; 988 989 if match_desired { 990 bit_size_check 991 } else { 992 docvec!["!(", bit_size_check, ")"] 993 } 994 } 995 Check::StringPrefix { subject, prefix } => { 996 let prefix = expression::string(prefix); 997 if match_desired { 998 docvec![subject, ".startsWith(", prefix, ")"] 999 } else { 1000 docvec!["!", subject, ".startsWith(", prefix, ")"] 1001 } 1002 } 1003 } 1004 } 1005 1006 pub(crate) fn may_require_wrapping(&self) -> bool { 1007 match self { 1008 Check::Result { .. } 1009 | Check::Variant { .. } 1010 | Check::Equal { .. } 1011 | Check::ListLength { .. } 1012 | Check::BitArrayBitSize { .. } 1013 | Check::StringPrefix { .. } 1014 | Check::Booly { .. } => false, 1015 } 1016 } 1017} 1018 1019pub(crate) fn assign_subject<'a>( 1020 expression_generator: &mut expression::Generator<'_, 'a>, 1021 subject: &'a TypedExpr, 1022) -> (EcoString, Option<EcoString>) { 1023 static ASSIGNMENT_VAR_ECO_STR: OnceLock<EcoString> = OnceLock::new(); 1024 1025 match subject { 1026 // If the value is a variable we don't need to assign it to a new 1027 // variable, we can the value expression safely without worrying about 1028 // performing computation or side effects multiple times. 1029 TypedExpr::Var { 1030 name, constructor, .. 1031 } if constructor.is_local_variable() => (expression_generator.local_var(name), None), 1032 // If it's not a variable we need to assign it to a variable 1033 // to avoid rendering the subject expression multiple times 1034 _ => { 1035 let subject = expression_generator 1036 .next_local_var(ASSIGNMENT_VAR_ECO_STR.get_or_init(|| ASSIGNMENT_VAR.into())); 1037 (subject.clone(), Some(subject)) 1038 } 1039 } 1040} 1041 1042/// Calculates the length of str as utf16 without escape characters. 1043fn utf16_no_escape_len(str: &EcoString) -> usize { 1044 convert_string_escape_chars(str).encode_utf16().count() 1045}