Fork of daniellemaywood.uk/gleam — Wasm codegen work
15 kB
387 lines
1use super::{
2 Output,
3 expression::{Generator, Ordering},
4};
5use crate::{
6 ast::{TypedClauseGuard, TypedExpr},
7 docvec,
8 exhaustiveness::{
9 Body, BoundValue, CompiledCase, Decision, FallbackCheck, RuntimeCheck, Variable,
10 },
11 format::break_block,
12 javascript::expression::{string, string_from_eco},
13 pretty::{Document, Documentable, join, line, nil},
14 strings::convert_string_escape_chars,
15};
16use ecow::{EcoString, eco_format};
17use std::{collections::HashMap, sync::OnceLock};
18
19pub static ASSIGNMENT_VAR: &str = "$";
20
21pub fn print<'a, 'generator, 'module>(
22 compiled_case: &'a CompiledCase,
23 clauses: Vec<(&'a TypedExpr, Option<&'a TypedClauseGuard>)>,
24 subjects: &'a [TypedExpr],
25 expression_generator: &'generator mut Generator<'module, 'a>,
26) -> Output<'a> {
27 // The case subjects might be repeated in the generated code, so we want to
28 // assign those to variables (if they're not already ones) and use those;
29 // otherwise we'd end up calling the same functions multiple times, which
30 // would change the program's meaning!
31 let subjects_assignments = assign_subjects(expression_generator, subjects);
32
33 let mut printer = DecisionPrinter {
34 clauses,
35 expression_generator,
36 variable_values: HashMap::new(),
37 };
38
39 // Might have to add those to the scope!!!!
40 for (var, (subject_value, _assignment)) in compiled_case
41 .subject_variables
42 .iter()
43 .zip(subjects_assignments.iter())
44 {
45 printer.set_pattern_variable_value(var, subject_value.clone());
46 }
47
48 let decision = printer.decision(&compiled_case.tree)?;
49
50 // Then if there's any assignment we write those before the generated
51 // decision tree.
52 let mut subject_assignments_docs = vec![];
53 for ((_, assignment), subject) in subjects_assignments.into_iter().zip(subjects) {
54 let Some(var) = assignment else { continue };
55 let value = expression_generator
56 .not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(subject))?;
57 subject_assignments_docs.push(let_(var, value).append(line()))
58 }
59
60 Ok(docvec![subject_assignments_docs, decision].force_break())
61}
62
63pub struct DecisionPrinter<'module, 'generator, 'a> {
64 clauses: Vec<(&'a TypedExpr, Option<&'a TypedClauseGuard>)>,
65 expression_generator: &'generator mut Generator<'module, 'a>,
66
67 /// All the pattern variables will be assigned a specific value: being bound
68 /// to a constructor field, tuple element and so on. Pattern variables never
69 /// end up in the generated code but we replace them with their actual value.
70 /// We store those values as we find them in this map.
71 variable_values: HashMap<usize, EcoString>,
72}
73
74impl<'module, 'generator, 'a> DecisionPrinter<'module, 'generator, 'a> {
75 fn set_pattern_variable_value(&mut self, variable: &Variable, value: EcoString) {
76 let _ = self.variable_values.insert(variable.id, value);
77 }
78
79 fn get_variable_value(&self, variable: &Variable) -> EcoString {
80 self.variable_values
81 .get(&variable.id)
82 .expect("pattern variable used before assignment")
83 .clone()
84 }
85
86 fn decision(&mut self, decision: &'a Decision) -> Output<'a> {
87 match decision {
88 Decision::Fail => unreachable!("Invalid decision tree reached code generation"),
89 Decision::Run { body } => {
90 let bindings = self.bindings(&body.bindings);
91 let body = self.body_expression(body.clause_index)?;
92 Ok(join_with_line(bindings, body))
93 }
94 Decision::Switch {
95 var,
96 choices,
97 fallback,
98 fallback_check,
99 } => self.switch(var, choices, fallback, fallback_check),
100 Decision::Guard {
101 guard,
102 if_true,
103 if_false,
104 } => self.decision_guard(*guard, if_true, if_false),
105 }
106 }
107
108 fn bindings(&mut self, bindings: &'a Vec<(EcoString, BoundValue)>) -> Document<'a> {
109 let bindings = (bindings.iter()).map(|(variable, value)| self.binding(variable, value));
110 join(bindings, line())
111 }
112
113 fn bindings_ref(&mut self, bindings: &Vec<&'a (EcoString, BoundValue)>) -> Document<'a> {
114 let bindings = (bindings.iter()).map(|(variable, value)| self.binding(variable, value));
115 join(bindings, line())
116 }
117
118 fn body_expression(&mut self, clause_index: usize) -> Output<'a> {
119 let (body, _) = &self
120 .clauses
121 .get(clause_index)
122 .expect("decision tree invalid clause index");
123
124 self.expression_generator.expression_flattening_blocks(body)
125 }
126
127 fn binding(&mut self, variable_name: &'a EcoString, value: &'a BoundValue) -> Document<'a> {
128 let variable_name = self.expression_generator.next_local_var(variable_name);
129 let assigned_value = match value {
130 BoundValue::Variable(variable) => self.get_variable_value(variable).to_doc(),
131 BoundValue::LiteralString(value) => string(value),
132 };
133 let_(variable_name.clone(), assigned_value)
134 }
135
136 fn switch(
137 &mut self,
138 var: &'a Variable,
139 choices: &'a [(RuntimeCheck, Box<Decision>)],
140 fallback: &'a Decision,
141 fallback_check: &'a FallbackCheck,
142 ) -> Output<'a> {
143 match choices {
144 // If there's just a single choice we can just generate the code for
145 // it: no need to do any checking, we know it must match!
146 [] => {
147 if let FallbackCheck::RuntimeCheck { check } = fallback_check {
148 self.record_check_assignments(var, check);
149 }
150 self.decision(fallback)
151 }
152 _ => {
153 let mut first = true;
154 let mut if_ = nil();
155 for (check, decision) in choices {
156 self.record_check_assignments(var, check);
157 let body = self.inside_new_scope(|this| this.decision(decision))?;
158 let check_doc = self.runtime_check(var, check);
159 let branch = if first {
160 first = false;
161 docvec!["if (", check_doc, ") "]
162 } else {
163 docvec![" else if (", check_doc, ") "]
164 };
165
166 if_ = if_.append(docvec![branch, break_block(body)]);
167 }
168
169 // In case there's some new variables we can extract after the
170 // successful check we store those. But we don't need to perform
171 // the check itself: the type system makes sure that, if we ever
172 // get here, the check is going to match no matter what!
173 if let FallbackCheck::RuntimeCheck { check } = fallback_check {
174 self.record_check_assignments(var, check);
175 }
176
177 let body = self.inside_new_scope(|this| this.decision(fallback))?;
178 if body.is_empty() {
179 Ok(if_)
180 } else {
181 let else_ = docvec![" else ", break_block(body)];
182 Ok(docvec![if_, else_])
183 }
184 }
185 }
186 }
187
188 fn inside_new_scope<A, F>(&mut self, run: F) -> A
189 where
190 F: Fn(&mut Self) -> A,
191 {
192 let old_scope = self.expression_generator.current_scope_vars.clone();
193 let output = run(self);
194 self.expression_generator.current_scope_vars = old_scope;
195 output
196 }
197
198 fn decision_guard(
199 &mut self,
200 guard: usize,
201 if_true: &'a Body,
202 if_false: &'a Decision,
203 ) -> Output<'a> {
204 let guard = self
205 .clauses
206 .get(guard)
207 .expect("invalid clause index")
208 .1
209 .expect("missing guard should");
210
211 // Before generating the if-else condition we want to generate all the
212 // assignments that will be needed by the guard condition so we can rest
213 // assured they are in scope for the if condition to use those.
214 let guard_variables = guard.referenced_variables();
215 let (check_bindings, if_true_bindings): (Vec<_>, Vec<_>) = if_true
216 .bindings
217 .iter()
218 .partition(|(variable, _)| guard_variables.contains(variable));
219
220 let check_bindings = self.bindings_ref(&check_bindings);
221 let check = self.expression_generator.guard(guard)?;
222 let if_true = self.inside_new_scope(|this| {
223 // All the other bindings are not needed by the guard check will
224 // end up directly in the body of the if clause to avoid doing any
225 // extra work before making sure the condition is true and they are
226 // actually needed.
227 let if_true_bindings = this.bindings_ref(&if_true_bindings);
228 let if_true_body = this.body_expression(if_true.clause_index)?;
229 Ok(join_with_line(if_true_bindings, if_true_body))
230 })?;
231 let if_false = self.inside_new_scope(|this| this.decision(if_false))?;
232
233 // We can now piece everything together into a single document!
234 let if_ = docvec!["if (", check, ") ", break_block(if_true)];
235 let if_ = join_with_line(check_bindings, if_);
236 if if_false.is_empty() {
237 Ok(docvec![if_])
238 } else {
239 let else_ = docvec![" else ", break_block(if_false)];
240 Ok(docvec![if_, else_])
241 }
242 }
243
244 fn runtime_check(&mut self, var: &Variable, runtime_check: &RuntimeCheck) -> Document<'a> {
245 let var_value = self.get_variable_value(var);
246 match runtime_check {
247 RuntimeCheck::String { value } => {
248 docvec![var_value, " === ", string_from_eco(value.clone())]
249 }
250
251 RuntimeCheck::Int { value } | RuntimeCheck::Float { value } => {
252 docvec![var_value, " === ", value]
253 }
254
255 RuntimeCheck::StringPrefix { prefix, .. } => docvec![
256 var_value,
257 ".startsWith(",
258 string_from_eco(prefix.clone()),
259 ")"
260 ],
261
262 RuntimeCheck::BitArray { value } => docvec!["TODO"],
263
264 // When checking on a tuple there's always going to be a single choice
265 // and the code generation will always skip generating the check for it
266 // as the type system ensures it must match.
267 RuntimeCheck::Tuple { .. } => unreachable!("tried generating runtime check for tuple"),
268
269 RuntimeCheck::Variant { match_, .. } if var.type_.is_bool() => {
270 if match_.name() == "True" {
271 var_value.to_doc()
272 } else {
273 docvec!["!", var_value]
274 }
275 }
276
277 RuntimeCheck::Variant { match_, .. } if var.type_.is_result() => {
278 docvec![var_value, ".is", match_.name(), "()"]
279 }
280
281 RuntimeCheck::Variant { match_, .. } => {
282 let qualification = match_
283 .module()
284 .map(|module| eco_format!("${module}."))
285 .unwrap_or_default();
286
287 docvec![var_value, " instanceof ", qualification, match_.name()]
288 }
289 RuntimeCheck::NonEmptyList { .. } => docvec!["!", var_value, ".hasLength(0)"],
290 RuntimeCheck::EmptyList => docvec![var_value, ".hasLength(0)"],
291 }
292 }
293
294 fn record_check_assignments(&mut self, var: &Variable, check: &RuntimeCheck) {
295 let value = self.get_variable_value(var);
296 match check {
297 RuntimeCheck::Int { .. }
298 | RuntimeCheck::Float { .. }
299 | RuntimeCheck::String { .. }
300 | RuntimeCheck::BitArray { .. }
301 | RuntimeCheck::EmptyList => (),
302
303 RuntimeCheck::StringPrefix { rest, prefix } => {
304 let prefix_size = utf16_no_escape_len(prefix);
305 self.set_pattern_variable_value(rest, eco_format!("{value}.slice({prefix_size})"));
306 }
307
308 RuntimeCheck::Tuple { elements, .. } => {
309 for (i, element) in elements.iter().enumerate() {
310 self.set_pattern_variable_value(element, eco_format!("{value}[{i}]"));
311 }
312 }
313
314 RuntimeCheck::Variant { fields, labels, .. } => {
315 for (i, field) in fields.iter().enumerate() {
316 let access = match labels.get(&i) {
317 Some(label) => eco_format!("{value}.{label}"),
318 None => eco_format!("{value}[{i}]"),
319 };
320 self.set_pattern_variable_value(field, access);
321 }
322 }
323
324 RuntimeCheck::NonEmptyList { first, rest } => {
325 self.set_pattern_variable_value(first, eco_format!("{value}.head"));
326 self.set_pattern_variable_value(rest, eco_format!("{value}.tail"));
327 }
328 }
329 }
330}
331
332fn let_<'a>(variable_name: EcoString, value: Document<'a>) -> Document<'a> {
333 docvec!["let ", variable_name, " = ", value, ";"]
334}
335
336/// Calculates the length of str as utf16 without escape characters.
337///
338fn utf16_no_escape_len(str: &EcoString) -> usize {
339 convert_string_escape_chars(str).encode_utf16().count()
340}
341
342fn assign_subjects<'a>(
343 expression_generator: &mut Generator<'_, 'a>,
344 subjects: &'a [TypedExpr],
345) -> Vec<(EcoString, Option<EcoString>)> {
346 let mut out = Vec::with_capacity(subjects.len());
347 for subject in subjects {
348 out.push(assign_subject(expression_generator, subject))
349 }
350 out
351}
352
353fn assign_subject<'a>(
354 expression_generator: &mut Generator<'_, 'a>,
355 subject: &'a TypedExpr,
356) -> (EcoString, Option<EcoString>) {
357 static ASSIGNMENT_VAR_ECO_STR: OnceLock<EcoString> = OnceLock::new();
358
359 match subject {
360 // If the value is a variable we don't need to assign it to a new
361 // variable, we can the value expression safely without worrying about
362 // performing computation or side effects multiple times.
363 TypedExpr::Var {
364 name, constructor, ..
365 } if constructor.is_local_variable() => (expression_generator.local_var(name), None),
366 // If it's not a variable we need to assign it to a variable
367 // to avoid rendering the subject expression multiple times
368 _ => {
369 let subject = expression_generator
370 .next_local_var(ASSIGNMENT_VAR_ECO_STR.get_or_init(|| ASSIGNMENT_VAR.into()));
371 (subject.clone(), Some(subject))
372 }
373 }
374}
375
376/// Appends the second document to the first one separating the two with a newline.
377/// However, if the second document is empty the empty line is not added.
378///
379fn join_with_line<'a>(one: Document<'a>, other: Document<'a>) -> Document<'a> {
380 if one.is_empty() {
381 other
382 } else if other.is_empty() {
383 one
384 } else {
385 docvec![one, line(), other]
386 }
387}