Fork of daniellemaywood.uk/gleam — Wasm codegen work
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}