Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

Allow variable size bit array segments when pattern matching

+258 -62
+175 -57
compiler-core/src/javascript/pattern.rs
··· 15 15 enum Index<'a> { 16 16 Int(usize), 17 17 String(&'a str), 18 - ByteAt(usize), 18 + ByteAt(OffsetBits), 19 19 BitArraySliceToInt { 20 - start: usize, 21 - end: usize, 20 + start: OffsetBits, 21 + end: OffsetBits, 22 22 endianness: Endianness, 23 23 is_signed: bool, 24 24 }, 25 25 BitArraySliceToFloat { 26 - start: usize, 27 - end: usize, 26 + start: OffsetBits, 27 + end: OffsetBits, 28 28 endianness: Endianness, 29 29 }, 30 - BitArraySlice(usize, Option<usize>), 30 + BitArraySlice(OffsetBits, Option<OffsetBits>), 31 31 StringPrefixSlice(usize), 32 32 } 33 33 ··· 50 50 Bytes, 51 51 } 52 52 53 + #[derive(Debug, Clone)] 54 + pub struct OffsetBits { 55 + constant: usize, 56 + variables: Vec<EcoString>, 57 + extra: Option<EcoString>, 58 + } 59 + 60 + impl OffsetBits { 61 + fn increment(&mut self, size: BitArraySize) { 62 + match size { 63 + BitArraySize::Literal(size) => self.constant += size, 64 + BitArraySize::Variable(name) => self.variables.push(name), 65 + } 66 + } 67 + 68 + fn is_whole_number_of_bytes(&self) -> bool { 69 + self.variables.is_empty() && self.extra.is_none() && self.constant % 8 == 0 70 + } 71 + 72 + fn divide(mut self, by: usize) -> Self { 73 + if self.variables.is_empty() { 74 + self.constant /= by; 75 + } else { 76 + self.extra = Some(eco_format!(" / {by}")); 77 + } 78 + 79 + self 80 + } 81 + } 82 + 83 + impl<'a> Documentable<'a> for OffsetBits { 84 + fn to_doc(self) -> Document<'a> { 85 + let doc = if self.variables.is_empty() { 86 + self.constant.to_doc() 87 + } else if self.constant == 0 { 88 + join( 89 + self.variables.into_iter().map(|variable| variable.to_doc()), 90 + " + ".to_doc(), 91 + ) 92 + .group() 93 + } else { 94 + docvec![ 95 + join( 96 + self.variables.into_iter().map(|variable| variable.to_doc()), 97 + " + ".to_doc() 98 + ), 99 + " + ", 100 + self.constant, 101 + ] 102 + .group() 103 + }; 104 + 105 + match self.extra { 106 + Some(extra) => doc.append(extra).group(), 107 + None => doc, 108 + } 109 + } 110 + } 111 + 53 112 struct Offset { 54 - bits: usize, 113 + bits: OffsetBits, 55 114 tail_spread_type: Option<BitArrayTailSpreadType>, 56 115 } 57 116 58 117 impl Offset { 59 118 pub fn new() -> Self { 60 119 Self { 61 - bits: 0, 120 + bits: OffsetBits { 121 + constant: 0, 122 + variables: Vec::new(), 123 + extra: None, 124 + }, 62 125 tail_spread_type: None, 63 126 } 64 127 } 65 128 // This should never be called on an open ended offset 66 129 // However previous checks ensure bit_array segments without a size are only 67 130 // allowed at the end of a pattern 68 - pub fn increment(&mut self, step: usize) { 69 - self.bits += step 131 + pub fn increment(&mut self, size: BitArraySize) { 132 + match size { 133 + BitArraySize::Literal(n) => self.increment_constant(n), 134 + BitArraySize::Variable(name) => self.increment_variable(name), 135 + } 136 + } 137 + pub fn increment_constant(&mut self, step: usize) { 138 + self.bits.constant += step; 139 + } 140 + pub fn increment_variable(&mut self, variable: EcoString) { 141 + self.bits.variables.push(variable); 70 142 } 71 143 pub fn set_open_ended(&mut self, tail_spread_type: BitArrayTailSpreadType) { 72 144 self.tail_spread_type = Some(tail_spread_type); 73 145 } 74 146 } 75 147 148 + #[derive(Debug, Clone)] 149 + enum BitArraySize { 150 + Literal(usize), 151 + Variable(EcoString), 152 + } 153 + 154 + impl BitArraySize { 155 + fn is_literal_and(&self, function: impl FnOnce(usize) -> bool) -> bool { 156 + match self { 157 + BitArraySize::Literal(n) => function(*n), 158 + BitArraySize::Variable(_) => false, 159 + } 160 + } 161 + } 162 + 76 163 #[derive(Debug)] 77 164 struct SizedBitArraySegmentDetails { 78 - size: usize, 165 + size: BitArraySize, 79 166 endianness: Endianness, 80 167 is_signed: bool, 81 168 } ··· 112 199 self.path.push(Index::StringPrefixSlice(i)); 113 200 } 114 201 115 - fn push_byte_at(&mut self, i: usize) { 202 + fn push_byte_at(&mut self, i: OffsetBits) { 116 203 self.path.push(Index::ByteAt(i)); 117 204 } 118 205 119 206 fn push_bit_array_slice_to_int( 120 207 &mut self, 121 - start: usize, 122 - end: usize, 208 + start: OffsetBits, 209 + end: OffsetBits, 123 210 endianness: Endianness, 124 211 is_signed: bool, 125 212 ) { ··· 135 222 }); 136 223 } 137 224 138 - fn push_bit_array_slice_to_float(&mut self, start: usize, end: usize, endianness: Endianness) { 225 + fn push_bit_array_slice_to_float( 226 + &mut self, 227 + start: OffsetBits, 228 + end: OffsetBits, 229 + endianness: Endianness, 230 + ) { 139 231 self.expression_generator 140 232 .tracker 141 233 .bit_array_slice_to_float_used = true; ··· 147 239 }); 148 240 } 149 241 150 - fn push_bit_array_slice(&mut self, start: usize, end: Option<usize>) { 242 + fn push_bit_array_slice(&mut self, start: OffsetBits, end: Option<OffsetBits>) { 151 243 self.expression_generator.tracker.bit_array_slice_used = true; 152 244 self.path.push(Index::BitArraySlice(start, end)); 153 245 } ··· 175 267 Index::Int(i) => acc.append(eco_format!("[{i}]").to_doc()), 176 268 // TODO: escape string if needed 177 269 Index::String(s) => acc.append(docvec![".", maybe_escape_property_doc(s)]), 178 - Index::ByteAt(i) => acc.append(docvec![".byteAt(", i, ")"]), 270 + Index::ByteAt(i) => acc.append(docvec![".byteAt(", i.clone(), ")"]), 179 271 Index::BitArraySliceToInt { 180 272 start, 181 273 end, ··· 185 277 "bitArraySliceToInt(", 186 278 acc, 187 279 ", ", 188 - start, 280 + start.clone(), 189 281 ", ", 190 - end, 282 + end.clone(), 191 283 ", ", 192 284 bool(endianness.is_big()), 193 285 ", ", ··· 202 294 "bitArraySliceToFloat(", 203 295 acc, 204 296 ", ", 205 - start, 297 + start.clone(), 206 298 ", ", 207 - end, 299 + end.clone(), 208 300 ", ", 209 301 bool(endianness.is_big()), 210 302 ")" 211 303 ], 212 304 Index::BitArraySlice(start, end) => match end { 213 - Some(end) => docvec!["bitArraySlice(", acc, ", ", start, ", ", end, ")"], 214 - None => docvec!["bitArraySlice(", acc, ", ", start, ")"], 305 + Some(end) => { 306 + docvec![ 307 + "bitArraySlice(", 308 + acc, 309 + ", ", 310 + start.clone(), 311 + ", ", 312 + end.clone(), 313 + ")" 314 + ] 315 + } 316 + None => docvec!["bitArraySlice(", acc, ", ", start.clone(), ")"], 215 317 }, 216 318 Index::StringPrefixSlice(i) => docvec!(acc, ".slice(", i, ")"), 217 319 }) ··· 621 723 622 724 match segment.value.as_ref() { 623 725 Pattern::Int { int_value, .. } 624 - if details.size <= SAFE_INT_SEGMENT_MAX_SIZE 625 - && details.size % 8 == 0 626 - && offset.bits % 8 == 0 => 726 + if details 727 + .size 728 + .is_literal_and(|size| size <= SAFE_INT_SEGMENT_MAX_SIZE) 729 + && details.size.is_literal_and(|size| size % 8 == 0) 730 + && offset.bits.is_whole_number_of_bytes() => 627 731 { 732 + let size = match details.size { 733 + BitArraySize::Literal(size) => size, 734 + BitArraySize::Variable(_) => { 735 + unreachable!("We already checked that it's a literal") 736 + } 737 + }; 738 + 628 739 let bytes = bit_array_segment_int_value_to_bytes( 629 740 (*int_value).clone(), 630 - BigInt::from(details.size), 741 + BigInt::from(size), 631 742 details.endianness, 632 743 )?; 633 744 634 745 for byte in bytes { 635 - self.push_byte_at(offset.bits / 8); 746 + self.push_byte_at(offset.bits.clone().divide(8)); 636 747 self.push_equality_check(subject.clone(), docvec![byte]); 637 748 self.pop(); 638 - offset.increment(8); 749 + offset.increment_constant(8); 639 750 } 640 751 } 641 752 ··· 644 755 } 645 756 646 757 _ => { 647 - let start = offset.bits; 648 - let increment = details.size; 649 - let end = offset.bits + increment; 758 + let start = offset.bits.clone(); 759 + let increment = details.size.clone(); 760 + let mut end = offset.bits.clone(); 761 + end.increment(increment.clone()); 650 762 651 763 if segment.type_ == crate::type_::int() { 652 - if details.size == 8 764 + if details.size.is_literal_and(|size| size == 8) 653 765 && !details.is_signed 654 - && offset.bits % 8 == 0 766 + && offset.bits.is_whole_number_of_bytes() 655 767 { 656 - self.push_byte_at(offset.bits / 8); 768 + self.push_byte_at(offset.bits.clone().divide(8)); 657 769 } else { 658 770 self.push_bit_array_slice_to_int( 659 771 start, ··· 678 790 } else { 679 791 match segment.options.as_slice() { 680 792 [Opt::Bits { .. }] => { 681 - self.push_bit_array_slice(offset.bits, None); 793 + self.push_bit_array_slice(offset.bits.clone(), None); 682 794 self.traverse_pattern(subject, &segment.value)?; 683 795 self.pop(); 684 796 offset.set_open_ended(BitArrayTailSpreadType::Bits); ··· 688 800 [Opt::Bits { .. }, Opt::Size { value: size, .. }] 689 801 | [Opt::Size { value: size, .. }, Opt::Bits { .. }] => match &**size { 690 802 Pattern::Int { value, .. } => { 691 - let start = offset.bits; 803 + let start = offset.bits.clone(); 692 804 let increment = value.parse::<usize>().expect( 693 805 "part of an Int node should always parse as integer", 694 806 ); 695 - offset.increment(increment); 696 - let end = offset.bits; 807 + offset.increment_constant(increment); 808 + let end = offset.bits.clone(); 697 809 698 810 self.push_bit_array_slice(start, Some(end)); 699 811 self.traverse_pattern(subject, &segment.value)?; ··· 708 820 }, 709 821 710 822 [Opt::Bytes { .. }] => { 711 - self.push_bit_array_slice(offset.bits, None); 823 + self.push_bit_array_slice(offset.bits.clone(), None); 712 824 self.traverse_pattern(subject, &segment.value)?; 713 825 self.pop(); 714 826 offset.set_open_ended(BitArrayTailSpreadType::Bytes); ··· 718 830 [Opt::Bytes { .. }, Opt::Size { value: size, .. }] 719 831 | [Opt::Size { value: size, .. }, Opt::Bytes { .. }] => match &**size { 720 832 Pattern::Int { value, .. } => { 721 - let start = offset.bits; 833 + let start = offset.bits.clone(); 722 834 let increment = value.parse::<usize>().expect( 723 835 "part of an Int node should always parse as integer", 724 836 ) * 8; 725 - offset.increment(increment); 726 - let end = offset.bits; 837 + offset.increment_constant(increment); 838 + let end = offset.bits.clone(); 727 839 728 840 self.push_bit_array_slice(start, Some(end)); 729 841 self.traverse_pattern(subject, &segment.value)?; ··· 740 852 [Opt::Utf8 { .. }] => match segment.value.as_ref() { 741 853 Pattern::String { value, .. } => { 742 854 for byte in convert_string_escape_chars(value).as_bytes() { 743 - if offset.bits % 8 == 0 { 744 - self.push_byte_at(offset.bits / 8); 855 + if offset.bits.is_whole_number_of_bytes() { 856 + self.push_byte_at(offset.bits.clone().divide(8)); 745 857 } else { 858 + let mut end = offset.bits.clone(); 859 + end.increment(BitArraySize::Literal(8)); 860 + 746 861 self.push_bit_array_slice_to_int( 747 - offset.bits, 748 - offset.bits + 8, 862 + offset.bits.clone(), 863 + end, 749 864 Endianness::Big, 750 865 false, 751 866 ); 752 867 } 753 868 self.push_equality_check(subject.clone(), byte.to_doc()); 754 869 self.pop(); 755 - offset.increment(8); 870 + offset.increment_constant(8); 756 871 } 757 872 758 873 Ok(()) ··· 774 889 775 890 self.push_bit_array_bit_size_check( 776 891 subject.clone(), 777 - offset.bits, 892 + offset.bits.clone(), 778 893 offset.tail_spread_type, 779 894 ); 780 895 Ok(()) ··· 819 934 .find(|x| matches!(x, Opt::Size { .. })) 820 935 { 821 936 Some(Opt::Size { value: size, .. }) => match &**size { 822 - Pattern::Int { value, .. } => Ok(value 823 - .parse::<usize>() 824 - .expect("part of an Int node should always parse as integer")), 937 + Pattern::Int { value, .. } => { 938 + Ok(BitArraySize::Literal(value.parse::<usize>().expect( 939 + "part of an Int node should always parse as integer", 940 + ))) 941 + } 942 + Pattern::VarUsage { name, .. } => Ok(BitArraySize::Variable(name.clone())), 825 943 _ => Err(Error::Unsupported { 826 944 feature: "Non-constant size option in patterns".into(), 827 945 location: segment.location, ··· 835 953 64usize 836 954 }; 837 955 838 - Ok(default_size) 956 + Ok(BitArraySize::Literal(default_size)) 839 957 } 840 958 }?; 841 959 842 960 // 16-bit floats are not supported 843 - if segment.type_ == crate::type_::float() && size == 16 { 961 + if segment.type_ == crate::type_::float() && size.is_literal_and(|size| size == 16) { 844 962 return Err(Error::Unsupported { 845 963 feature: "Float width of 16 bits in patterns".into(), 846 964 location: segment.location, ··· 916 1034 fn push_bit_array_bit_size_check( 917 1035 &mut self, 918 1036 subject: Document<'a>, 919 - expected_bit_size: usize, 1037 + expected_bit_size: OffsetBits, 920 1038 tail_spread_type: Option<BitArrayTailSpreadType>, 921 1039 ) { 922 1040 self.checks.push(Check::BitArrayBitSize { ··· 973 1091 }, 974 1092 BitArrayBitSize { 975 1093 subject: Document<'a>, 976 - expected_bit_size: usize, 1094 + expected_bit_size: OffsetBits, 977 1095 tail_spread_type: Option<BitArrayTailSpreadType>, 978 1096 }, 979 1097 StringPrefix { ··· 1067 1185 "(", 1068 1186 bit_size.clone(), 1069 1187 " >= ", 1070 - expected_bit_size, 1188 + expected_bit_size.clone(), 1071 1189 " && (", 1072 1190 bit_size, 1073 1191 " - ",
+16 -3
compiler-core/src/javascript/tests/bit_arrays.rs
··· 613 613 } 614 614 615 615 #[test] 616 - fn match_dynamic_size_error() { 617 - assert_js_error!( 616 + fn match_dynamic_size() { 617 + assert_js!( 618 618 r#" 619 619 fn go(x) { 620 620 let n = 16 621 621 let assert <<a:size(n)>> = x 622 + } 623 + "# 624 + ); 625 + } 626 + 627 + #[test] 628 + fn match_dynamic_size_with_other_segments() { 629 + assert_js!( 630 + r#" 631 + fn go(x) { 632 + let n = 16 633 + let m = 32 634 + let assert <<first:size(8), a:size(n), b:size(m), rest:bits>> = x 622 635 } 623 636 "# 624 637 ); ··· 937 950 "Gleam":utf8, 938 951 4.2:float, 939 952 4.2:32-float, 940 - <<0xFA>>:bits-6, 953 + <<0xFA>>:bits-6, 941 954 -1:64, 942 955 << 943 956 <<1, 2, 3>>:bits,
+2 -2
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__as_module_const.snap
··· 1 1 --- 2 2 source: compiler-core/src/javascript/tests/bit_arrays.rs 3 - expression: "\n pub const data = <<\n 0x1,\n 2,\n 2:size(16),\n 0x4:size(32),\n -1:32,\n \"Gleam\":utf8,\n 4.2:float,\n 4.2:32-float,\n <<0xFA>>:bits-6, \n -1:64,\n <<\n <<1, 2, 3>>:bits,\n \"Gleam\":utf8,\n 1024\n >>:bits\n >>\n " 3 + expression: "\n pub const data = <<\n 0x1,\n 2,\n 2:size(16),\n 0x4:size(32),\n -1:32,\n \"Gleam\":utf8,\n 4.2:float,\n 4.2:32-float,\n <<0xFA>>:bits-6,\n -1:64,\n <<\n <<1, 2, 3>>:bits,\n \"Gleam\":utf8,\n 1024\n >>:bits\n >>\n " 4 4 --- 5 5 ----- SOURCE CODE 6 6 ··· 13 13 "Gleam":utf8, 14 14 4.2:float, 15 15 4.2:32-float, 16 - <<0xFA>>:bits-6, 16 + <<0xFA>>:bits-6, 17 17 -1:64, 18 18 << 19 19 <<1, 2, 3>>:bits,
+30
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_dynamic_size.snap
··· 1 + --- 2 + source: compiler-core/src/javascript/tests/bit_arrays.rs 3 + expression: "\nfn go(x) {\n let n = 16\n let assert <<a:size(n)>> = x\n}\n" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + fn go(x) { 8 + let n = 16 9 + let assert <<a:size(n)>> = x 10 + } 11 + 12 + 13 + ----- COMPILED JAVASCRIPT 14 + import { makeError, bitArraySliceToInt } from "../gleam.mjs"; 15 + 16 + function go(x) { 17 + let n = 16; 18 + if (!(x.bitSize == n)) { 19 + throw makeError( 20 + "let_assert", 21 + "my/mod", 22 + 4, 23 + "go", 24 + "Pattern match failed, no pattern matched the value.", 25 + { value: x } 26 + ) 27 + } 28 + let a = bitArraySliceToInt(x, 0, n, true, false); 29 + return x; 30 + }
+35
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_dynamic_size_with_other_segments.snap
··· 1 + --- 2 + source: compiler-core/src/javascript/tests/bit_arrays.rs 3 + expression: "\nfn go(x) {\n let n = 16\n let m = 32\n let assert <<first:size(8), a:size(n), b:size(m), rest:bits>> = x\n}\n" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + fn go(x) { 8 + let n = 16 9 + let m = 32 10 + let assert <<first:size(8), a:size(n), b:size(m), rest:bits>> = x 11 + } 12 + 13 + 14 + ----- COMPILED JAVASCRIPT 15 + import { makeError, bitArraySlice, bitArraySliceToInt } from "../gleam.mjs"; 16 + 17 + function go(x) { 18 + let n = 16; 19 + let m = 32; 20 + if (!(x.bitSize >= n + m + 8)) { 21 + throw makeError( 22 + "let_assert", 23 + "my/mod", 24 + 5, 25 + "go", 26 + "Pattern match failed, no pattern matched the value.", 27 + { value: x } 28 + ) 29 + } 30 + let first = x.byteAt(0); 31 + let a = bitArraySliceToInt(x, 8, n + 8, true, false); 32 + let b = bitArraySliceToInt(x, n + 8, n + m + 8, true, false); 33 + let rest = bitArraySlice(x, n + m + 8); 34 + return x; 35 + }