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

Configure Feed

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

Collapse `else if` in decision tree generation

+192 -83
+51 -14
compiler-core/src/javascript/decision.rs
··· 39 39 let mut variables = Variables::new(expression_generator); 40 40 let assignments = variables.assign_case_subjects(compiled_case, subjects)?; 41 41 let decision = CasePrinter { clauses, variables }.decision(&compiled_case.tree)?; 42 - Ok(docvec![assignments_to_doc(assignments), decision].force_break()) 42 + Ok(docvec![assignments_to_doc(assignments), decision.into_doc()].force_break()) 43 + } 44 + 45 + enum CaseBody<'a> { 46 + If(Document<'a>), 47 + Statements(Document<'a>), 48 + } 49 + 50 + impl<'a> CaseBody<'a> { 51 + fn into_doc(self) -> Document<'a> { 52 + match self { 53 + CaseBody::If(document) | CaseBody::Statements(document) => document, 54 + } 55 + } 43 56 } 44 57 45 58 struct CasePrinter<'module, 'generator, 'a> { ··· 101 114 /// 102 115 /// So, as we're generating code for each check and move further down the decision 103 116 /// tree, we will have to keep track of all the variables that we've discovered 104 - /// after each successfull check. 117 + /// after each successful check. 105 118 /// 106 119 /// In order to do that we'll be using a `Variables` data structure to hold all 107 120 /// this information about the current scope. This also allows us to reuse a lot ··· 109 122 /// the decision tree of let expressions! 110 123 /// 111 124 impl<'a> CasePrinter<'_, '_, 'a> { 112 - fn decision(&mut self, decision: &'a Decision) -> Output<'a> { 125 + fn decision(&mut self, decision: &'a Decision) -> Result<CaseBody<'a>, Error> { 113 126 match decision { 114 127 Decision::Fail => unreachable!("Invalid decision tree reached code generation"), 115 128 Decision::Run { body } => { 116 129 let bindings = self.variables.bindings_doc(&body.bindings); 117 130 let body = self.body_expression(body.clause_index)?; 118 - Ok(join_with_line(bindings, body)) 131 + Ok(CaseBody::Statements(join_with_line(bindings, body))) 119 132 } 120 133 Decision::Switch { 121 134 var, ··· 149 162 choices: &'a [(RuntimeCheck, Box<Decision>)], 150 163 fallback: &'a Decision, 151 164 fallback_check: &'a FallbackCheck, 152 - ) -> Output<'a> { 165 + ) -> Result<CaseBody<'a>, Error> { 153 166 // If there's just a single choice we can just generate the code for 154 167 // it: no need to do any checking, we know it must match! 155 168 if choices.is_empty() { ··· 200 213 } else { 201 214 docvec![" else if (", check_doc, ") "] 202 215 }; 203 - if_ = if_.append(docvec![branch, break_block(body?)]); 216 + if_ = if_.append(docvec![branch, break_block(body?.into_doc())]); 204 217 } 205 218 206 219 // In case there's some new variables we can extract after the ··· 212 225 } 213 226 214 227 let body = self.inside_new_scope(|this| this.decision(fallback))?; 228 + let (body, wrap_body_in_block) = match body { 229 + CaseBody::If(document) => (document, false), 230 + CaseBody::Statements(document) => (document, true), 231 + }; 232 + 233 + let has_assignments = !assignments.is_empty(); 234 + 215 235 let if_ = join_with_line(join(assignments, line()), if_); 216 - Ok(if body.is_empty() { 236 + let document = if body.is_empty() { 217 237 if_ 218 - } else { 238 + } else if wrap_body_in_block { 219 239 docvec![if_, " else ", break_block(body)] 240 + } else { 241 + docvec![if_, " else ", body] 242 + }; 243 + 244 + Ok(if has_assignments { 245 + CaseBody::Statements(document) 246 + } else { 247 + CaseBody::If(document) 220 248 }) 221 249 } 222 250 ··· 245 273 guard: usize, 246 274 if_true: &'a Body, 247 275 if_false: &'a Decision, 248 - ) -> Output<'a> { 276 + ) -> Result<CaseBody<'a>, Error> { 249 277 let guard = self 250 278 .clauses 251 279 .get(guard) ··· 272 300 let if_true_body = this.body_expression(if_true.clause_index)?; 273 301 Ok(join_with_line(if_true_bindings, if_true_body)) 274 302 })?; 275 - let if_false = self.inside_new_scope(|this| this.decision(if_false))?; 303 + let if_false = self 304 + .inside_new_scope(|this| this.decision(if_false))? 305 + .into_doc(); 276 306 277 307 // We can now piece everything together into a single document! 278 308 let if_ = docvec!["if (", check, ") ", break_block(if_true)]; 309 + let has_assignments = !check_bindings.is_empty(); 310 + 279 311 let if_ = join_with_line(check_bindings, if_); 280 - if if_false.is_empty() { 281 - Ok(docvec![if_]) 312 + let document = if if_false.is_empty() { 313 + if_ 282 314 } else { 283 315 let else_ = docvec![" else ", break_block(if_false)]; 284 - Ok(docvec![if_, else_]) 285 - } 316 + docvec![if_, else_] 317 + }; 318 + Ok(if has_assignments { 319 + CaseBody::Statements(document) 320 + } else { 321 + CaseBody::If(document) 322 + }) 286 323 } 287 324 } 288 325
+30
compiler-core/src/javascript/tests/case.rs
··· 413 413 " 414 414 ); 415 415 } 416 + 417 + #[test] 418 + fn list_with_guard() { 419 + assert_js!( 420 + " 421 + pub fn go(x) { 422 + case x { 423 + [] -> 0 424 + [first, ..] if first < 10 -> first * 2 425 + [first, ..] -> first 426 + } 427 + } 428 + " 429 + ); 430 + } 431 + 432 + #[test] 433 + fn list_with_guard_no_binding() { 434 + assert_js!( 435 + " 436 + pub fn go(x) { 437 + case x { 438 + [] -> 0 439 + [first, ..] if 1 < 10 -> first * 2 440 + [first, ..] -> first 441 + } 442 + } 443 + " 444 + ); 445 + }
+6 -8
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_bit_array_assignment_discard.snap
··· 18 18 if (x.bitSize === 8) { 19 19 let n = x.byteAt(0); 20 20 return n; 21 - } else { 22 - if (x.bitSize >= 8) { 23 - if ((x.bitSize - 8) % 8 === 0) { 24 - let n = x.byteAt(0); 25 - return n; 26 - } else { 27 - return 1; 28 - } 21 + } else if (x.bitSize >= 8) { 22 + if ((x.bitSize - 8) % 8 === 0) { 23 + let n = x.byteAt(0); 24 + return n; 29 25 } else { 30 26 return 1; 31 27 } 28 + } else { 29 + return 1; 32 30 } 33 31 }
+13 -17
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_bit_array_assignment_float.snap
··· 21 21 if (bitArraySliceToFloat(x, 0, 64, true) === 3.14) { 22 22 let pi = 3.14; 23 23 return pi; 24 - } else { 25 - if (bitArraySliceToFloat(x, 0, 64, true) === 1.1) { 26 - if ((x.bitSize - 64) % 8 === 0) { 27 - let pi = 1.1; 28 - return pi; 29 - } else { 30 - return 1.1; 31 - } 24 + } else if (bitArraySliceToFloat(x, 0, 64, true) === 1.1) { 25 + if ((x.bitSize - 64) % 8 === 0) { 26 + let pi = 1.1; 27 + return pi; 32 28 } else { 33 29 return 1.1; 34 30 } 31 + } else { 32 + return 1.1; 35 33 } 36 - } else { 37 - if (x.bitSize >= 64) { 38 - if (bitArraySliceToFloat(x, 0, 64, true) === 1.1) { 39 - if ((x.bitSize - 64) % 8 === 0) { 40 - let pi = 1.1; 41 - return pi; 42 - } else { 43 - return 1.1; 44 - } 34 + } else if (x.bitSize >= 64) { 35 + if (bitArraySliceToFloat(x, 0, 64, true) === 1.1) { 36 + if ((x.bitSize - 64) % 8 === 0) { 37 + let pi = 1.1; 38 + return pi; 45 39 } else { 46 40 return 1.1; 47 41 } 48 42 } else { 49 43 return 1.1; 50 44 } 45 + } else { 46 + return 1.1; 51 47 } 52 48 }
+13 -17
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_bit_array_assignment_int.snap
··· 19 19 if (x.byteAt(0) === 1) { 20 20 let n = 1; 21 21 return n; 22 - } else { 23 - if (x.byteAt(0) === 2) { 24 - if ((x.bitSize - 8) % 8 === 0) { 25 - let n = 2; 26 - return n; 27 - } else { 28 - return 1; 29 - } 22 + } else if (x.byteAt(0) === 2) { 23 + if ((x.bitSize - 8) % 8 === 0) { 24 + let n = 2; 25 + return n; 30 26 } else { 31 27 return 1; 32 28 } 29 + } else { 30 + return 1; 33 31 } 34 - } else { 35 - if (x.bitSize >= 8) { 36 - if (x.byteAt(0) === 2) { 37 - if ((x.bitSize - 8) % 8 === 0) { 38 - let n = 2; 39 - return n; 40 - } else { 41 - return 1; 42 - } 32 + } else if (x.bitSize >= 8) { 33 + if (x.byteAt(0) === 2) { 34 + if ((x.bitSize - 8) % 8 === 0) { 35 + let n = 2; 36 + return n; 43 37 } else { 44 38 return 1; 45 39 } 46 40 } else { 47 41 return 1; 48 42 } 43 + } else { 44 + return 1; 49 45 } 50 46 }
+13 -17
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_bit_array_assignment_string.snap
··· 23 23 x.byteAt(4) === 111) { 24 24 let message = "Hello"; 25 25 return message; 26 - } else { 27 - if (x.byteAt(0) === 74 && x.byteAt(1) === 97 && x.byteAt(2) === 107) { 28 - if ((x.bitSize - 24) % 8 === 0) { 29 - let message = "Jak"; 30 - return message; 31 - } else { 32 - return "wibble"; 33 - } 26 + } else if (x.byteAt(0) === 74 && x.byteAt(1) === 97 && x.byteAt(2) === 107) { 27 + if ((x.bitSize - 24) % 8 === 0) { 28 + let message = "Jak"; 29 + return message; 34 30 } else { 35 31 return "wibble"; 36 32 } 33 + } else { 34 + return "wibble"; 37 35 } 38 - } else { 39 - if (x.bitSize >= 24) { 40 - if (x.byteAt(0) === 74 && x.byteAt(1) === 97 && x.byteAt(2) === 107) { 41 - if ((x.bitSize - 24) % 8 === 0) { 42 - let message = "Jak"; 43 - return message; 44 - } else { 45 - return "wibble"; 46 - } 36 + } else if (x.bitSize >= 24) { 37 + if (x.byteAt(0) === 74 && x.byteAt(1) === 97 && x.byteAt(2) === 107) { 38 + if ((x.bitSize - 24) % 8 === 0) { 39 + let message = "Jak"; 40 + return message; 47 41 } else { 48 42 return "wibble"; 49 43 } 50 44 } else { 51 45 return "wibble"; 52 46 } 47 + } else { 48 + return "wibble"; 53 49 } 54 50 }
+31
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__list_with_guard.snap
··· 1 + --- 2 + source: compiler-core/src/javascript/tests/case.rs 3 + expression: "\npub fn go(x) {\n case x {\n [] -> 0\n [first, ..] if first < 10 -> first * 2\n [first, ..] -> first\n }\n}\n" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + pub fn go(x) { 8 + case x { 9 + [] -> 0 10 + [first, ..] if first < 10 -> first * 2 11 + [first, ..] -> first 12 + } 13 + } 14 + 15 + 16 + ----- COMPILED JAVASCRIPT 17 + import { Empty as $Empty } from "../gleam.mjs"; 18 + 19 + export function go(x) { 20 + if (x instanceof $Empty) { 21 + return 0; 22 + } else { 23 + let first = x.head; 24 + if (first < 10) { 25 + return first * 2; 26 + } else { 27 + let first$1 = x.head; 28 + return first$1; 29 + } 30 + } 31 + }
+29
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__list_with_guard_no_binding.snap
··· 1 + --- 2 + source: compiler-core/src/javascript/tests/case.rs 3 + expression: "\npub fn go(x) {\n case x {\n [] -> 0\n [first, ..] if 1 < 10 -> first * 2\n [first, ..] -> first\n }\n}\n" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + pub fn go(x) { 8 + case x { 9 + [] -> 0 10 + [first, ..] if 1 < 10 -> first * 2 11 + [first, ..] -> first 12 + } 13 + } 14 + 15 + 16 + ----- COMPILED JAVASCRIPT 17 + import { Empty as $Empty } from "../gleam.mjs"; 18 + 19 + export function go(x) { 20 + if (x instanceof $Empty) { 21 + return 0; 22 + } else if (1 < 10) { 23 + let first = x.head; 24 + return first * 2; 25 + } else { 26 + let first = x.head; 27 + return first; 28 + } 29 + }
+3 -5
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__multi_subject_no_catch_all.snap
··· 17 17 function go(x, y) { 18 18 if (x) { 19 19 return 1; 20 + } else if (y) { 21 + return 2; 20 22 } else { 21 - if (y) { 22 - return 2; 23 - } else { 24 - return 0; 25 - } 23 + return 0; 26 24 } 27 25 }
+3 -5
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__multi_subject_or.snap
··· 16 16 function go(x, y) { 17 17 if (x) { 18 18 return 1; 19 + } else if (y) { 20 + return 1; 19 21 } else { 20 - if (y) { 21 - return 1; 22 - } else { 23 - return 0; 24 - } 22 + return 0; 25 23 } 26 24 }