Fork of daniellemaywood.uk/gleam — Wasm codegen work
9.3 kB
284 lines
1use std::cell::RefCell;
2
3use ecow::eco_format;
4
5use crate::analyse::Inferred;
6
7use super::*;
8
9pub(super) fn pattern<'a>(
10 p: &'a TypedPattern,
11 env: &mut Env<'a>,
12 guards: &mut Vec<Document<'a>>,
13) -> Document<'a> {
14 let mut vars = vec![];
15 to_doc(p, &mut vars, env, guards)
16}
17
18fn print<'a>(
19 p: &'a TypedPattern,
20 vars: &mut Vec<&'a str>,
21 env: &mut Env<'a>,
22 guards: &mut Vec<Document<'a>>,
23) -> Document<'a> {
24 match p {
25 Pattern::Assign {
26 name, pattern: p, ..
27 } => {
28 vars.push(name);
29 print(p, vars, env, guards)
30 .append(" = ")
31 .append(env.next_local_var_name(name))
32 }
33
34 Pattern::List { elements, tail, .. } => {
35 pattern_list(elements, tail.as_deref(), vars, env, guards)
36 }
37
38 Pattern::Discard { .. } => "_".to_doc(),
39
40 Pattern::BitArraySize(size) => bit_array_size(size, env),
41
42 Pattern::Variable { name, .. } => {
43 vars.push(name);
44 env.next_local_var_name(name)
45 }
46
47 Pattern::Int { value, .. } => int(value),
48
49 Pattern::Float { value, .. } => float(value),
50
51 Pattern::String { value, .. } => string(value),
52
53 Pattern::Constructor {
54 arguments: args,
55 constructor: Inferred::Known(PatternConstructor { name, .. }),
56 ..
57 } => tag_tuple_pattern(name, args, vars, env, guards),
58
59 Pattern::Constructor {
60 constructor: Inferred::Unknown,
61 ..
62 } => {
63 panic!("Erlang generation performed with uninferred pattern constructor")
64 }
65
66 Pattern::Tuple { elements, .. } => {
67 tuple(elements.iter().map(|p| print(p, vars, env, guards)))
68 }
69
70 Pattern::BitArray { segments, .. } => bit_array(
71 segments
72 .iter()
73 .map(|s| pattern_segment(&s.value, &s.options, vars, env, guards)),
74 ),
75
76 Pattern::StringPrefix {
77 left_side_string,
78 right_side_assignment,
79 left_side_assignment,
80 ..
81 } => {
82 let right = match right_side_assignment {
83 AssignName::Variable(right) => {
84 vars.push(right);
85 env.next_local_var_name(right)
86 }
87 AssignName::Discard(_) => "_".to_doc(),
88 };
89
90 match left_side_assignment {
91 Some((left_name, _)) => {
92 // "wibble" as prefix <> rest
93 // ^^^^^^^^^ In case the left prefix of the pattern matching is given an alias
94 // we bind it to a local variable so that it can be correctly
95 // referenced inside the case branch.
96 //
97 // <<Prefix:3/binary, Rest/binary>> when Prefix =:= <<"wibble">>
98 // ^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
99 // since erlang's binary pattern matching doesn't allow direct string assignment
100 // to variables within the pattern, we first match the expected prefix length in
101 // bytes, then use a guard clause to verify the content.
102 //
103 vars.push(left_name);
104 let name = env.next_local_var_name(left_name);
105 guards.push(docvec![name.clone(), " =:= ", string(left_side_string)]);
106 docvec![
107 "<<",
108 name.clone(),
109 ":",
110 string_length_utf8_bytes(left_side_string),
111 "/binary",
112 ", ",
113 right,
114 "/binary>>",
115 ]
116 }
117 None => docvec![
118 "<<\"",
119 string_inner(left_side_string),
120 "\"/utf8",
121 ", ",
122 right,
123 "/binary>>"
124 ],
125 }
126 }
127
128 Pattern::Invalid { .. } => panic!("invalid patterns should not reach code generation"),
129 }
130}
131
132fn bit_array_size<'a>(size: &'a TypedBitArraySize, env: &mut Env<'a>) -> Document<'a> {
133 match size {
134 BitArraySize::Int { value, .. } => int(value),
135
136 BitArraySize::Variable {
137 name, constructor, ..
138 } => {
139 let v = &constructor
140 .as_ref()
141 .expect("Constructor not found for variable usage")
142 .variant;
143 match v {
144 ValueConstructorVariant::ModuleConstant { literal, .. } => {
145 const_inline(literal, env)
146 }
147 _ => env.local_var_name(name),
148 }
149 }
150 }
151}
152
153pub(super) fn to_doc<'a>(
154 p: &'a TypedPattern,
155 vars: &mut Vec<&'a str>,
156 env: &mut Env<'a>,
157 guards: &mut Vec<Document<'a>>,
158) -> Document<'a> {
159 print(p, vars, env, guards)
160}
161
162fn tag_tuple_pattern<'a>(
163 name: &'a str,
164 args: &'a [CallArg<TypedPattern>],
165 vars: &mut Vec<&'a str>,
166 env: &mut Env<'a>,
167 guards: &mut Vec<Document<'a>>,
168) -> Document<'a> {
169 if args.is_empty() {
170 atom_string(to_snake_case(name))
171 } else {
172 tuple(
173 [atom_string(to_snake_case(name))]
174 .into_iter()
175 .chain(args.iter().map(|p| print(&p.value, vars, env, guards))),
176 )
177 }
178}
179
180fn pattern_segment<'a>(
181 value: &'a TypedPattern,
182 options: &'a [BitArrayOption<TypedPattern>],
183 vars: &mut Vec<&'a str>,
184 env: &mut Env<'a>,
185 guards: &mut Vec<Document<'a>>,
186) -> Document<'a> {
187 let pattern_is_a_string_literal = matches!(value, Pattern::String { .. });
188 let pattern_is_a_discard = matches!(value, Pattern::Discard { .. });
189
190 let vars = RefCell::new(vars);
191 let guards = RefCell::new(guards);
192
193 let create_document = |env: &mut Env<'a>| match value {
194 Pattern::String { value, .. } => value.to_doc().surround("\"", "\""),
195 Pattern::Discard { .. }
196 | Pattern::Variable { .. }
197 | Pattern::Int { .. }
198 | Pattern::Float { .. } => {
199 print(value, &mut vars.borrow_mut(), env, &mut guards.borrow_mut())
200 }
201
202 Pattern::Assign { name, pattern, .. } => {
203 vars.borrow_mut().push(name);
204 let variable_name = env.next_local_var_name(name);
205
206 match pattern.as_ref() {
207 // In Erlang, assignment patterns inside bit arrays are not allowed. So instead of
208 // generating `<<1 = A>>`, we use guards, and generate `<<A>> when A =:= 1`.
209 Pattern::Int { value, .. } => {
210 guards
211 .borrow_mut()
212 .push(docvec![variable_name.clone(), " =:= ", int(value)]);
213 variable_name
214 }
215 Pattern::Float { value, .. } => {
216 guards
217 .borrow_mut()
218 .push(docvec![variable_name.clone(), " =:= ", float(value)]);
219 variable_name
220 }
221
222 // Here we do the same as for floats and ints, but we must calculate the size of
223 // the string first, so we can correctly match the bit array segment then compare
224 // it afterwards.
225 Pattern::String { value, .. } => {
226 guards.borrow_mut().push(docvec![
227 variable_name.clone(),
228 " =:= ",
229 string(value)
230 ]);
231 docvec![variable_name, ":", string_length_utf8_bytes(value)]
232 }
233
234 // Doing a pattern such as `<<_ as a>>` is the same as just `<<a>>`, so we treat it
235 // as such.
236 Pattern::Discard { .. } => variable_name,
237
238 // Any other pattern is invalid as a bit array segment. We already handle the case
239 // of `<<a as b>>` in the type-checker, and assignment patterns cannot be nested.
240 _ => panic!("Pattern segment match not recognised"),
241 }
242 }
243
244 _ => panic!("Pattern segment match not recognised"),
245 };
246
247 let size = |value: &'a TypedPattern, env: &mut Env<'a>| {
248 Some(":".to_doc().append(print(
249 value,
250 &mut vars.borrow_mut(),
251 env,
252 &mut guards.borrow_mut(),
253 )))
254 };
255
256 let unit = |value: &'a u8| Some(eco_format!("unit:{value}").to_doc());
257
258 bit_array_segment(
259 create_document,
260 options,
261 size,
262 unit,
263 pattern_is_a_string_literal,
264 pattern_is_a_discard,
265 env,
266 )
267}
268
269fn pattern_list<'a>(
270 elements: &'a [TypedPattern],
271 tail: Option<&'a TypedPattern>,
272 vars: &mut Vec<&'a str>,
273 env: &mut Env<'a>,
274 guards: &mut Vec<Document<'a>>,
275) -> Document<'a> {
276 let elements = join(
277 elements
278 .iter()
279 .map(|element| print(element, vars, env, guards)),
280 break_(",", ", "),
281 );
282 let tail = tail.map(|tail| print(tail, vars, env, guards));
283 list(elements, tail)
284}