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

Configure Feed

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

gleam / compiler-core / src / javascript / decision.rs
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}