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