Fork of daniellemaywood.uk/gleam — Wasm codegen work
16 kB
429 lines
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2021 The Gleam contributors
3
4use erlang_generation::BitArraySegmentSpecifier;
5
6use crate::{analyse::Inferred, parse::LiteralFloatValue};
7
8use super::*;
9
10/// This is used to generate the code for a pattern.
11/// Most Gleam patterns can be translated to Erlang in a pretty straightforward
12/// way but there's notable exceptions that require some extra bookeping, this
13/// helps with that.
14pub(super) struct PatternGenerator<'a, 'generator, 'module> {
15 pub generator: &'generator mut FunctionGenerator<'a, 'module>,
16
17 /// Not all Gleam patterns can be cleanly (or efficiently!) translated to
18 /// Erlang ones. In particular, we allow aliasing almost all patterns like:
19 ///
20 /// ```gleam
21 /// "a" as letter <> _ -> todo
22 /// // ^^^^^^^^^ This...
23 /// <<1 as number, _:bits>> -> todo
24 /// // ^^^^^^^^^ ...or this!
25 /// ```
26 ///
27 /// In those cases we generate a pattern matching on the literal value and
28 /// keep track of the fact we'll have to define such variable in the
29 /// following case branch.
30 ///
31 /// This map maps the name of those Gleam variables that have been
32 /// introduced with an alias to their constant value and position.
33 /// You can check the docs of `AliasedValue` for some more examples and a
34 /// more in depth explanation.
35 pub variables_to_add_later: HashMap<EcoString, AliasedLiteral>,
36}
37
38/// This is used to hold data about string prefix pattern with an alias like:
39/// `"a" as letter <> _`.
40///
41/// This pattern cannot be easily translated to Erlang since it doesn't allow to
42/// write something like this in a bitstring: `<<"a" = Letter, _:bits>>`.
43/// So what the generator will do is it will generate the following simpler
44/// pattern:
45///
46/// ```erl
47/// <<"a", _:bits>>
48/// % ^^^ Notice how this isn't bound to a `Letter` variable
49/// ```
50///
51/// And it will return this data structure so we can then generate the needed
52/// variable assignment later in the case body. So, overall, this:
53///
54/// ```gleam
55/// "a" as letter <> _ -> ...
56/// ```
57///
58/// Will become:
59///
60/// ```erl
61/// <<"a", _:bits>> ->
62/// Letter = "a",
63/// ...
64/// ```
65///
66/// > Note: We could have also generated slightly different code, where we use
67/// > a guard `<<Letter, _:bits>> when Letter =:= "a"`. That would mean we don't
68/// > have to add that additional variable binding; the problem is that the
69/// > Erlang compiler doesn't seem to be able to optimise that as well as the
70/// > one with the literal value in the pattern!
71///
72#[derive(Debug)]
73pub enum AliasedLiteral {
74 String {
75 /// The location of the name given to the alias:
76 ///
77 /// ```gleam
78 /// "a" as letter <> _
79 /// // ^^^^^^ This span here
80 ///
81 /// <<"a" as letter>>
82 /// // ^^^^^^ or, if we're dealing with bit arrays this span here
83 /// ```
84 ///
85 location: SrcSpan,
86
87 /// This is the content of the literal string.
88 ///
89 /// ```gleam
90 /// "książka" as word <> _
91 /// // ^^^^^^^ This right here
92 /// ```
93 ///
94 value: EcoString,
95 },
96 Int {
97 /// The location of the name given to the alias:
98 ///
99 /// ```gleam
100 /// <<1 as digit>>
101 /// // ^^^^^ This span here
102 /// ```
103 location: SrcSpan,
104
105 /// The value of the literal int being aliased.
106 value: BigInt,
107 },
108 Float {
109 /// The location of the name given to the alias:
110 ///
111 /// ```gleam
112 /// <<1.1 as number>>
113 /// // ^^^^^^ This span here
114 /// ```
115 location: SrcSpan,
116
117 /// The value of the literal float being aliased.
118 value: LiteralFloatValue,
119 },
120}
121
122impl<'a, 'generator, 'module> PatternGenerator<'a, 'generator, 'module> {
123 pub(super) fn new(generator: &'generator mut FunctionGenerator<'a, 'module>) -> Self {
124 Self {
125 generator,
126 variables_to_add_later: HashMap::new(),
127 }
128 }
129
130 pub(super) fn pattern<Output>(
131 &mut self,
132 builder: &mut impl ErlangBuilder<Output>,
133 pattern: &'a TypedPattern,
134 ) {
135 match pattern {
136 Pattern::Discard { .. } => builder.discard_pattern(),
137 Pattern::Float { float_value, .. } => builder.float_pattern(float_value.value()),
138 Pattern::Int { int_value, .. } => builder.int_pattern(int_value.clone()),
139 Pattern::String { value, .. } => builder.string_pattern(value),
140 Pattern::Variable { name, location, .. } => {
141 builder.variable_pattern(&self.generator.new_erlang_variable(name, *location))
142 }
143
144 Pattern::Assign {
145 name,
146 pattern,
147 location,
148 } => {
149 builder.match_pattern();
150 self.pattern(builder, pattern);
151 builder.variable_pattern(&self.generator.new_erlang_variable(name, *location));
152 }
153
154 Pattern::Tuple { elements, .. } => {
155 let tuple = builder.start_tuple_pattern();
156 for element in elements {
157 self.pattern(builder, element);
158 }
159 builder.end_tuple_pattern(tuple);
160 }
161
162 Pattern::List { elements, tail, .. } => {
163 for element in elements {
164 builder.cons_list_pattern();
165 self.pattern(builder, element);
166 }
167 if let Some(tail) = tail {
168 self.pattern(builder, &tail.pattern);
169 } else {
170 builder.empty_list_pattern();
171 }
172 }
173
174 Pattern::Constructor {
175 arguments,
176 constructor,
177 ..
178 } => {
179 let Inferred::Known(PatternConstructor { name, .. }) = constructor else {
180 panic!("uninferred constructor made it to codegen ")
181 };
182
183 if arguments.is_empty() {
184 builder.atom_pattern(&to_snake_case(name));
185 } else {
186 let tuple = builder.start_tuple_pattern();
187 builder.atom_pattern(&to_snake_case(name));
188 for argument in arguments {
189 self.pattern(builder, &argument.value);
190 }
191 builder.end_tuple_pattern(tuple);
192 }
193 }
194
195 Pattern::StringPrefix {
196 left_side_string,
197 left_side_assignment,
198 right_side_assignment,
199 right_location,
200 ..
201 } => {
202 // If the constant string prefix is being aliased we need to add
203 // that value to the variables that are going to be generated
204 // later:
205 if let Some((prefix_name, prefix_location)) = left_side_assignment {
206 let _ = self.variables_to_add_later.insert(
207 prefix_name.clone(),
208 AliasedLiteral::String {
209 location: *prefix_location,
210 value: left_side_string.clone(),
211 },
212 );
213 }
214
215 let bit_array = builder.start_bit_array_pattern();
216
217 // We first generate a segment matching on the literal prefix.
218 builder.bit_array_segment();
219 builder.string_pattern(left_side_string);
220 builder.atom("deafult");
221 builder.bit_array_segment_specifiers([BitArraySegmentSpecifier::Utf8]);
222
223 // We then add a segment matching on the rest of the string.
224 builder.bit_array_segment();
225 match right_side_assignment {
226 AssignName::Variable(name) => builder.variable_pattern(
227 &self.generator.new_erlang_variable(name, *right_location),
228 ),
229 AssignName::Discard(_) => builder.discard_pattern(),
230 }
231 builder.atom("default");
232 builder.bit_array_segment_specifiers([BitArraySegmentSpecifier::Binary]);
233
234 builder.end_bit_array_pattern(bit_array);
235 }
236
237 Pattern::BitArray { segments, .. } => {
238 let bit_array = builder.start_bit_array_pattern();
239 for segment in segments {
240 builder.bit_array_segment();
241 self.bit_array_pattern_segment_value(builder, segment);
242 self.bit_array_pattern_segment_size(builder, segment);
243 self.generator
244 .bit_array_segment_specifiers(builder, segment);
245 }
246 builder.end_bit_array_pattern(bit_array);
247 }
248
249 Pattern::BitArraySize(size) => self.bit_array_size(builder, size),
250
251 Pattern::Invalid { .. } => {
252 panic!("invalid patterns should not reach code generation")
253 }
254 }
255 }
256
257 fn bit_array_size<Output>(
258 &mut self,
259 builder: &mut impl ErlangBuilder<Output>,
260 size: &'a TypedBitArraySize,
261 ) {
262 match size {
263 BitArraySize::Int { int_value, .. } => builder.int(int_value.clone()),
264 BitArraySize::Block { inner, .. } => self.bit_array_size(builder, inner),
265
266 BitArraySize::Variable {
267 constructor, name, ..
268 } => match self.variables_to_add_later.get(name) {
269 Some(AliasedLiteral::Int { value, .. }) => builder.int(value.clone()),
270 Some(_) => panic!("segment size that is not int made it through type checking"),
271 None => {
272 let constructor = constructor.as_ref().expect("variable with no constructor");
273 match &constructor.variant {
274 ValueConstructorVariant::ModuleConstant { literal, .. } => {
275 self.generator.inlined_constant(builder, literal)
276 }
277 ValueConstructorVariant::LocalVariable { location, .. } => {
278 builder.variable(&self.generator.local_var_name(location))
279 }
280 ValueConstructorVariant::ModuleFn { .. }
281 | ValueConstructorVariant::Record { .. } => panic!("invalid segment"),
282 }
283 }
284 },
285
286 BitArraySize::BinaryOperator {
287 operator,
288 left,
289 right,
290 ..
291 } => {
292 let operator = match operator {
293 IntOperator::Add => "+",
294 IntOperator::Subtract => "-",
295 IntOperator::Multiply => "*",
296 IntOperator::Divide => {
297 return self.bit_array_size_divide(builder, left, right, "div");
298 }
299 IntOperator::Remainder => {
300 return self.bit_array_size_divide(builder, left, right, "rem");
301 }
302 };
303 builder.binary_operator(operator);
304 self.bit_array_size(builder, left);
305 self.bit_array_size(builder, right);
306 }
307 }
308 }
309
310 fn bit_array_pattern_segment_value<Output>(
311 &mut self,
312 builder: &mut impl ErlangBuilder<Output>,
313 segment: &'a TypedPatternBitArraySegment,
314 ) {
315 let Pattern::Assign {
316 name,
317 location,
318 pattern,
319 } = segment.value.as_ref()
320 else {
321 // If the pattern is not an assign, it needs no extra care, we can
322 // just produce the code for such pattern!
323 self.pattern(builder, &segment.value);
324 return;
325 };
326
327 // But if we're dealing with an assign pattern inside a bit array
328 // segment we have to give it the same treatment we reserve for string
329 // prefixes (after all those are aliased bit array patterns too, since
330 // strings are just bitstrings!).
331 //
332 // After reading the docs for those you might already be familiar with
333 // the problem. But it's still worth going over that too one more time.
334 // In Gleam we can write `<<1 as a, _:bits>>` but in Erlang we can't
335 // produce the following pattern: `<<1 = A, _:bits>>`.
336 // So what we will do is match on the literal value and keep track of
337 // the constant value we'll have to add into scope later.
338 let aliased_value = match pattern.as_ref() {
339 Pattern::Int { int_value, .. } => AliasedLiteral::Int {
340 location: *location,
341 value: int_value.clone(),
342 },
343
344 Pattern::Float { float_value, .. } => AliasedLiteral::Float {
345 location: *location,
346 value: *float_value,
347 },
348 Pattern::String { value, .. } => AliasedLiteral::String {
349 location: *location,
350 value: value.clone(),
351 },
352
353 // Aliasing a discard is the same as just producing a variable
354 // pattern, that makes things even simpler, we can just produce the
355 // code for a variable pattern with the wanted name and call it a day
356 Pattern::Discard { .. } => {
357 builder.variable_pattern(&self.generator.new_erlang_variable(name, *location));
358 return;
359 }
360
361 Pattern::Variable { .. }
362 | Pattern::BitArraySize(_)
363 | Pattern::Assign { .. }
364 | Pattern::List { .. }
365 | Pattern::Constructor { .. }
366 | Pattern::Tuple { .. }
367 | Pattern::BitArray { .. }
368 | Pattern::StringPrefix { .. }
369 | Pattern::Invalid { .. } => {
370 panic!("invalid pattern inside aliased bit array pattern segment")
371 }
372 };
373
374 let _ = self
375 .variables_to_add_later
376 .insert(name.clone(), aliased_value);
377 self.pattern(builder, pattern);
378 }
379
380 fn bit_array_pattern_segment_size<Output>(
381 &mut self,
382 builder: &mut impl ErlangBuilder<Output>,
383 segment: &'a TypedPatternBitArraySegment,
384 ) {
385 let Some(size) = segment.size() else {
386 builder.atom("default");
387 return;
388 };
389 let TypedPattern::BitArraySize(size) = size else {
390 panic!("invalid size in pattern size segment")
391 };
392 self.bit_array_size(builder, size);
393 }
394
395 fn bit_array_size_divide<Output>(
396 &mut self,
397 builder: &mut impl ErlangBuilder<Output>,
398 left: &'a TypedBitArraySize,
399 right: &'a TypedBitArraySize,
400 operator: &'static str,
401 ) {
402 if right.non_zero_compile_time_number() {
403 builder.binary_operator(operator);
404 self.bit_array_size(builder, left);
405 self.bit_array_size(builder, right);
406 } else {
407 let case = builder.start_case();
408 self.bit_array_size(builder, right);
409
410 let clause = builder.start_case_clause();
411 builder.int_pattern(BigInt::ZERO);
412 let clause = builder.end_clause_pattern(clause);
413 let clause = builder.end_clause_guards(clause);
414 builder.int(BigInt::ZERO);
415 builder.end_clause_body(clause);
416
417 let clause = builder.start_case_clause();
418 let denominator = self.generator.new_throwaway_variable();
419 builder.variable_pattern(&denominator);
420 let clause = builder.end_clause_pattern(clause);
421 let clause = builder.end_clause_guards(clause);
422 builder.binary_operator(operator);
423 self.bit_array_size(builder, left);
424 builder.variable(&denominator);
425 builder.end_clause_body(clause);
426 builder.end_case(case);
427 }
428 }
429}