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
47 kB 1293 lines
1use num_bigint::BigInt; 2use std::sync::OnceLock; 3 4use super::{expression::is_js_scalar, *}; 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 local_var(&mut self, name: &EcoString) -> EcoString { 193 self.expression_generator.local_var(name) 194 } 195 196 fn push_string(&mut self, s: &'a str) { 197 self.path.push(Index::String(s)); 198 } 199 200 fn push_int(&mut self, i: usize) { 201 self.path.push(Index::Int(i)); 202 } 203 204 fn push_string_prefix_slice(&mut self, i: usize) { 205 self.path.push(Index::StringPrefixSlice(i)); 206 } 207 208 fn push_byte_at(&mut self, i: OffsetBits) { 209 self.path.push(Index::ByteAt(i)); 210 } 211 212 fn push_bit_array_slice_to_int( 213 &mut self, 214 start: OffsetBits, 215 end: OffsetBits, 216 endianness: Endianness, 217 is_signed: bool, 218 ) { 219 self.expression_generator 220 .tracker 221 .bit_array_slice_to_int_used = true; 222 223 self.path.push(Index::BitArraySliceToInt { 224 start, 225 end, 226 endianness, 227 is_signed, 228 }); 229 } 230 231 fn push_bit_array_slice_to_float( 232 &mut self, 233 start: OffsetBits, 234 end: OffsetBits, 235 endianness: Endianness, 236 ) { 237 self.expression_generator 238 .tracker 239 .bit_array_slice_to_float_used = true; 240 241 self.path.push(Index::BitArraySliceToFloat { 242 start, 243 end, 244 endianness, 245 }); 246 } 247 248 fn push_bit_array_slice(&mut self, start: OffsetBits, end: Option<OffsetBits>) { 249 self.expression_generator.tracker.bit_array_slice_used = true; 250 self.path.push(Index::BitArraySlice(start, end)); 251 } 252 253 fn push_string_times(&mut self, s: &'a str, times: usize) { 254 for _ in 0..times { 255 self.push_string(s); 256 } 257 } 258 259 fn pop(&mut self) { 260 let _ = self.path.pop(); 261 } 262 263 fn pop_times(&mut self, times: usize) { 264 for _ in 0..times { 265 self.pop(); 266 } 267 } 268 269 fn apply_path_to_subject(&self, subject: Document<'a>) -> Document<'a> { 270 self.path 271 .iter() 272 .fold(subject, |acc, segment| match segment { 273 Index::Int(i) => acc.append(eco_format!("[{i}]").to_doc()), 274 // TODO: escape string if needed 275 Index::String(s) => acc.append(docvec![".", maybe_escape_property_doc(s)]), 276 Index::ByteAt(i) => acc.append(docvec![".byteAt(", i, ")"]), 277 Index::BitArraySliceToInt { 278 start, 279 end, 280 endianness, 281 is_signed, 282 } => docvec![ 283 "bitArraySliceToInt(", 284 acc, 285 ", ", 286 start, 287 ", ", 288 end, 289 ", ", 290 bool(endianness.is_big()), 291 ", ", 292 bool(*is_signed), 293 ")" 294 ], 295 Index::BitArraySliceToFloat { 296 start, 297 end, 298 endianness, 299 } => docvec![ 300 "bitArraySliceToFloat(", 301 acc, 302 ", ", 303 start, 304 ", ", 305 end, 306 ", ", 307 bool(endianness.is_big()), 308 ")" 309 ], 310 Index::BitArraySlice(start, end) => match end { 311 Some(end) => { 312 docvec!["bitArraySlice(", acc, ", ", start, ", ", end, ")"] 313 } 314 None => docvec!["bitArraySlice(", acc, ", ", start, ")"], 315 }, 316 Index::StringPrefixSlice(i) => docvec!(acc, ".slice(", i, ")"), 317 }) 318 } 319 320 pub fn generate( 321 &mut self, 322 subjects: &[Document<'a>], 323 patterns: &'a [TypedPattern], 324 guard: Option<&'a TypedClauseGuard>, 325 ) -> Result<CompiledPattern<'a>, Error> { 326 for (subject, pattern) in subjects.iter().zip_eq(patterns) { 327 self.traverse_pattern(subject, pattern)?; 328 } 329 if let Some(guard) = guard { 330 self.push_guard_check(guard)?; 331 } 332 333 Ok(self.take_compiled()) 334 } 335 336 pub fn take_compiled(&mut self) -> CompiledPattern<'a> { 337 CompiledPattern { 338 checks: std::mem::take(&mut self.checks), 339 assignments: std::mem::take(&mut self.assignments), 340 } 341 } 342 343 fn push_guard_check(&mut self, guard: &'a TypedClauseGuard) -> Result<(), Error> { 344 let expression = self.guard(guard)?; 345 self.checks.push(Check::Guard { expression }); 346 Ok(()) 347 } 348 349 fn wrapped_guard(&mut self, guard: &'a TypedClauseGuard) -> Result<Document<'a>, Error> { 350 match guard { 351 ClauseGuard::Var { .. } 352 | ClauseGuard::TupleIndex { .. } 353 | ClauseGuard::Constant(_) 354 | ClauseGuard::Not { .. } 355 | ClauseGuard::FieldAccess { .. } => self.guard(guard), 356 357 ClauseGuard::Equals { .. } 358 | ClauseGuard::NotEquals { .. } 359 | ClauseGuard::GtInt { .. } 360 | ClauseGuard::GtEqInt { .. } 361 | ClauseGuard::LtInt { .. } 362 | ClauseGuard::LtEqInt { .. } 363 | ClauseGuard::GtFloat { .. } 364 | ClauseGuard::GtEqFloat { .. } 365 | ClauseGuard::LtFloat { .. } 366 | ClauseGuard::LtEqFloat { .. } 367 | ClauseGuard::AddInt { .. } 368 | ClauseGuard::AddFloat { .. } 369 | ClauseGuard::SubInt { .. } 370 | ClauseGuard::SubFloat { .. } 371 | ClauseGuard::MultInt { .. } 372 | ClauseGuard::MultFloat { .. } 373 | ClauseGuard::DivInt { .. } 374 | ClauseGuard::DivFloat { .. } 375 | ClauseGuard::RemainderInt { .. } 376 | ClauseGuard::Or { .. } 377 | ClauseGuard::And { .. } 378 | ClauseGuard::ModuleSelect { .. } => Ok(docvec!["(", self.guard(guard)?, ")"]), 379 } 380 } 381 382 fn guard(&mut self, guard: &'a TypedClauseGuard) -> Output<'a> { 383 Ok(match guard { 384 ClauseGuard::Equals { left, right, .. } if is_js_scalar(left.type_()) => { 385 let left = self.wrapped_guard(left)?; 386 let right = self.wrapped_guard(right)?; 387 docvec![left, " === ", right] 388 } 389 390 ClauseGuard::NotEquals { left, right, .. } if is_js_scalar(left.type_()) => { 391 let left = self.wrapped_guard(left)?; 392 let right = self.wrapped_guard(right)?; 393 docvec![left, " !== ", right] 394 } 395 396 ClauseGuard::Equals { left, right, .. } => { 397 let left = self.guard(left)?; 398 let right = self.guard(right)?; 399 self.expression_generator 400 .prelude_equal_call(true, left, right) 401 } 402 403 ClauseGuard::NotEquals { left, right, .. } => { 404 let left = self.guard(left)?; 405 let right = self.guard(right)?; 406 self.expression_generator 407 .prelude_equal_call(false, left, right) 408 } 409 410 ClauseGuard::GtFloat { left, right, .. } | ClauseGuard::GtInt { left, right, .. } => { 411 let left = self.wrapped_guard(left)?; 412 let right = self.wrapped_guard(right)?; 413 docvec![left, " > ", right] 414 } 415 416 ClauseGuard::GtEqFloat { left, right, .. } 417 | ClauseGuard::GtEqInt { left, right, .. } => { 418 let left = self.wrapped_guard(left)?; 419 let right = self.wrapped_guard(right)?; 420 docvec![left, " >= ", right] 421 } 422 423 ClauseGuard::LtFloat { left, right, .. } | ClauseGuard::LtInt { left, right, .. } => { 424 let left = self.wrapped_guard(left)?; 425 let right = self.wrapped_guard(right)?; 426 docvec![left, " < ", right] 427 } 428 429 ClauseGuard::LtEqFloat { left, right, .. } 430 | ClauseGuard::LtEqInt { left, right, .. } => { 431 let left = self.wrapped_guard(left)?; 432 let right = self.wrapped_guard(right)?; 433 docvec![left, " <= ", right] 434 } 435 436 ClauseGuard::AddFloat { left, right, .. } | ClauseGuard::AddInt { left, right, .. } => { 437 let left = self.wrapped_guard(left)?; 438 let right = self.wrapped_guard(right)?; 439 docvec![left, " + ", right] 440 } 441 442 ClauseGuard::SubFloat { left, right, .. } | ClauseGuard::SubInt { left, right, .. } => { 443 let left = self.wrapped_guard(left)?; 444 let right = self.wrapped_guard(right)?; 445 docvec![left, " - ", right] 446 } 447 448 ClauseGuard::MultFloat { left, right, .. } 449 | ClauseGuard::MultInt { left, right, .. } => { 450 let left = self.wrapped_guard(left)?; 451 let right = self.wrapped_guard(right)?; 452 docvec![left, " * ", right] 453 } 454 455 ClauseGuard::DivFloat { left, right, .. } => { 456 let left = self.wrapped_guard(left)?; 457 let right = self.wrapped_guard(right)?; 458 self.expression_generator.tracker.float_division_used = true; 459 docvec!["divideFloat", wrap_args([left, right])] 460 } 461 462 ClauseGuard::DivInt { left, right, .. } => { 463 let left = self.wrapped_guard(left)?; 464 let right = self.wrapped_guard(right)?; 465 self.expression_generator.tracker.int_division_used = true; 466 docvec!["divideInt", wrap_args([left, right])] 467 } 468 469 ClauseGuard::RemainderInt { left, right, .. } => { 470 let left = self.wrapped_guard(left)?; 471 let right = self.wrapped_guard(right)?; 472 self.expression_generator.tracker.int_remainder_used = true; 473 docvec!["remainderInt", wrap_args([left, right])] 474 } 475 476 ClauseGuard::Or { left, right, .. } => { 477 let left = self.wrapped_guard(left)?; 478 let right = self.wrapped_guard(right)?; 479 docvec![left, " || ", right] 480 } 481 482 ClauseGuard::And { left, right, .. } => { 483 let left = self.wrapped_guard(left)?; 484 let right = self.wrapped_guard(right)?; 485 docvec![left, " && ", right] 486 } 487 488 ClauseGuard::Var { name, .. } => self 489 .path_doc_from_assignments(name) 490 .unwrap_or_else(|| self.local_var(name).to_doc()), 491 492 ClauseGuard::TupleIndex { tuple, index, .. } => { 493 docvec![self.guard(tuple)?, "[", index, "]"] 494 } 495 496 ClauseGuard::FieldAccess { 497 label, container, .. 498 } => { 499 docvec![ 500 self.guard(container)?, 501 ".", 502 maybe_escape_property_doc(label) 503 ] 504 } 505 506 ClauseGuard::ModuleSelect { 507 module_alias, 508 label, 509 .. 510 } => docvec!["$", module_alias, ".", label], 511 512 ClauseGuard::Not { expression, .. } => { 513 docvec!["!", self.guard(expression)?] 514 } 515 516 ClauseGuard::Constant(constant) => { 517 return expression::guard_constant_expression( 518 &mut self.assignments, 519 self.expression_generator.tracker, 520 constant, 521 ); 522 } 523 }) 524 } 525 526 /// Get the path that would assign a variable, if there is one for the given name. 527 /// This is in used in clause guards where may use variables defined in 528 /// patterns can be referenced, but in the compiled JavaScript they have not 529 /// yet been defined. 530 fn path_doc_from_assignments(&self, name: &str) -> Option<Document<'a>> { 531 self.assignments 532 .iter() 533 .find(|assignment| assignment.name == name) 534 .map(|assignment| assignment.subject.clone()) 535 } 536 537 pub fn traverse_pattern( 538 &mut self, 539 subject: &Document<'a>, 540 pattern: &'a TypedPattern, 541 ) -> Result<(), Error> { 542 match pattern { 543 Pattern::String { value, .. } => { 544 self.push_equality_check(subject.clone(), expression::string(value)); 545 Ok(()) 546 } 547 Pattern::Int { value, .. } => { 548 self.push_equality_check(subject.clone(), expression::int(value)); 549 Ok(()) 550 } 551 Pattern::Float { value, .. } => { 552 self.push_equality_check(subject.clone(), expression::float(value)); 553 Ok(()) 554 } 555 556 Pattern::Discard { .. } => Ok(()), 557 558 Pattern::Variable { name, .. } => { 559 self.push_assignment(subject.clone(), name); 560 Ok(()) 561 } 562 563 Pattern::Assign { name, pattern, .. } => { 564 self.push_assignment(subject.clone(), name); 565 self.traverse_pattern(subject, pattern) 566 } 567 568 Pattern::List { elements, tail, .. } => { 569 self.push_list_length_check(subject.clone(), elements.len(), tail.is_some()); 570 for pattern in elements { 571 self.push_string("head"); 572 self.traverse_pattern(subject, pattern)?; 573 self.pop(); 574 self.push_string("tail"); 575 } 576 self.pop_times(elements.len()); 577 if let Some(pattern) = tail { 578 self.push_string_times("tail", elements.len()); 579 self.traverse_pattern(subject, pattern)?; 580 self.pop_times(elements.len()); 581 } 582 Ok(()) 583 } 584 585 Pattern::Tuple { elements, .. } => { 586 // We don't check the length because type system ensures it's a 587 // tuple of the correct size 588 for (index, pattern) in elements.iter().enumerate() { 589 self.push_int(index); 590 self.traverse_pattern(subject, pattern)?; 591 self.pop(); 592 } 593 Ok(()) 594 } 595 596 Pattern::Constructor { 597 type_, 598 constructor: Inferred::Known(PatternConstructor { name, .. }), 599 .. 600 } if type_.is_bool() && name == "True" => { 601 self.push_booly_check(subject.clone(), true); 602 Ok(()) 603 } 604 605 Pattern::Constructor { 606 type_, 607 constructor: Inferred::Known(PatternConstructor { name, .. }), 608 .. 609 } if type_.is_bool() && name == "False" => { 610 self.push_booly_check(subject.clone(), false); 611 Ok(()) 612 } 613 614 Pattern::Constructor { 615 type_, 616 constructor: Inferred::Known(PatternConstructor { .. }), 617 .. 618 } if type_.is_nil() => { 619 self.push_booly_check(subject.clone(), false); 620 Ok(()) 621 } 622 623 Pattern::Constructor { 624 constructor: Inferred::Unknown, 625 .. 626 } => { 627 panic!("JavaScript generation performed with uninferred pattern constructor"); 628 } 629 630 Pattern::StringPrefix { 631 left_side_string, 632 right_side_assignment, 633 left_side_assignment, 634 .. 635 } => { 636 self.push_string_prefix_check(subject.clone(), left_side_string); 637 if let AssignName::Variable(right) = right_side_assignment { 638 self.push_string_prefix_slice(utf16_no_escape_len(left_side_string)); 639 self.push_assignment(subject.clone(), right); 640 // After pushing the assignment we need to pop the prefix slicing we used to 641 // check the condition. 642 self.pop(); 643 } 644 if let Some((left, _)) = left_side_assignment { 645 // "wibble" as prefix <> rest 646 // ^^^^^^^^^ In case the left prefix of the pattern matching is given an 647 // alias we bind it to a local variable so that it can be 648 // correctly referenced inside the case branch. 649 // let prefix = "wibble"; 650 // ^^^^^^^^^^^^^^^^^^^^^ we're adding this assignment inside the if clause 651 // the case branch gets translated into. 652 // 653 // We also want to push this assignment without using push_assignment, since we 654 // do _not_ want to access the current path on the static string! 655 let var = self.next_local_var(left).to_doc(); 656 self.assignments.push(Assignment { 657 subject: expression::string(left_side_string), 658 name: left, 659 var, 660 }); 661 } 662 Ok(()) 663 } 664 665 Pattern::Constructor { 666 constructor: 667 Inferred::Known(PatternConstructor { 668 field_map, 669 name: record_name, 670 .. 671 }), 672 arguments, 673 name, 674 type_, 675 module, 676 .. 677 } => { 678 match module { 679 _ if type_.is_result() => { 680 self.push_result_check(subject.clone(), record_name == "Ok") 681 } 682 Some((m, _)) => { 683 self.push_variant_check(subject.clone(), docvec!["$", m, ".", name]) 684 } 685 None => self.push_variant_check(subject.clone(), name.to_doc()), 686 } 687 688 for (index, arg) in arguments.iter().enumerate() { 689 match field_map { 690 None => self.push_int(index), 691 Some(FieldMap { fields, .. }) => { 692 let find = |(key, &val)| { 693 if val as usize == index { 694 Some(key) 695 } else { 696 None 697 } 698 }; 699 let label = fields.iter().find_map(find); 700 match label { 701 Some(label) => self.push_string(label), 702 None => self.push_int(index), 703 } 704 } 705 } 706 self.traverse_pattern(subject, &arg.value)?; 707 self.pop(); 708 } 709 Ok(()) 710 } 711 712 Pattern::BitArray { segments, .. } => { 713 use BitArrayOption as Opt; 714 715 let mut offset = Offset::new(); 716 for segment in segments { 717 if segment.type_ == crate::type_::int() 718 || segment.type_ == crate::type_::float() 719 { 720 let details = self.sized_bit_array_segment_details(segment)?; 721 722 match (segment.value.as_ref(), details.size) { 723 (Pattern::Int { int_value, .. }, BitArraySize::Literal(size)) 724 if size <= SAFE_INT_SEGMENT_MAX_SIZE 725 && size % 8 == 0 726 && offset.bits.is_whole_number_of_bytes() => 727 { 728 let bytes = bit_array_segment_int_value_to_bytes( 729 (*int_value).clone(), 730 BigInt::from(size), 731 details.endianness, 732 )?; 733 734 for byte in bytes { 735 self.push_byte_at(offset.bits.clone().divide(8)); 736 self.push_equality_check(subject.clone(), docvec![byte]); 737 self.pop(); 738 offset.bits.increment(BitArraySize::Literal(8)); 739 } 740 } 741 742 (Pattern::Discard { .. }, size) => { 743 offset.bits.increment(size); 744 } 745 746 (_, size) => { 747 let start = offset.bits.clone(); 748 let mut end = offset.bits.clone(); 749 end.increment(size.clone()); 750 751 if segment.type_ == crate::type_::int() { 752 if size.is_constant_value(8) 753 && !details.is_signed 754 && offset.bits.is_whole_number_of_bytes() 755 { 756 self.push_byte_at(offset.bits.clone().divide(8)); 757 } else { 758 self.push_bit_array_slice_to_int( 759 start, 760 end, 761 details.endianness, 762 details.is_signed, 763 ); 764 } 765 } else { 766 self.push_bit_array_slice_to_float( 767 start, 768 end, 769 details.endianness, 770 ); 771 } 772 773 self.traverse_pattern(subject, &segment.value)?; 774 self.pop(); 775 offset.bits.increment(size); 776 } 777 } 778 } else { 779 match segment.options.as_slice() { 780 [Opt::Bits { .. }] => { 781 self.push_bit_array_slice(offset.bits.clone(), None); 782 self.traverse_pattern(subject, &segment.value)?; 783 self.pop(); 784 offset.set_open_ended(BitArrayTailSpreadType::Bits); 785 Ok(()) 786 } 787 788 [Opt::Bits { .. }, Opt::Size { value: size, .. }] 789 | [Opt::Size { value: size, .. }, Opt::Bits { .. }] => match &**size { 790 Pattern::Int { value, .. } => { 791 let start = offset.bits.clone(); 792 let increment = value.parse::<usize>().expect( 793 "part of an Int node should always parse as integer", 794 ); 795 offset.bits.increment(BitArraySize::Literal(increment)); 796 let end = offset.bits.clone(); 797 798 self.push_bit_array_slice(start, Some(end)); 799 self.traverse_pattern(subject, &segment.value)?; 800 self.pop(); 801 Ok(()) 802 } 803 804 Pattern::VarUsage { name, .. } => { 805 let name = self.expression_generator.local_var(name); 806 let start = offset.bits.clone(); 807 808 offset.bits.increment(BitArraySize::Variable(name)); 809 let end = offset.bits.clone(); 810 811 self.push_bit_array_slice(start, Some(end)); 812 self.traverse_pattern(subject, &segment.value)?; 813 self.pop(); 814 Ok(()) 815 } 816 817 _ => Err(Error::Unsupported { 818 feature: "This bit array size option in patterns".into(), 819 location: segment.location, 820 }), 821 }, 822 823 [Opt::Bytes { .. }] => { 824 self.push_bit_array_slice(offset.bits.clone(), None); 825 self.traverse_pattern(subject, &segment.value)?; 826 self.pop(); 827 offset.set_open_ended(BitArrayTailSpreadType::Bytes); 828 Ok(()) 829 } 830 831 [Opt::Bytes { .. }, Opt::Size { value: size, .. }] 832 | [Opt::Size { value: size, .. }, Opt::Bytes { .. }] => match &**size { 833 Pattern::Int { value, .. } => { 834 let start = offset.bits.clone(); 835 let increment = value.parse::<usize>().expect( 836 "part of an Int node should always parse as integer", 837 ) * 8; 838 offset.bits.increment(BitArraySize::Literal(increment)); 839 let end = offset.bits.clone(); 840 841 self.push_bit_array_slice(start, Some(end)); 842 self.traverse_pattern(subject, &segment.value)?; 843 self.pop(); 844 Ok(()) 845 } 846 847 Pattern::VarUsage { name, .. } => { 848 let start = offset.bits.clone(); 849 let mut name = self.expression_generator.local_var(name); 850 name.push_str(" * 8"); 851 offset.bits.increment(BitArraySize::Variable(name)); 852 let end = offset.bits.clone(); 853 854 self.push_bit_array_slice(start, Some(end)); 855 self.traverse_pattern(subject, &segment.value)?; 856 self.pop(); 857 Ok(()) 858 } 859 860 _ => Err(Error::Unsupported { 861 feature: "This bit array size option in patterns".into(), 862 location: segment.location, 863 }), 864 }, 865 866 [Opt::Utf8 { .. }] => match segment.value.as_ref() { 867 Pattern::String { value, .. } => { 868 for byte in convert_string_escape_chars(value).as_bytes() { 869 if offset.bits.is_whole_number_of_bytes() { 870 self.push_byte_at(offset.bits.clone().divide(8)); 871 } else { 872 let mut end = offset.bits.clone(); 873 end.increment(BitArraySize::Literal(8)); 874 875 self.push_bit_array_slice_to_int( 876 offset.bits.clone(), 877 end, 878 Endianness::Big, 879 false, 880 ); 881 } 882 self.push_equality_check(subject.clone(), byte.to_doc()); 883 self.pop(); 884 offset.bits.increment(BitArraySize::Literal(8)); 885 } 886 887 Ok(()) 888 } 889 890 _ => Err(Error::Unsupported { 891 feature: "This bit array segment option in patterns".into(), 892 location: segment.location, 893 }), 894 }, 895 896 _ => Err(Error::Unsupported { 897 feature: "This bit array segment option in patterns".into(), 898 location: segment.location, 899 }), 900 }?; 901 } 902 } 903 904 self.push_bit_array_bit_size_check( 905 subject.clone(), 906 offset.bits.clone(), 907 offset.tail_spread_type, 908 ); 909 Ok(()) 910 } 911 Pattern::VarUsage { location, .. } => Err(Error::Unsupported { 912 feature: "Bit array matching".into(), 913 location: *location, 914 }), 915 Pattern::Invalid { .. } => panic!("invalid patterns should not reach code generation"), 916 } 917 } 918 919 fn sized_bit_array_segment_details( 920 &mut self, 921 segment: &TypedPatternBitArraySegment, 922 ) -> Result<SizedBitArraySegmentDetails, Error> { 923 use BitArrayOption as Opt; 924 925 if segment 926 .options 927 .iter() 928 .any(|x| matches!(x, Opt::Native { .. })) 929 { 930 return Err(Error::Unsupported { 931 feature: "This bit array segment option".into(), 932 location: segment.location, 933 }); 934 } 935 936 let endianness = if segment 937 .options 938 .iter() 939 .any(|x| matches!(x, Opt::Little { .. })) 940 { 941 Endianness::Little 942 } else { 943 Endianness::Big 944 }; 945 946 let unit = segment 947 .options 948 .iter() 949 .find_map(|option| match option { 950 Opt::Unit { value, .. } => Some(*value), 951 _ => None, 952 }) 953 .unwrap_or(1); 954 955 let size = match segment 956 .options 957 .iter() 958 .find(|x| matches!(x, Opt::Size { .. })) 959 { 960 Some(Opt::Size { value: size, .. }) => match &**size { 961 Pattern::Int { value, .. } => Ok(BitArraySize::Literal( 962 value 963 .parse::<usize>() 964 .expect("part of an Int node should always parse as integer") 965 * unit as usize, 966 )), 967 Pattern::VarUsage { name, .. } => { 968 let mut variable = self.expression_generator.local_var(name); 969 if unit != 1 { 970 variable.push_str(&eco_format!(" * {unit}")); 971 } 972 Ok(BitArraySize::Variable(variable)) 973 } 974 _ => Err(Error::Unsupported { 975 feature: "Non-constant size option in patterns".into(), 976 location: segment.location, 977 }), 978 }, 979 980 _ => { 981 let default_size = if segment.type_ == crate::type_::int() { 982 8usize 983 } else { 984 64usize 985 }; 986 987 Ok(BitArraySize::Literal(default_size)) 988 } 989 }?; 990 991 let is_signed = segment 992 .options 993 .iter() 994 .any(|x| matches!(x, Opt::Signed { .. })); 995 996 Ok(SizedBitArraySegmentDetails { 997 size, 998 endianness, 999 is_signed, 1000 }) 1001 } 1002 1003 fn push_assignment(&mut self, subject: Document<'a>, name: &'a EcoString) { 1004 let var = self.next_local_var(name).to_doc(); 1005 let subject = self.apply_path_to_subject(subject); 1006 self.assignments.push(Assignment { subject, var, name }); 1007 } 1008 1009 fn push_string_prefix_check(&mut self, subject: Document<'a>, prefix: &'a str) { 1010 self.checks.push(Check::StringPrefix { 1011 prefix, 1012 subject: self.apply_path_to_subject(subject), 1013 }) 1014 } 1015 1016 fn push_booly_check(&mut self, subject: Document<'a>, expected_to_be_truthy: bool) { 1017 self.checks.push(Check::Booly { 1018 expected_to_be_truthy, 1019 subject: self.apply_path_to_subject(subject), 1020 }) 1021 } 1022 1023 fn push_equality_check(&mut self, subject: Document<'a>, to: Document<'a>) { 1024 self.checks.push(Check::Equal { 1025 to, 1026 subject: self.apply_path_to_subject(subject), 1027 }) 1028 } 1029 1030 fn push_variant_check(&mut self, subject: Document<'a>, kind: Document<'a>) { 1031 self.checks.push(Check::Variant { 1032 kind, 1033 subject: self.apply_path_to_subject(subject), 1034 }) 1035 } 1036 1037 fn push_result_check(&mut self, subject: Document<'a>, is_ok: bool) { 1038 self.checks.push(Check::Result { 1039 is_ok, 1040 subject: self.apply_path_to_subject(subject), 1041 }) 1042 } 1043 1044 fn push_list_length_check( 1045 &mut self, 1046 subject: Document<'a>, 1047 expected_length: usize, 1048 has_tail_spread: bool, 1049 ) { 1050 self.checks.push(Check::ListLength { 1051 expected_length, 1052 has_tail_spread, 1053 subject: self.apply_path_to_subject(subject), 1054 }) 1055 } 1056 1057 fn push_bit_array_bit_size_check( 1058 &mut self, 1059 subject: Document<'a>, 1060 expected_bit_size: OffsetBits, 1061 tail_spread_type: Option<BitArrayTailSpreadType>, 1062 ) { 1063 self.checks.push(Check::BitArrayBitSize { 1064 expected_bit_size, 1065 tail_spread_type, 1066 subject: self.apply_path_to_subject(subject), 1067 }) 1068 } 1069} 1070 1071#[derive(Debug)] 1072pub struct CompiledPattern<'a> { 1073 pub checks: Vec<Check<'a>>, 1074 pub assignments: Vec<Assignment<'a>>, 1075} 1076 1077impl CompiledPattern<'_> { 1078 pub fn has_assignments(&self) -> bool { 1079 !self.assignments.is_empty() 1080 } 1081} 1082 1083#[derive(Debug)] 1084pub struct Assignment<'a> { 1085 pub name: &'a str, 1086 var: Document<'a>, 1087 pub subject: Document<'a>, 1088} 1089 1090impl<'a> Assignment<'a> { 1091 pub fn into_doc(self) -> Document<'a> { 1092 docvec!["let ", self.var, " = ", self.subject, ";"] 1093 } 1094} 1095 1096#[derive(Debug)] 1097pub enum Check<'a> { 1098 Result { 1099 subject: Document<'a>, 1100 is_ok: bool, 1101 }, 1102 Variant { 1103 subject: Document<'a>, 1104 kind: Document<'a>, 1105 }, 1106 Equal { 1107 subject: Document<'a>, 1108 to: Document<'a>, 1109 }, 1110 ListLength { 1111 subject: Document<'a>, 1112 expected_length: usize, 1113 has_tail_spread: bool, 1114 }, 1115 BitArrayBitSize { 1116 subject: Document<'a>, 1117 expected_bit_size: OffsetBits, 1118 tail_spread_type: Option<BitArrayTailSpreadType>, 1119 }, 1120 StringPrefix { 1121 subject: Document<'a>, 1122 prefix: &'a str, 1123 }, 1124 Booly { 1125 subject: Document<'a>, 1126 expected_to_be_truthy: bool, 1127 }, 1128 Guard { 1129 expression: Document<'a>, 1130 }, 1131} 1132 1133impl<'a> Check<'a> { 1134 pub fn into_doc(self, match_desired: bool) -> Document<'a> { 1135 match self { 1136 Check::Guard { expression } => { 1137 if match_desired { 1138 expression 1139 } else { 1140 docvec!["!", expression] 1141 } 1142 } 1143 1144 Check::Booly { 1145 expected_to_be_truthy, 1146 subject, 1147 } => { 1148 if expected_to_be_truthy == match_desired { 1149 subject 1150 } else { 1151 docvec!["!", subject] 1152 } 1153 } 1154 1155 Check::Variant { subject, kind } => { 1156 if match_desired { 1157 docvec![subject, " instanceof ", kind] 1158 } else { 1159 docvec!["!(", subject, " instanceof ", kind, ")"] 1160 } 1161 } 1162 1163 Check::Result { subject, is_ok } => { 1164 if match_desired == is_ok { 1165 docvec![subject, ".isOk()"] 1166 } else { 1167 docvec!["!", subject, ".isOk()"] 1168 } 1169 } 1170 1171 Check::Equal { subject, to } => { 1172 let operator = if match_desired { " === " } else { " !== " }; 1173 docvec![subject, operator, to] 1174 } 1175 1176 Check::ListLength { 1177 subject, 1178 expected_length, 1179 has_tail_spread, 1180 } => { 1181 let length_check = if has_tail_spread { 1182 eco_format!(".atLeastLength({expected_length})").to_doc() 1183 } else { 1184 eco_format!(".hasLength({expected_length})").to_doc() 1185 }; 1186 if match_desired { 1187 docvec![subject, length_check,] 1188 } else { 1189 docvec!["!", subject, length_check,] 1190 } 1191 } 1192 Check::BitArrayBitSize { 1193 subject, 1194 expected_bit_size, 1195 tail_spread_type, 1196 } => { 1197 let bit_size = docvec![subject.clone(), ".bitSize"]; 1198 1199 let bit_size_check = match tail_spread_type { 1200 Some(BitArrayTailSpreadType::Bits) => { 1201 docvec![bit_size, " >= ", expected_bit_size] 1202 } 1203 Some(BitArrayTailSpreadType::Bytes) => { 1204 // When the tail spread is for bytes rather than bits, 1205 // check that there is a whole number of bytes left in 1206 // the bit array 1207 docvec![ 1208 "(", 1209 bit_size.clone(), 1210 " >= ", 1211 expected_bit_size, 1212 " && (", 1213 bit_size, 1214 " - ", 1215 expected_bit_size, 1216 ") % 8 === 0)" 1217 ] 1218 } 1219 None => docvec![bit_size, " == ", expected_bit_size], 1220 }; 1221 1222 if match_desired { 1223 bit_size_check 1224 } else { 1225 docvec!["!(", bit_size_check, ")"] 1226 } 1227 } 1228 Check::StringPrefix { subject, prefix } => { 1229 let prefix = expression::string(prefix); 1230 if match_desired { 1231 docvec![subject, ".startsWith(", prefix, ")"] 1232 } else { 1233 docvec!["!", subject, ".startsWith(", prefix, ")"] 1234 } 1235 } 1236 } 1237 } 1238 1239 pub(crate) fn may_require_wrapping(&self) -> bool { 1240 match self { 1241 Check::Result { .. } 1242 | Check::Variant { .. } 1243 | Check::Equal { .. } 1244 | Check::ListLength { .. } 1245 | Check::BitArrayBitSize { .. } 1246 | Check::StringPrefix { .. } 1247 | Check::Booly { .. } => false, 1248 Check::Guard { .. } => true, 1249 } 1250 } 1251} 1252 1253pub(crate) fn assign_subject<'a>( 1254 expression_generator: &mut expression::Generator<'_, 'a>, 1255 subject: &'a TypedExpr, 1256) -> (Document<'a>, Option<Document<'a>>) { 1257 static ASSIGNMENT_VAR_ECO_STR: OnceLock<EcoString> = OnceLock::new(); 1258 1259 match subject { 1260 // If the value is a variable we don't need to assign it to a new 1261 // variable, we can the value expression safely without worrying about 1262 // performing computation or side effects multiple times. 1263 TypedExpr::Var { 1264 name, constructor, .. 1265 } if constructor.is_local_variable() => { 1266 (expression_generator.local_var(name).to_doc(), None) 1267 } 1268 // If it's not a variable we need to assign it to a variable 1269 // to avoid rendering the subject expression multiple times 1270 _ => { 1271 let subject = expression_generator 1272 .next_local_var(ASSIGNMENT_VAR_ECO_STR.get_or_init(|| ASSIGNMENT_VAR.into())) 1273 .to_doc(); 1274 (subject.clone(), Some(subject)) 1275 } 1276 } 1277} 1278 1279pub(crate) fn assign_subjects<'a>( 1280 expression_generator: &mut expression::Generator<'_, 'a>, 1281 subjects: &'a [TypedExpr], 1282) -> Vec<(Document<'a>, Option<Document<'a>>)> { 1283 let mut out = Vec::with_capacity(subjects.len()); 1284 for subject in subjects { 1285 out.push(assign_subject(expression_generator, subject)) 1286 } 1287 out 1288} 1289 1290/// Calculates the length of str as utf16 without escape characters. 1291fn utf16_no_escape_len(str: &EcoString) -> usize { 1292 convert_string_escape_chars(str).encode_utf16().count() 1293}