Fork of daniellemaywood.uk/gleam — Wasm codegen work
14 kB
376 lines
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2021 The Gleam contributors
3
4use ecow::eco_format;
5
6use crate::analyse::Inferred;
7
8use super::*;
9
10pub(super) struct PatternPrinter<'a, 'env> {
11 pub environment: &'env mut Env<'a>,
12 pub variables: Vec<&'a str>,
13 pub guards: Vec<Document<'a>>,
14 /// In case we're dealing with string patterns, we might have something like
15 /// this: `"a" as letter <> rest`. In this case we want to compile it to
16 /// `<<"a"/utf8, rest/binary>>` and then bind a variable to `"a"`.
17 /// This way it's easier for the erlang compiler to optimise the pattern
18 /// matching.
19 ///
20 /// Here we store a list of gleam variable name to its name used in the
21 /// Erlang code and its literal value.
22 pub assignments: Vec<StringPatternAssignment<'a>>,
23}
24
25/// This is used to hold data about string patterns with an alias like:
26/// `"a" as letter <> _`
27pub struct StringPatternAssignment<'a> {
28 /// The name assigned to the pattern in the Gleam code:
29 ///
30 /// ```gleam
31 /// "a" as letter <> _
32 /// // ^^^^^^ This one
33 /// ```
34 ///
35 pub gleam_name: EcoString,
36 /// The name we're using for that same variable in the generated Erlang
37 /// code, could have numbers added to it to make sure it's unique, like
38 /// `Letter@1`.
39 ///
40 pub erlang_name: Document<'a>,
41 /// The document representing the literal value of that variable. For
42 /// example, if we had this pattern `"a" <> letter` it's literal value in
43 /// Erlang is going to be a document with the following string
44 /// `<<"a"/utf8>>`.
45 ///
46 pub literal_value: Document<'a>,
47}
48
49impl<'a> StringPatternAssignment<'a> {
50 pub fn to_assignment_doc(&self) -> Document<'a> {
51 docvec![self.erlang_name.clone(), " = ", self.literal_value.clone()]
52 }
53}
54
55impl<'a, 'env> PatternPrinter<'a, 'env> {
56 pub(super) fn new(environment: &'env mut Env<'a>) -> Self {
57 Self {
58 environment,
59 variables: vec![],
60 guards: vec![],
61 assignments: vec![],
62 }
63 }
64
65 pub(super) fn reset_variables(&mut self) {
66 self.variables = vec![];
67 }
68
69 pub(super) fn print(&mut self, pattern: &'a TypedPattern) -> Document<'a> {
70 match pattern {
71 Pattern::Assign { name, pattern, .. } => {
72 self.variables.push(name);
73 self.print(pattern)
74 .append(" = ")
75 .append(self.environment.next_local_var_name(name))
76 }
77
78 Pattern::List { elements, tail, .. } => self.pattern_list(elements, tail.as_deref()),
79
80 Pattern::Discard { .. } => "_".to_doc(),
81
82 Pattern::BitArraySize(size) => match size {
83 BitArraySize::Int { .. }
84 | BitArraySize::Variable { .. }
85 | BitArraySize::Block { .. } => self.bit_array_size(size),
86 BitArraySize::BinaryOperator { .. } => self.bit_array_size(size).surround("(", ")"),
87 },
88
89 Pattern::Variable { name, .. } => {
90 self.variables.push(name);
91 self.environment.next_local_var_name(name)
92 }
93
94 Pattern::Int { value, .. } => int(value),
95 Pattern::Float { value, .. } => float(value),
96 Pattern::String { value, .. } => string(value),
97
98 Pattern::Constructor {
99 arguments,
100 constructor: Inferred::Known(PatternConstructor { name, .. }),
101 ..
102 } => self.tag_tuple_pattern(name, arguments),
103
104 Pattern::Constructor {
105 constructor: Inferred::Unknown,
106 ..
107 } => {
108 panic!("Erlang generation performed with uninferred pattern constructor")
109 }
110
111 Pattern::Tuple { elements, .. } => {
112 tuple(elements.iter().map(|pattern| self.print(pattern)))
113 }
114
115 Pattern::BitArray { segments, .. } => bit_array(
116 segments
117 .iter()
118 .map(|s| self.pattern_segment(&s.value, &s.options)),
119 ),
120
121 Pattern::StringPrefix {
122 left_side_string,
123 right_side_assignment,
124 left_side_assignment,
125 ..
126 } => {
127 let right = match right_side_assignment {
128 AssignName::Variable(right) => {
129 self.variables.push(right);
130 self.environment.next_local_var_name(right)
131 }
132 AssignName::Discard(_) => "_".to_doc(),
133 };
134
135 if let Some((left_name, _)) = left_side_assignment {
136 // "wibble" as prefix <> rest
137 // ^^^^^^^^^ In case the left prefix of the pattern matching is given an alias
138 // we bind it to a local variable so that it can be correctly
139 // referenced inside the case branch.
140 //
141 // So we will end up with something that looks like this:
142 //
143 // <<"wibble"/binary, Rest/binary>> ->
144 // Prefix = "wibble",
145 // ...
146 //
147 self.variables.push(left_name);
148
149 self.assignments.push(StringPatternAssignment {
150 gleam_name: left_name.clone(),
151 erlang_name: self.environment.next_local_var_name(left_name),
152 literal_value: string(left_side_string),
153 });
154 }
155
156 docvec![
157 "<<\"",
158 string_inner(left_side_string),
159 "\"/utf8",
160 ", ",
161 right,
162 "/binary>>"
163 ]
164 }
165
166 Pattern::Invalid { .. } => panic!("invalid patterns should not reach code generation"),
167 }
168 }
169
170 fn bit_array_size(&mut self, size: &'a TypedBitArraySize) -> Document<'a> {
171 match size {
172 BitArraySize::Int { value, .. } => int(value),
173 BitArraySize::Block { inner, .. } => self.bit_array_size(inner).surround("(", ")"),
174 BitArraySize::Variable {
175 name, constructor, ..
176 } => {
177 let variant = &constructor
178 .as_ref()
179 .expect("Constructor not found for variable usage")
180 .variant;
181 match variant {
182 ValueConstructorVariant::ModuleConstant { literal, .. } => {
183 const_inline(literal, self.environment)
184 }
185 ValueConstructorVariant::LocalVariable { .. }
186 | ValueConstructorVariant::ModuleFn { .. }
187 | ValueConstructorVariant::Record { .. } => {
188 self.environment.local_var_name(name)
189 }
190 }
191 }
192 BitArraySize::BinaryOperator {
193 operator,
194 left,
195 right,
196 ..
197 } => {
198 let operator = match operator {
199 IntOperator::Add => " + ",
200 IntOperator::Subtract => " - ",
201 IntOperator::Multiply => " * ",
202 IntOperator::Divide => {
203 return self.bit_array_size_divide(left, right, "div");
204 }
205 IntOperator::Remainder => {
206 return self.bit_array_size_divide(left, right, "rem");
207 }
208 };
209
210 docvec![
211 self.bit_array_size(left),
212 operator,
213 self.bit_array_size(right)
214 ]
215 }
216 }
217 }
218
219 fn bit_array_size_divide(
220 &mut self,
221 left: &'a TypedBitArraySize,
222 right: &'a TypedBitArraySize,
223 operator: &'static str,
224 ) -> Document<'a> {
225 if right.non_zero_compile_time_number() {
226 return self.bit_array_size_operator(left, operator, right);
227 }
228
229 let left = self.bit_array_size(left);
230 let right = self.bit_array_size(right);
231 let denominator = self.environment.next_local_var_name("gleam@denominator");
232 let clauses = docvec![
233 line(),
234 "0 -> 0;",
235 line(),
236 denominator.clone(),
237 " -> ",
238 binop_documents(left, operator, denominator)
239 ];
240 docvec!["case ", right, " of", clauses.nest(INDENT), line(), "end"]
241 }
242
243 fn bit_array_size_operator(
244 &mut self,
245 left: &'a TypedBitArraySize,
246 operator: &'static str,
247 right: &'a TypedBitArraySize,
248 ) -> Document<'a> {
249 let left = if let BitArraySize::BinaryOperator { .. } = left {
250 self.bit_array_size(left).surround("(", ")")
251 } else {
252 self.bit_array_size(left)
253 };
254 let right = if let BitArraySize::BinaryOperator { .. } = right {
255 self.bit_array_size(right).surround("(", ")")
256 } else {
257 self.bit_array_size(right)
258 };
259 binop_documents(left, operator, right)
260 }
261
262 fn tag_tuple_pattern(
263 &mut self,
264 name: &'a str,
265 arguments: &'a [CallArg<TypedPattern>],
266 ) -> Document<'a> {
267 if arguments.is_empty() {
268 atom_string(to_snake_case(name))
269 } else {
270 tuple(
271 [atom_string(to_snake_case(name))]
272 .into_iter()
273 .chain(arguments.iter().map(|argument| self.print(&argument.value))),
274 )
275 }
276 }
277
278 fn pattern_list(
279 &mut self,
280 elements: &'a [TypedPattern],
281 tail: Option<&'a TypedTailPattern>,
282 ) -> Document<'a> {
283 let elements = join(
284 elements.iter().map(|element| self.print(element)),
285 break_(",", ", "),
286 );
287 let tail = tail.map(|tail| self.print(&tail.pattern));
288 list(elements, tail)
289 }
290
291 fn pattern_segment(
292 &mut self,
293 value: &'a TypedPattern,
294 options: &'a [BitArrayOption<TypedPattern>],
295 ) -> Document<'a> {
296 let pattern_is_a_string_literal = matches!(value, Pattern::String { .. });
297 let pattern_is_a_discard = matches!(value, Pattern::Discard { .. });
298
299 let create_document = |this: &mut PatternPrinter<'a, 'env>| match value {
300 Pattern::String { value, .. } => string_inner(value).surround("\"", "\""),
301 Pattern::Discard { .. }
302 | Pattern::Variable { .. }
303 | Pattern::Int { .. }
304 | Pattern::Float { .. } => this.print(value),
305
306 Pattern::Assign { name, pattern, .. } => {
307 this.variables.push(name);
308 let variable_name = this.environment.next_local_var_name(name);
309
310 match pattern.as_ref() {
311 // In Erlang, assignment patterns inside bit arrays are not allowed. So instead of
312 // generating `<<1 = A>>`, we use guards, and generate `<<A>> when A =:= 1`.
313 Pattern::Int { value, .. } => {
314 this.guards
315 .push(docvec![variable_name.clone(), " =:= ", int(value)]);
316 variable_name
317 }
318 Pattern::Float { value, .. } => {
319 this.guards
320 .push(docvec![variable_name.clone(), " =:= ", float(value)]);
321 variable_name
322 }
323
324 // Here we do the same as for floats and ints, but we must calculate the size of
325 // the string first, so we can correctly match the bit array segment then compare
326 // it afterwards.
327 Pattern::String { value, .. } => {
328 this.guards
329 .push(docvec![variable_name.clone(), " =:= ", string(value)]);
330 docvec![variable_name, ":", string_length_utf8_bytes(value)]
331 }
332
333 // Doing a pattern such as `<<_ as a>>` is the same as just `<<a>>`, so we treat it
334 // as such.
335 Pattern::Discard { .. } => variable_name,
336
337 // Any other pattern is invalid as a bit array segment. We already handle the case
338 // of `<<a as b>>` in the type-checker, and assignment patterns cannot be nested.
339 Pattern::Variable { .. }
340 | Pattern::BitArraySize(_)
341 | Pattern::Assign { .. }
342 | Pattern::List { .. }
343 | Pattern::Constructor { .. }
344 | Pattern::Tuple { .. }
345 | Pattern::BitArray { .. }
346 | Pattern::StringPrefix { .. }
347 | Pattern::Invalid { .. } => panic!("Pattern segment match not recognised"),
348 }
349 }
350
351 Pattern::BitArraySize(_)
352 | Pattern::List { .. }
353 | Pattern::Constructor { .. }
354 | Pattern::Tuple { .. }
355 | Pattern::BitArray { .. }
356 | Pattern::StringPrefix { .. }
357 | Pattern::Invalid { .. } => panic!("Pattern segment match not recognised"),
358 };
359
360 let size = |value: &'a TypedPattern, this: &mut PatternPrinter<'a, 'env>| {
361 Some(":".to_doc().append(this.print(value)))
362 };
363
364 let unit = |value: &'a u8| Some(eco_format!("unit:{value}").to_doc());
365
366 bit_array_segment(
367 create_document,
368 options,
369 size,
370 unit,
371 pattern_is_a_string_literal,
372 pattern_is_a_discard,
373 self,
374 )
375 }
376}