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 / expression.rs
76 kB 2100 lines
1use num_bigint::BigInt; 2use vec1::Vec1; 3 4use super::{ 5 pattern::{Assignment, CompiledPattern}, 6 *, 7}; 8use crate::{ 9 ast::*, 10 line_numbers::LineNumbers, 11 pretty::*, 12 type_::{ 13 ModuleValueConstructor, Type, TypedCallArg, ValueConstructor, ValueConstructorVariant, 14 }, 15}; 16use std::sync::Arc; 17 18#[derive(Debug, Clone)] 19pub enum Position { 20 Tail, 21 NotTail(Ordering), 22 /// We are compiling an expression inside a block, meaning we must assign 23 /// to the `_block` variable at the end of the scope, because blocks are not 24 /// expressions in JS. 25 /// Since JS doesn't have variable shadowing, we must store the name of the 26 /// variable being used, which will include the incrementing counter. 27 /// For example, `block$2` 28 Assign(EcoString), 29} 30 31impl Position { 32 /// Returns `true` if the position is [`Tail`]. 33 /// 34 /// [`Tail`]: Position::Tail 35 #[must_use] 36 pub fn is_tail(&self) -> bool { 37 matches!(self, Self::Tail) 38 } 39 40 #[must_use] 41 pub fn ordering(&self) -> Ordering { 42 match self { 43 Self::NotTail(ordering) => *ordering, 44 Self::Tail | Self::Assign(_) => Ordering::Loose, 45 } 46 } 47} 48 49#[derive(Debug, Clone, Copy)] 50/// Determines whether we can lift blocks into statement level instead of using 51/// immediately invoked function expressions. Consider the following piece of code: 52/// 53/// ```gleam 54/// some_function(function_with_side_effect(), { 55/// let a = 10 56/// other_function_with_side_effects(a) 57/// }) 58/// ``` 59/// Here, if we lift the block that is the second argument of the function, we 60/// would end up running `other_function_with_side_effects` before 61/// `function_with_side_effects`. This would be invalid, as code in Gleam should be 62/// evaluated left-to-right, top-to-bottom. In this case, the ordering would be 63/// `Strict`, indicating that we cannot lift the block. 64/// 65/// However, in this example: 66/// 67/// ```gleam 68/// let value = !{ 69/// let value = False 70/// some_function_with_side_effect() 71/// value 72/// } 73/// ``` 74/// The only expression is the block, meaning it can be safely lifted without 75/// changing the evaluation order of the program. So the ordering is `Loose`. 76/// 77pub enum Ordering { 78 Strict, 79 Loose, 80} 81 82#[derive(Debug)] 83pub(crate) struct Generator<'module, 'ast> { 84 module_name: EcoString, 85 src_path: &'module Utf8Path, 86 project_root: &'module Utf8Path, 87 line_numbers: &'module LineNumbers, 88 function_name: Option<EcoString>, 89 function_arguments: Vec<Option<&'module EcoString>>, 90 current_scope_vars: im::HashMap<EcoString, usize>, 91 pub function_position: Position, 92 pub scope_position: Position, 93 // We register whether these features are used within an expression so that 94 // the module generator can output a suitable function if it is needed. 95 pub tracker: &'module mut UsageTracker, 96 // We track whether tail call recursion is used so that we can render a loop 97 // at the top level of the function to use in place of pushing new stack 98 // frames. 99 pub tail_recursion_used: bool, 100 /// Statements to be compiled when lifting blocks into statement scope. 101 /// For example, when compiling the following code: 102 /// ```gleam 103 /// let a = { 104 /// let b = 1 105 /// b + 1 106 /// } 107 /// ``` 108 /// There will be 2 items in `statement_level`: The first will be `let _block;` 109 /// The second will be the generated code for the block being assigned to `a`. 110 /// This lets use return `_block` as the value that the block evaluated to, 111 /// while still including the necessary code in the output at the right place. 112 /// 113 /// Once the `let` statement has compiled its value, it will add anything accumulated 114 /// in `statement_level` to the generated code, so it will result in: 115 /// 116 /// ```javascript 117 /// let _block; 118 /// {...} 119 /// let a = _block; 120 /// ``` 121 /// 122 statement_level: Vec<Document<'ast>>, 123} 124 125impl<'module, 'a> Generator<'module, 'a> { 126 #[allow(clippy::too_many_arguments)] // TODO: FIXME 127 pub fn new( 128 module_name: EcoString, 129 src_path: &'module Utf8Path, 130 project_root: &'module Utf8Path, 131 line_numbers: &'module LineNumbers, 132 function_name: EcoString, 133 function_arguments: Vec<Option<&'module EcoString>>, 134 tracker: &'module mut UsageTracker, 135 mut current_scope_vars: im::HashMap<EcoString, usize>, 136 ) -> Self { 137 let mut function_name = Some(function_name); 138 for &name in function_arguments.iter().flatten() { 139 // Initialise the function arguments 140 let _ = current_scope_vars.insert(name.clone(), 0); 141 142 // If any of the function arguments shadow the current function then 143 // recursion is no longer possible. 144 if function_name.as_ref() == Some(name) { 145 function_name = None; 146 } 147 } 148 Self { 149 tracker, 150 module_name, 151 src_path, 152 project_root, 153 line_numbers, 154 function_name, 155 function_arguments, 156 tail_recursion_used: false, 157 current_scope_vars, 158 function_position: Position::Tail, 159 scope_position: Position::Tail, 160 statement_level: Vec::new(), 161 } 162 } 163 164 pub fn local_var(&mut self, name: &EcoString) -> EcoString { 165 match self.current_scope_vars.get(name) { 166 None => { 167 let _ = self.current_scope_vars.insert(name.clone(), 0); 168 maybe_escape_identifier(name) 169 } 170 Some(0) => maybe_escape_identifier(name), 171 Some(n) if name == "$" => eco_format!("${n}"), 172 Some(n) => eco_format!("{name}${n}"), 173 } 174 } 175 176 pub fn next_local_var(&mut self, name: &EcoString) -> EcoString { 177 let next = self.current_scope_vars.get(name).map_or(0, |i| i + 1); 178 let _ = self.current_scope_vars.insert(name.clone(), next); 179 self.local_var(name) 180 } 181 182 pub fn function_body( 183 &mut self, 184 body: &'a [TypedStatement], 185 args: &'a [TypedArg], 186 ) -> Output<'a> { 187 let body = self.statements(body)?; 188 if self.tail_recursion_used { 189 self.tail_call_loop(body, args) 190 } else { 191 Ok(body) 192 } 193 } 194 195 fn tail_call_loop(&mut self, body: Document<'a>, args: &'a [TypedArg]) -> Output<'a> { 196 let loop_assignments = concat(args.iter().flat_map(Arg::get_variable_name).map(|name| { 197 let var = maybe_escape_identifier(name); 198 docvec!["let ", var, " = loop$", name, ";", line()] 199 })); 200 Ok(docvec![ 201 "while (true) {", 202 docvec![line(), loop_assignments, body].nest(INDENT), 203 line(), 204 "}" 205 ]) 206 } 207 208 fn statement(&mut self, statement: &'a TypedStatement) -> Output<'a> { 209 let expression_doc = match statement { 210 Statement::Expression(expression) => self.expression(expression), 211 Statement::Assignment(assignment) => self.assignment(assignment), 212 Statement::Use(_use) => self.expression(&_use.call), 213 }?; 214 Ok(self.add_statement_level(expression_doc)) 215 } 216 217 fn add_statement_level(&mut self, expression: Document<'a>) -> Document<'a> { 218 if self.statement_level.is_empty() { 219 expression 220 } else { 221 let mut statements = std::mem::take(&mut self.statement_level); 222 statements.push(expression); 223 Itertools::intersperse(statements.into_iter(), line()) 224 .collect_vec() 225 .to_doc() 226 } 227 } 228 229 pub fn expression(&mut self, expression: &'a TypedExpr) -> Output<'a> { 230 let document = match expression { 231 TypedExpr::String { value, .. } => Ok(string(value)), 232 233 TypedExpr::Int { value, .. } => Ok(int(value)), 234 TypedExpr::Float { value, .. } => Ok(float(value)), 235 236 TypedExpr::List { elements, tail, .. } => { 237 self.not_in_tail_position(Some(Ordering::Strict), |this| match tail { 238 Some(tail) => { 239 this.tracker.prepend_used = true; 240 let tail = this.wrap_expression(tail)?; 241 prepend( 242 elements.iter().map(|element| this.wrap_expression(element)), 243 tail, 244 ) 245 } 246 None => { 247 this.tracker.list_used = true; 248 list(elements.iter().map(|element| this.wrap_expression(element))) 249 } 250 }) 251 } 252 253 TypedExpr::Tuple { elements, .. } => self.tuple(elements), 254 TypedExpr::TupleIndex { tuple, index, .. } => self.tuple_index(tuple, *index), 255 256 TypedExpr::Case { 257 subjects, clauses, .. 258 } => self.case(subjects, clauses), 259 260 TypedExpr::Call { fun, args, .. } => self.call(fun, args), 261 TypedExpr::Fn { args, body, .. } => self.fn_(args, body), 262 263 TypedExpr::RecordAccess { record, label, .. } => self.record_access(record, label), 264 TypedExpr::RecordUpdate { 265 record, 266 constructor, 267 args, 268 .. 269 } => self.record_update(record, constructor, args), 270 271 TypedExpr::Var { 272 name, constructor, .. 273 } => self.variable(name, constructor), 274 275 TypedExpr::Pipeline { 276 first_value, 277 assignments, 278 finally, 279 .. 280 } => self.pipeline(first_value, assignments.as_slice(), finally), 281 282 TypedExpr::Block { statements, .. } => self.block(statements), 283 284 TypedExpr::BinOp { 285 name, left, right, .. 286 } => self.bin_op(name, left, right), 287 288 TypedExpr::Todo { 289 message, location, .. 290 } => self.todo(message.as_ref().map(|m| &**m), location), 291 292 TypedExpr::Panic { 293 location, message, .. 294 } => self.panic(location, message.as_ref().map(|m| &**m)), 295 296 TypedExpr::BitArray { segments, .. } => self.bit_array(segments), 297 298 TypedExpr::ModuleSelect { 299 module_alias, 300 label, 301 constructor, 302 .. 303 } => Ok(self.module_select(module_alias, label, constructor)), 304 305 TypedExpr::NegateBool { value, .. } => self.negate_with("!", value), 306 307 TypedExpr::NegateInt { value, .. } => self.negate_with("- ", value), 308 309 TypedExpr::Echo { 310 expression, 311 location, 312 .. 313 } => { 314 let expression = expression 315 .as_ref() 316 .expect("echo with no expression outside of pipe"); 317 let expresion_doc = 318 self.not_in_tail_position(None, |this| this.wrap_expression(expression))?; 319 self.echo(expresion_doc, location) 320 } 321 322 TypedExpr::Invalid { .. } => { 323 panic!("invalid expressions should not reach code generation") 324 } 325 }?; 326 Ok(if expression.handles_own_return() { 327 document 328 } else { 329 self.wrap_return(document) 330 }) 331 } 332 333 fn negate_with(&mut self, with: &'static str, value: &'a TypedExpr) -> Output<'a> { 334 self.not_in_tail_position(None, |this| Ok(docvec![with, this.wrap_expression(value)?])) 335 } 336 337 fn bit_array(&mut self, segments: &'a [TypedExprBitArraySegment]) -> Output<'a> { 338 use BitArrayOption as Opt; 339 340 self.tracker.bit_array_literal_used = true; 341 342 // Collect all the values used in segments. 343 let segments_array = array(segments.iter().map(|segment| { 344 let value = self.not_in_tail_position(Some(Ordering::Strict), |this| { 345 this.wrap_expression(&segment.value) 346 })?; 347 348 if segment.has_native_option() { 349 return Err(Error::Unsupported { 350 feature: "This bit array segment option".into(), 351 location: segment.location, 352 }); 353 } 354 355 match segment.options.as_slice() { 356 // Int segment 357 _ if segment.type_.is_int() => { 358 let details = self.sized_bit_array_segment_details(segment)?; 359 match (details.size_value, segment.value.as_ref()) { 360 (Some(size_value), TypedExpr::Int { int_value, .. }) 361 if size_value <= SAFE_INT_SEGMENT_MAX_SIZE.into() 362 && (&size_value % BigInt::from(8) == BigInt::ZERO) => 363 { 364 let bytes = bit_array_segment_int_value_to_bytes( 365 int_value.clone(), 366 size_value, 367 segment.endianness(), 368 )?; 369 370 Ok(u8_slice(&bytes)) 371 } 372 373 (Some(size_value), _) if size_value == 8.into() => Ok(value), 374 375 (Some(size_value), _) if size_value <= 0.into() => Ok(nil()), 376 377 _ => { 378 self.tracker.sized_integer_segment_used = true; 379 let size = details.size; 380 let is_big = bool(segment.endianness().is_big()); 381 Ok(docvec!["sizedInt(", value, ", ", size, ", ", is_big, ")"]) 382 } 383 } 384 } 385 386 // Float segment 387 _ if segment.type_.is_float() => { 388 self.tracker.float_bit_array_segment_used = true; 389 let details = self.sized_bit_array_segment_details(segment)?; 390 let size = details.size; 391 let is_big = bool(segment.endianness().is_big()); 392 Ok(docvec!["sizedFloat(", value, ", ", size, ", ", is_big, ")"]) 393 } 394 395 // UTF8 strings 396 [Opt::Utf8 { .. }] => { 397 self.tracker.string_bit_array_segment_used = true; 398 Ok(docvec!["stringBits(", value, ")"]) 399 } 400 401 // UTF8 codepoints 402 [Opt::Utf8Codepoint { .. }] => { 403 self.tracker.codepoint_bit_array_segment_used = true; 404 Ok(docvec!["codepointBits(", value, ")"]) 405 } 406 407 // Bit arrays 408 [Opt::Bits { .. }] => Ok(value), 409 410 // Bit arrays with explicit size. The explicit size slices the bit array to the 411 // specified size. A runtime exception is thrown if the size exceeds the number 412 // of bits in the bit array. 413 [Opt::Bits { .. }, Opt::Size { value: size, .. }] 414 | [Opt::Size { value: size, .. }, Opt::Bits { .. }] => match &**size { 415 TypedExpr::Int { value: size, .. } => { 416 self.tracker.bit_array_slice_used = true; 417 Ok(docvec!["bitArraySlice(", value, ", 0, ", size, ")"]) 418 } 419 420 TypedExpr::Var { name, .. } => { 421 self.tracker.bit_array_slice_used = true; 422 Ok(docvec!["bitArraySlice(", value, ", 0, ", name, ")"]) 423 } 424 425 _ => Err(Error::Unsupported { 426 feature: "This bit array segment option".into(), 427 location: segment.location, 428 }), 429 }, 430 431 // Anything else 432 _ => Err(Error::Unsupported { 433 feature: "This bit array segment option".into(), 434 location: segment.location, 435 }), 436 } 437 }))?; 438 439 Ok(docvec!["toBitArray(", segments_array, ")"]) 440 } 441 442 fn sized_bit_array_segment_details( 443 &mut self, 444 segment: &'a TypedExprBitArraySegment, 445 ) -> Result<SizedBitArraySegmentDetails<'a>, Error> { 446 let size = segment.size(); 447 let unit = segment.unit(); 448 let (size_value, size) = match size { 449 Some(TypedExpr::Int { int_value, .. }) => { 450 let size_value = int_value * unit; 451 let size = eco_format!("{}", size_value).to_doc(); 452 (Some(size_value), size) 453 } 454 Some(size) => { 455 let mut size = self.not_in_tail_position(Some(Ordering::Strict), |this| { 456 this.wrap_expression(size) 457 })?; 458 459 if unit != 1 { 460 size = size.group().append(" * ".to_doc().append(unit.to_doc())); 461 } 462 463 (None, size) 464 } 465 466 None => { 467 let size_value: usize = if segment.type_.is_int() { 8 } else { 64 }; 468 (Some(BigInt::from(size_value)), docvec![size_value]) 469 } 470 }; 471 472 Ok(SizedBitArraySegmentDetails { size, size_value }) 473 } 474 475 pub fn wrap_return(&mut self, document: Document<'a>) -> Document<'a> { 476 match &self.scope_position { 477 Position::Tail => docvec!["return ", document, ";"], 478 Position::NotTail(_) => document, 479 Position::Assign(name) => docvec![name.clone(), " = ", document, ";"], 480 } 481 } 482 483 pub fn not_in_tail_position<CompileFn>( 484 &mut self, 485 // If ordering is None, it is inherited from the parent scope. 486 // It will be None in cases like `!x`, where `x` can be lifted 487 // only if the ordering is already loose. 488 ordering: Option<Ordering>, 489 compile: CompileFn, 490 ) -> Output<'a> 491 where 492 CompileFn: Fn(&mut Self) -> Output<'a>, 493 { 494 let new_ordering = ordering.unwrap_or(self.scope_position.ordering()); 495 496 let function_position = 497 std::mem::replace(&mut self.function_position, Position::NotTail(new_ordering)); 498 let scope_position = 499 std::mem::replace(&mut self.scope_position, Position::NotTail(new_ordering)); 500 501 let result = compile(self); 502 503 self.function_position = function_position; 504 self.scope_position = scope_position; 505 result 506 } 507 508 /// Use the `_block` variable if the expression is JS statement. 509 pub fn wrap_expression(&mut self, expression: &'a TypedExpr) -> Output<'a> { 510 match (expression, &self.scope_position) { 511 (_, Position::Tail | Position::Assign(_)) => self.expression(expression), 512 ( 513 TypedExpr::Panic { .. } 514 | TypedExpr::Todo { .. } 515 | TypedExpr::Case { .. } 516 | TypedExpr::Pipeline { .. } 517 | TypedExpr::RecordUpdate { .. }, 518 Position::NotTail(Ordering::Loose), 519 ) => self.wrap_block(|this| this.expression(expression)), 520 ( 521 TypedExpr::Panic { .. } 522 | TypedExpr::Todo { .. } 523 | TypedExpr::Case { .. } 524 | TypedExpr::Pipeline { .. } 525 | TypedExpr::RecordUpdate { .. }, 526 Position::NotTail(Ordering::Strict), 527 ) => self.immediately_invoked_function_expression(expression, |this, expr| { 528 this.expression(expr) 529 }), 530 _ => self.expression(expression), 531 } 532 } 533 534 /// Wrap an expression using the `_block` variable if required due to being 535 /// a JS statement, or in parens if required due to being an operator or 536 /// a function literal. 537 pub fn child_expression(&mut self, expression: &'a TypedExpr) -> Output<'a> { 538 match expression { 539 TypedExpr::BinOp { name, .. } if name.is_operator_to_wrap() => {} 540 TypedExpr::Fn { .. } => {} 541 542 _ => return self.wrap_expression(expression), 543 } 544 545 let document = self.expression(expression)?; 546 Ok(match &self.scope_position { 547 // Here the document is a return statement: `return <expr>;` 548 // or an assignment: `_block = <expr>;` 549 Position::Tail | Position::Assign(_) => document, 550 Position::NotTail(_) => docvec!["(", document, ")"], 551 }) 552 } 553 554 /// Wrap an expression in an immediately invoked function expression 555 fn immediately_invoked_function_expression<T, ToDoc>( 556 &mut self, 557 statements: &'a T, 558 to_doc: ToDoc, 559 ) -> Output<'a> 560 where 561 ToDoc: FnOnce(&mut Self, &'a T) -> Output<'a>, 562 { 563 // Save initial state 564 let scope_position = std::mem::replace(&mut self.scope_position, Position::Tail); 565 566 // Set state for in this iife 567 let current_scope_vars = self.current_scope_vars.clone(); 568 569 // Generate the expression 570 let result = to_doc(self, statements); 571 572 // Reset 573 self.current_scope_vars = current_scope_vars; 574 self.scope_position = scope_position; 575 576 // Wrap in iife document 577 let doc = immediately_invoked_function_expression_document(result?); 578 Ok(self.wrap_return(doc)) 579 } 580 581 fn wrap_block<CompileFn>(&mut self, compile: CompileFn) -> Output<'a> 582 where 583 CompileFn: Fn(&mut Self) -> Output<'a>, 584 { 585 let block_variable = self.next_local_var(&BLOCK_VARIABLE.into()); 586 587 // Save initial state 588 let scope_position = std::mem::replace( 589 &mut self.scope_position, 590 Position::Assign(block_variable.clone()), 591 ); 592 let function_position = std::mem::replace( 593 &mut self.function_position, 594 Position::NotTail(Ordering::Strict), 595 ); 596 597 // Generate the expression 598 let statement_doc = compile(self); 599 600 // Reset 601 self.scope_position = scope_position; 602 self.function_position = function_position; 603 604 self.statement_level 605 .push(docvec!["let ", block_variable.clone(), ";"]); 606 self.statement_level.push(statement_doc?); 607 608 Ok(self.wrap_return(block_variable.to_doc())) 609 } 610 611 fn variable(&mut self, name: &'a EcoString, constructor: &'a ValueConstructor) -> Output<'a> { 612 match &constructor.variant { 613 ValueConstructorVariant::LocalConstant { literal } => { 614 constant_expression(Context::Function, self.tracker, literal) 615 } 616 ValueConstructorVariant::Record { arity, .. } => { 617 let type_ = constructor.type_.clone(); 618 let tracker = &mut self.tracker; 619 Ok(record_constructor(type_, None, name, *arity, tracker)) 620 } 621 ValueConstructorVariant::ModuleFn { .. } 622 | ValueConstructorVariant::ModuleConstant { .. } 623 | ValueConstructorVariant::LocalVariable { .. } => Ok(self.local_var(name).to_doc()), 624 } 625 } 626 627 fn pipeline( 628 &mut self, 629 first_value: &'a TypedPipelineAssignment, 630 assignments: &'a [(TypedPipelineAssignment, PipelineAssignmentKind)], 631 finally: &'a TypedExpr, 632 ) -> Output<'a> { 633 let count = assignments.len(); 634 let mut documents = Vec::with_capacity((count + 2) * 2); 635 636 let all_assignments = std::iter::once(first_value) 637 .chain(assignments.iter().map(|(assignment, _kind)| assignment)); 638 639 let mut latest_local_var: Option<EcoString> = None; 640 for assignment in all_assignments { 641 match assignment.value.as_ref() { 642 // An echo in a pipeline won't result in an assignment, instead it 643 // just prints the previous variable assigned in the pipeline. 644 TypedExpr::Echo { 645 expression: None, 646 location, 647 .. 648 } => documents.push(self.not_in_tail_position(Some(Ordering::Strict), |this| { 649 let var = latest_local_var 650 .as_ref() 651 .expect("echo with no previous step in a pipe"); 652 this.echo(var.to_doc(), location) 653 })?), 654 655 // Otherwise we assign the intermediate pipe value to a variable. 656 _ => { 657 documents.push(self.not_in_tail_position(Some(Ordering::Strict), |this| { 658 this.simple_variable_assignment(&assignment.name, &assignment.value) 659 })?); 660 latest_local_var = Some(self.local_var(&assignment.name)); 661 } 662 } 663 664 documents.push(line()); 665 } 666 667 match finally { 668 TypedExpr::Echo { 669 expression: None, 670 location, 671 .. 672 } => { 673 let var = latest_local_var.expect("echo with no previous step in a pipe"); 674 documents.push(self.echo(var.to_doc(), location)?); 675 } 676 _ => documents.push(self.expression(finally)?), 677 } 678 679 Ok(documents.to_doc().force_break()) 680 } 681 682 fn expression_flattening_blocks(&mut self, expression: &'a TypedExpr) -> Output<'a> { 683 match expression { 684 TypedExpr::Block { statements, .. } => self.statements(statements), 685 _ => { 686 let expression_document = self.expression(expression)?; 687 Ok(self.add_statement_level(expression_document)) 688 } 689 } 690 } 691 692 fn block(&mut self, statements: &'a Vec1<TypedStatement>) -> Output<'a> { 693 if statements.len() == 1 { 694 match statements.first() { 695 Statement::Expression(expression) => return self.child_expression(expression), 696 697 Statement::Assignment(assignment) => match &assignment.kind { 698 AssignmentKind::Let | AssignmentKind::Generated => { 699 return self.child_expression(assignment.value.as_ref()); 700 } 701 // We can't just return the right-hand side of a `let assert` 702 // assignment; we still need to check that the pattern matches. 703 AssignmentKind::Assert { .. } => {} 704 }, 705 706 Statement::Use(use_) => return self.child_expression(&use_.call), 707 } 708 } 709 710 match &self.scope_position { 711 Position::Tail | Position::Assign(_) => self.block_document(statements), 712 Position::NotTail(Ordering::Strict) => self 713 .immediately_invoked_function_expression(statements, |this, statements| { 714 this.statements(statements) 715 }), 716 Position::NotTail(Ordering::Loose) => self.wrap_block(|this| { 717 // Save previous scope 718 let current_scope_vars = this.current_scope_vars.clone(); 719 720 let document = this.block_document(statements)?; 721 722 // Restore previous state 723 this.current_scope_vars = current_scope_vars; 724 725 Ok(document) 726 }), 727 } 728 } 729 730 fn block_document(&mut self, statements: &'a Vec1<TypedStatement>) -> Output<'a> { 731 let statements = self.statements(statements)?; 732 Ok(docvec![ 733 "{", 734 docvec![line(), statements].nest(INDENT), 735 line(), 736 "}" 737 ]) 738 } 739 740 fn statements(&mut self, statements: &'a [TypedStatement]) -> Output<'a> { 741 // If there are any statements that need to be printed at statement level, that's 742 // for an outer scope so we don't want to print them inside this one. 743 let statement_level = std::mem::take(&mut self.statement_level); 744 let count = statements.len(); 745 let mut documents = Vec::with_capacity(count * 3); 746 for (i, statement) in statements.iter().enumerate() { 747 if i + 1 < count { 748 documents.push(self.not_in_tail_position(Some(Ordering::Loose), |this| { 749 this.statement(statement) 750 })?); 751 if requires_semicolon(statement) { 752 documents.push(";".to_doc()); 753 } 754 documents.push(line()); 755 } else { 756 documents.push(self.statement(statement)?); 757 } 758 } 759 self.statement_level = statement_level; 760 if count == 1 { 761 Ok(documents.to_doc()) 762 } else { 763 Ok(documents.to_doc().force_break()) 764 } 765 } 766 767 fn simple_variable_assignment( 768 &mut self, 769 name: &'a EcoString, 770 value: &'a TypedExpr, 771 ) -> Output<'a> { 772 // Subject must be rendered before the variable for variable numbering 773 let subject = 774 self.not_in_tail_position(Some(Ordering::Loose), |this| this.wrap_expression(value))?; 775 let js_name = self.next_local_var(name); 776 let assignment = docvec!["let ", js_name.clone(), " = ", subject, ";"]; 777 let assignment = match &self.scope_position { 778 Position::NotTail(_) => assignment, 779 Position::Tail => docvec![assignment, line(), "return ", js_name, ";"], 780 Position::Assign(block_variable) => docvec![ 781 assignment, 782 line(), 783 block_variable.clone(), 784 " = ", 785 js_name, 786 ";" 787 ], 788 }; 789 790 Ok(assignment.force_break()) 791 } 792 793 fn assignment(&mut self, assignment: &'a TypedAssignment) -> Output<'a> { 794 let TypedAssignment { 795 pattern, 796 kind, 797 value, 798 annotation: _, 799 location: _, 800 } = assignment; 801 802 // If it is a simple assignment to a variable we can generate a normal 803 // JS assignment 804 if let TypedPattern::Variable { name, .. } = pattern { 805 return self.simple_variable_assignment(name, value); 806 } 807 808 // Otherwise we need to compile the patterns 809 let (subject, subject_assignment) = pattern::assign_subject(self, value); 810 // Value needs to be rendered before traversing pattern to have correctly incremented variables. 811 let value = 812 self.not_in_tail_position(Some(Ordering::Loose), |this| this.wrap_expression(value))?; 813 let mut pattern_generator = pattern::Generator::new(self); 814 pattern_generator.traverse_pattern(&subject, pattern)?; 815 let compiled = pattern_generator.take_compiled(); 816 817 // If we are in tail position we can return value being assigned 818 let afterwards = match &self.scope_position { 819 Position::NotTail(_) => nil(), 820 Position::Tail => docvec![ 821 line(), 822 "return ", 823 subject_assignment.clone().unwrap_or_else(|| value.clone()), 824 ";" 825 ], 826 Position::Assign(block_variable) => docvec![ 827 line(), 828 block_variable.clone(), 829 " = ", 830 subject_assignment.clone().unwrap_or_else(|| value.clone()), 831 ";" 832 ], 833 }; 834 835 let compiled = 836 self.pattern_into_assignment_doc(compiled, subject, pattern.location(), kind)?; 837 // If there is a subject name given create a variable to hold it for 838 // use in patterns 839 let doc = match subject_assignment { 840 Some(name) => docvec!["let ", name, " = ", value, ";", line(), compiled], 841 None => compiled, 842 }; 843 844 Ok(doc.append(afterwards).force_break()) 845 } 846 847 fn case(&mut self, subject_values: &'a [TypedExpr], clauses: &'a [TypedClause]) -> Output<'a> { 848 let (subjects, subject_assignments): (Vec<_>, Vec<_>) = 849 pattern::assign_subjects(self, subject_values) 850 .into_iter() 851 .unzip(); 852 let mut generator = pattern::Generator::new(self); 853 854 let mut doc = nil(); 855 856 // We wish to be able to know whether this is the first or clause being 857 // processed, so record the index number. We use this instead of 858 // `Iterator.enumerate` because we are using a nested for loop. 859 let mut clause_number = 0; 860 let total_patterns: usize = clauses 861 .iter() 862 .map(|c| c.alternative_patterns.len()) 863 .sum::<usize>() 864 + clauses.len(); 865 866 // A case has many clauses `pattern -> consequence` 867 for clause in clauses { 868 let multipattern = std::iter::once(&clause.pattern); 869 let multipatterns = multipattern.chain(&clause.alternative_patterns); 870 871 // A clause can have many patterns `pattern, pattern ->...` 872 for multipatterns in multipatterns { 873 let scope = generator.expression_generator.current_scope_vars.clone(); 874 let mut compiled = 875 generator.generate(&subjects, multipatterns, clause.guard.as_ref())?; 876 let consequence = generator 877 .expression_generator 878 .expression_flattening_blocks(&clause.then)?; 879 880 // We've seen one more clause 881 clause_number += 1; 882 883 // Reset the scope now that this clause has finished, causing the 884 // variables to go out of scope. 885 generator.expression_generator.current_scope_vars = scope; 886 887 // If the pattern assigns any variables we need to render assignments 888 let body = if compiled.has_assignments() { 889 let assignments = generator 890 .expression_generator 891 .pattern_take_assignments_doc(&mut compiled); 892 docvec![assignments, line(), consequence] 893 } else { 894 consequence 895 }; 896 897 let is_final_clause = clause_number == total_patterns; 898 let is_first_clause = clause_number == 1; 899 let is_only_clause = is_final_clause && is_first_clause; 900 901 doc = if is_only_clause { 902 // If this is the only clause and there are no checks then we can 903 // render just the body as the case does nothing 904 // A block is used as it could declare variables still. 905 doc.append("{") 906 .append(docvec![line(), body].nest(INDENT)) 907 .append(line()) 908 .append("}") 909 } else if is_final_clause { 910 // If this is the final clause and there are no checks then we can 911 // render `else` instead of `else if (...)` 912 doc.append(" else {") 913 .append(docvec![line(), body].nest(INDENT)) 914 .append(line()) 915 .append("}") 916 } else { 917 doc.append(if is_first_clause { 918 "if (" 919 } else { 920 " else if (" 921 }) 922 .append( 923 generator 924 .expression_generator 925 .pattern_take_checks_doc(&mut compiled, true), 926 ) 927 .append(") {") 928 .append(docvec![line(), body].nest(INDENT)) 929 .append(line()) 930 .append("}") 931 }; 932 } 933 } 934 935 // If there is a subject name given create a variable to hold it for 936 // use in patterns 937 let subject_assignments: Vec<_> = subject_assignments 938 .into_iter() 939 .zip(subject_values) 940 .flat_map(|(assignment_name, value)| assignment_name.map(|name| (name, value))) 941 .map(|(name, value)| { 942 let value = self.not_in_tail_position(Some(Ordering::Strict), |this| { 943 this.wrap_expression(value) 944 })?; 945 Ok(docvec!["let ", name, " = ", value, ";", line()]) 946 }) 947 .try_collect()?; 948 949 Ok(docvec![subject_assignments, doc].force_break()) 950 } 951 952 fn assignment_no_match( 953 &mut self, 954 location: SrcSpan, 955 subject: Document<'a>, 956 message: Option<&'a TypedExpr>, 957 ) -> Output<'a> { 958 let message = match message { 959 Some(m) => { 960 self.not_in_tail_position(Some(Ordering::Strict), |this| this.expression(m))? 961 } 962 None => string("Pattern match failed, no pattern matched the value."), 963 }; 964 965 Ok(self.throw_error("let_assert", &message, location, [("value", subject)])) 966 } 967 968 fn tuple(&mut self, elements: &'a [TypedExpr]) -> Output<'a> { 969 self.not_in_tail_position(Some(Ordering::Strict), |this| { 970 array(elements.iter().map(|element| this.wrap_expression(element))) 971 }) 972 } 973 974 fn call(&mut self, fun: &'a TypedExpr, arguments: &'a [TypedCallArg]) -> Output<'a> { 975 let arguments = arguments 976 .iter() 977 .map(|element| { 978 self.not_in_tail_position(Some(Ordering::Strict), |this| { 979 this.wrap_expression(&element.value) 980 }) 981 }) 982 .try_collect()?; 983 984 self.call_with_doc_args(fun, arguments) 985 } 986 987 fn call_with_doc_args( 988 &mut self, 989 fun: &'a TypedExpr, 990 arguments: Vec<Document<'a>>, 991 ) -> Output<'a> { 992 match fun { 993 // Qualified record construction 994 TypedExpr::ModuleSelect { 995 constructor: ModuleValueConstructor::Record { name, .. }, 996 module_alias, 997 .. 998 } => Ok(self.wrap_return(construct_record(Some(module_alias), name, arguments))), 999 1000 // Record construction 1001 TypedExpr::Var { 1002 constructor: 1003 ValueConstructor { 1004 variant: ValueConstructorVariant::Record { .. }, 1005 type_, 1006 .. 1007 }, 1008 name, 1009 .. 1010 } => { 1011 if type_.is_result_constructor() { 1012 if name == "Ok" { 1013 self.tracker.ok_used = true; 1014 } else if name == "Error" { 1015 self.tracker.error_used = true; 1016 } 1017 } 1018 Ok(self.wrap_return(construct_record(None, name, arguments))) 1019 } 1020 1021 // Tail call optimisation. If we are calling the current function 1022 // and we are in tail position we can avoid creating a new stack 1023 // frame, enabling recursion with constant memory usage. 1024 TypedExpr::Var { name, .. } 1025 if self.function_name.as_ref() == Some(name) 1026 && self.function_position.is_tail() 1027 && self.current_scope_vars.get(name) == Some(&0) => 1028 { 1029 let mut docs = Vec::with_capacity(arguments.len() * 4); 1030 // Record that tail recursion is happening so that we know to 1031 // render the loop at the top level of the function. 1032 self.tail_recursion_used = true; 1033 1034 for (i, (element, argument)) in arguments 1035 .into_iter() 1036 .zip(&self.function_arguments) 1037 .enumerate() 1038 { 1039 if i != 0 { 1040 docs.push(line()); 1041 } 1042 // Create an assignment for each variable created by the function arguments 1043 if let Some(name) = argument { 1044 docs.push("loop$".to_doc()); 1045 docs.push(name.to_doc()); 1046 docs.push(" = ".to_doc()); 1047 } 1048 // Render the value given to the function. Even if it is not 1049 // assigned we still render it because the expression may 1050 // have some side effects. 1051 docs.push(element); 1052 docs.push(";".to_doc()); 1053 } 1054 Ok(docs.to_doc()) 1055 } 1056 1057 _ => { 1058 let fun = self.not_in_tail_position(None, |this| { 1059 let is_fn_literal = matches!(fun, TypedExpr::Fn { .. }); 1060 let fun = this.wrap_expression(fun)?; 1061 if is_fn_literal { 1062 Ok(docvec!["(", fun, ")"]) 1063 } else { 1064 Ok(fun) 1065 } 1066 })?; 1067 let arguments = call_arguments(arguments.into_iter().map(Ok))?; 1068 Ok(self.wrap_return(docvec![fun, arguments])) 1069 } 1070 } 1071 } 1072 1073 fn fn_(&mut self, arguments: &'a [TypedArg], body: &'a [TypedStatement]) -> Output<'a> { 1074 // New function, this is now the tail position 1075 let function_position = std::mem::replace(&mut self.function_position, Position::Tail); 1076 let scope_position = std::mem::replace(&mut self.scope_position, Position::Tail); 1077 1078 // And there's a new scope 1079 let scope = self.current_scope_vars.clone(); 1080 for name in arguments.iter().flat_map(Arg::get_variable_name) { 1081 let _ = self.current_scope_vars.insert(name.clone(), 0); 1082 } 1083 1084 // This is a new function so unset the recorded name so that we don't 1085 // mistakenly trigger tail call optimisation 1086 let mut name = None; 1087 std::mem::swap(&mut self.function_name, &mut name); 1088 1089 // Generate the function body 1090 let result = self.statements(body); 1091 1092 // Reset function name, scope, and tail position tracking 1093 self.function_position = function_position; 1094 self.scope_position = scope_position; 1095 self.current_scope_vars = scope; 1096 std::mem::swap(&mut self.function_name, &mut name); 1097 1098 Ok(docvec![ 1099 docvec![ 1100 fun_args(arguments, false), 1101 " => {", 1102 break_("", " "), 1103 result? 1104 ] 1105 .nest(INDENT) 1106 .append(break_("", " ")) 1107 .group(), 1108 "}", 1109 ]) 1110 } 1111 1112 fn record_access(&mut self, record: &'a TypedExpr, label: &'a str) -> Output<'a> { 1113 self.not_in_tail_position(None, |this| { 1114 let record = this.wrap_expression(record)?; 1115 Ok(docvec![record, ".", maybe_escape_property_doc(label)]) 1116 }) 1117 } 1118 1119 fn record_update( 1120 &mut self, 1121 record: &'a TypedAssignment, 1122 constructor: &'a TypedExpr, 1123 args: &'a [TypedCallArg], 1124 ) -> Output<'a> { 1125 Ok(docvec![ 1126 self.not_in_tail_position(None, |this| this.assignment(record))?, 1127 line(), 1128 self.call(constructor, args)?, 1129 ]) 1130 } 1131 1132 fn tuple_index(&mut self, tuple: &'a TypedExpr, index: u64) -> Output<'a> { 1133 self.not_in_tail_position(None, |this| { 1134 let tuple = this.wrap_expression(tuple)?; 1135 Ok(docvec![tuple, eco_format!("[{index}]")]) 1136 }) 1137 } 1138 1139 fn bin_op(&mut self, name: &'a BinOp, left: &'a TypedExpr, right: &'a TypedExpr) -> Output<'a> { 1140 match name { 1141 BinOp::And => self.print_bin_op(left, right, "&&"), 1142 BinOp::Or => self.print_bin_op(left, right, "||"), 1143 BinOp::LtInt | BinOp::LtFloat => self.print_bin_op(left, right, "<"), 1144 BinOp::LtEqInt | BinOp::LtEqFloat => self.print_bin_op(left, right, "<="), 1145 BinOp::Eq => self.equal(left, right, true), 1146 BinOp::NotEq => self.equal(left, right, false), 1147 BinOp::GtInt | BinOp::GtFloat => self.print_bin_op(left, right, ">"), 1148 BinOp::GtEqInt | BinOp::GtEqFloat => self.print_bin_op(left, right, ">="), 1149 BinOp::Concatenate | BinOp::AddInt | BinOp::AddFloat => { 1150 self.print_bin_op(left, right, "+") 1151 } 1152 BinOp::SubInt | BinOp::SubFloat => self.print_bin_op(left, right, "-"), 1153 BinOp::MultInt | BinOp::MultFloat => self.print_bin_op(left, right, "*"), 1154 BinOp::RemainderInt => self.remainder_int(left, right), 1155 BinOp::DivInt => self.div_int(left, right), 1156 BinOp::DivFloat => self.div_float(left, right), 1157 } 1158 } 1159 1160 fn div_int(&mut self, left: &'a TypedExpr, right: &'a TypedExpr) -> Output<'a> { 1161 let left = 1162 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left))?; 1163 let right = 1164 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right))?; 1165 self.tracker.int_division_used = true; 1166 Ok(docvec!["divideInt", wrap_args([left, right])]) 1167 } 1168 1169 fn remainder_int(&mut self, left: &'a TypedExpr, right: &'a TypedExpr) -> Output<'a> { 1170 let left = 1171 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left))?; 1172 let right = 1173 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right))?; 1174 self.tracker.int_remainder_used = true; 1175 Ok(docvec!["remainderInt", wrap_args([left, right])]) 1176 } 1177 1178 fn div_float(&mut self, left: &'a TypedExpr, right: &'a TypedExpr) -> Output<'a> { 1179 let left = 1180 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left))?; 1181 let right = 1182 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right))?; 1183 self.tracker.float_division_used = true; 1184 Ok(docvec!["divideFloat", wrap_args([left, right])]) 1185 } 1186 1187 fn equal( 1188 &mut self, 1189 left: &'a TypedExpr, 1190 right: &'a TypedExpr, 1191 should_be_equal: bool, 1192 ) -> Output<'a> { 1193 // If it is a simple scalar type then we can use JS' reference identity 1194 if is_js_scalar(left.type_()) { 1195 let left_doc = self 1196 .not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left))?; 1197 let right_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| { 1198 this.child_expression(right) 1199 })?; 1200 let operator = if should_be_equal { " === " } else { " !== " }; 1201 return Ok(docvec![left_doc, operator, right_doc]); 1202 } 1203 1204 // Other types must be compared using structural equality 1205 let left = 1206 self.not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(left))?; 1207 let right = 1208 self.not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(right))?; 1209 Ok(self.prelude_equal_call(should_be_equal, left, right)) 1210 } 1211 1212 pub(super) fn prelude_equal_call( 1213 &mut self, 1214 should_be_equal: bool, 1215 left: Document<'a>, 1216 right: Document<'a>, 1217 ) -> Document<'a> { 1218 // Record that we need to import the prelude's isEqual function into the module 1219 self.tracker.object_equality_used = true; 1220 // Construct the call 1221 let args = wrap_args([left, right]); 1222 let operator = if should_be_equal { 1223 "isEqual" 1224 } else { 1225 "!isEqual" 1226 }; 1227 docvec![operator, args] 1228 } 1229 1230 fn print_bin_op( 1231 &mut self, 1232 left: &'a TypedExpr, 1233 right: &'a TypedExpr, 1234 op: &'a str, 1235 ) -> Output<'a> { 1236 let left = 1237 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left))?; 1238 let right = 1239 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right))?; 1240 Ok(docvec![left, " ", op, " ", right]) 1241 } 1242 1243 fn todo(&mut self, message: Option<&'a TypedExpr>, location: &'a SrcSpan) -> Output<'a> { 1244 let message = match message { 1245 Some(m) => self.not_in_tail_position(None, |this| this.expression(m))?, 1246 None => string("`todo` expression evaluated. This code has not yet been implemented."), 1247 }; 1248 let doc = self.throw_error("todo", &message, *location, vec![]); 1249 1250 Ok(doc) 1251 } 1252 1253 fn panic(&mut self, location: &'a SrcSpan, message: Option<&'a TypedExpr>) -> Output<'a> { 1254 let message = match message { 1255 Some(m) => self.not_in_tail_position(None, |this| this.expression(m))?, 1256 None => string("`panic` expression evaluated."), 1257 }; 1258 let doc = self.throw_error("panic", &message, *location, vec![]); 1259 1260 Ok(doc) 1261 } 1262 1263 fn throw_error<Fields>( 1264 &mut self, 1265 error_name: &'a str, 1266 message: &Document<'a>, 1267 location: SrcSpan, 1268 fields: Fields, 1269 ) -> Document<'a> 1270 where 1271 Fields: IntoIterator<Item = (&'a str, Document<'a>)>, 1272 { 1273 self.tracker.make_error_used = true; 1274 let module = self.module_name.clone().to_doc().surround('"', '"'); 1275 let function = self 1276 .function_name 1277 .clone() 1278 .unwrap_or_default() 1279 .to_doc() 1280 .surround("\"", "\""); 1281 let line = self.line_numbers.line_number(location.start).to_doc(); 1282 let fields = wrap_object(fields.into_iter().map(|(k, v)| (k.to_doc(), Some(v)))); 1283 1284 docvec![ 1285 "throw makeError", 1286 wrap_args([ 1287 string(error_name), 1288 module, 1289 line, 1290 function, 1291 message.clone(), 1292 fields 1293 ]), 1294 ] 1295 } 1296 1297 fn module_select( 1298 &mut self, 1299 module: &'a str, 1300 label: &'a EcoString, 1301 constructor: &'a ModuleValueConstructor, 1302 ) -> Document<'a> { 1303 match constructor { 1304 ModuleValueConstructor::Fn { .. } | ModuleValueConstructor::Constant { .. } => { 1305 docvec!["$", module, ".", maybe_escape_identifier(label)] 1306 } 1307 1308 ModuleValueConstructor::Record { 1309 name, arity, type_, .. 1310 } => record_constructor(type_.clone(), Some(module), name, *arity, self.tracker), 1311 } 1312 } 1313 1314 fn pattern_into_assignment_doc( 1315 &mut self, 1316 compiled_pattern: CompiledPattern<'a>, 1317 subject: Document<'a>, 1318 location: SrcSpan, 1319 kind: &'a AssignmentKind<TypedExpr>, 1320 ) -> Output<'a> { 1321 let any_assignments = !compiled_pattern.assignments.is_empty(); 1322 let assignments = Self::pattern_assignments_doc(compiled_pattern.assignments); 1323 1324 // If it's an assert then it is likely that the pattern is inexhaustive. When a value is 1325 // provided that does not get matched the code needs to throw an exception, which is done 1326 // by the pattern_checks_or_throw_doc method. 1327 match kind { 1328 AssignmentKind::Assert { message, .. } if !compiled_pattern.checks.is_empty() => { 1329 let checks = self.pattern_checks_or_throw_doc( 1330 compiled_pattern.checks, 1331 subject, 1332 location, 1333 message.as_deref(), 1334 )?; 1335 1336 if !any_assignments { 1337 Ok(checks) 1338 } else { 1339 Ok(docvec![checks, line(), assignments]) 1340 } 1341 } 1342 _ => Ok(assignments), 1343 } 1344 } 1345 1346 fn pattern_checks_or_throw_doc( 1347 &mut self, 1348 checks: Vec<pattern::Check<'a>>, 1349 subject: Document<'a>, 1350 location: SrcSpan, 1351 message: Option<&'a TypedExpr>, 1352 ) -> Output<'a> { 1353 let checks = self.pattern_checks_doc(checks, false); 1354 Ok(docvec![ 1355 "if (", 1356 docvec![break_("", ""), checks].nest(INDENT), 1357 break_("", ""), 1358 ") {", 1359 docvec![ 1360 line(), 1361 self.assignment_no_match(location, subject, message)? 1362 ] 1363 .nest(INDENT), 1364 line(), 1365 "}", 1366 ] 1367 .group()) 1368 } 1369 1370 fn pattern_assignments_doc(assignments: Vec<Assignment<'_>>) -> Document<'_> { 1371 let assignments = assignments.into_iter().map(Assignment::into_doc); 1372 join(assignments, line()) 1373 } 1374 1375 fn pattern_take_assignments_doc( 1376 &self, 1377 compiled_pattern: &mut CompiledPattern<'a>, 1378 ) -> Document<'a> { 1379 let assignments = std::mem::take(&mut compiled_pattern.assignments); 1380 Self::pattern_assignments_doc(assignments) 1381 } 1382 1383 fn pattern_take_checks_doc( 1384 &self, 1385 compiled_pattern: &mut CompiledPattern<'a>, 1386 match_desired: bool, 1387 ) -> Document<'a> { 1388 let checks = std::mem::take(&mut compiled_pattern.checks); 1389 self.pattern_checks_doc(checks, match_desired) 1390 } 1391 1392 fn pattern_checks_doc( 1393 &self, 1394 checks: Vec<pattern::Check<'a>>, 1395 match_desired: bool, 1396 ) -> Document<'a> { 1397 if checks.is_empty() { 1398 return "true".to_doc(); 1399 }; 1400 let operator = if match_desired { 1401 break_(" &&", " && ") 1402 } else { 1403 break_(" ||", " || ") 1404 }; 1405 1406 let checks_len = checks.len(); 1407 join( 1408 checks.into_iter().map(|check| { 1409 if checks_len > 1 && check.may_require_wrapping() { 1410 docvec!["(", check.into_doc(match_desired), ")"] 1411 } else { 1412 check.into_doc(match_desired) 1413 } 1414 }), 1415 operator, 1416 ) 1417 .group() 1418 } 1419 1420 fn echo(&mut self, expression: Document<'a>, location: &'a SrcSpan) -> Output<'a> { 1421 self.tracker.echo_used = true; 1422 1423 let relative_path = self 1424 .src_path 1425 .strip_prefix(self.project_root) 1426 .unwrap_or(self.src_path) 1427 .as_str() 1428 .replace("\\", "\\\\"); 1429 1430 let relative_path_doc = EcoString::from(relative_path).to_doc(); 1431 1432 let echo_argument = call_arguments(vec![ 1433 Ok(expression), 1434 Ok(relative_path_doc.surround("\"", "\"")), 1435 Ok(self.line_numbers.line_number(location.start).to_doc()), 1436 ])?; 1437 Ok(self.wrap_return(docvec!["echo", echo_argument])) 1438 } 1439} 1440 1441pub fn int(value: &str) -> Document<'_> { 1442 let mut out = EcoString::with_capacity(value.len()); 1443 1444 if value.starts_with('-') { 1445 out.push('-'); 1446 } else if value.starts_with('+') { 1447 out.push('+'); 1448 }; 1449 let value = value.trim_start_matches(['+', '-'].as_ref()); 1450 1451 let value = if value.starts_with("0x") { 1452 out.push_str("0x"); 1453 value.trim_start_matches("0x") 1454 } else if value.starts_with("0o") { 1455 out.push_str("0o"); 1456 value.trim_start_matches("0o") 1457 } else if value.starts_with("0b") { 1458 out.push_str("0b"); 1459 value.trim_start_matches("0b") 1460 } else { 1461 value 1462 }; 1463 1464 // If the number starts with `0x_`, `0b_` or `0o_`, that is valid Gleam syntax 1465 // but not valid JavaScript syntax, so we remove the `_` here. 1466 let value = value.trim_start_matches('_'); 1467 1468 let value = value.trim_start_matches('0'); 1469 if value.is_empty() { 1470 out.push('0'); 1471 } 1472 out.push_str(value); 1473 1474 out.to_doc() 1475} 1476 1477pub fn float(value: &str) -> Document<'_> { 1478 let mut out = EcoString::with_capacity(value.len()); 1479 1480 if value.starts_with('-') { 1481 out.push('-'); 1482 } else if value.starts_with('+') { 1483 out.push('+'); 1484 }; 1485 let value = value.trim_start_matches(['+', '-'].as_ref()); 1486 1487 let value = value.trim_start_matches('0'); 1488 if value.starts_with(['.', 'e', 'E']) { 1489 out.push('0'); 1490 } 1491 out.push_str(value); 1492 1493 out.to_doc() 1494} 1495 1496pub(crate) fn guard_constant_expression<'a>( 1497 assignments: &mut Vec<Assignment<'a>>, 1498 tracker: &mut UsageTracker, 1499 expression: &'a TypedConstant, 1500) -> Output<'a> { 1501 match expression { 1502 Constant::Tuple { elements, .. } => array( 1503 elements 1504 .iter() 1505 .map(|element| guard_constant_expression(assignments, tracker, element)), 1506 ), 1507 1508 Constant::List { elements, .. } => { 1509 tracker.list_used = true; 1510 list( 1511 elements 1512 .iter() 1513 .map(|element| guard_constant_expression(assignments, tracker, element)), 1514 ) 1515 } 1516 Constant::Record { type_, name, .. } if type_.is_bool() && name == "True" => { 1517 Ok("true".to_doc()) 1518 } 1519 Constant::Record { type_, name, .. } if type_.is_bool() && name == "False" => { 1520 Ok("false".to_doc()) 1521 } 1522 Constant::Record { type_, .. } if type_.is_nil() => Ok("undefined".to_doc()), 1523 1524 Constant::Record { 1525 args, 1526 module, 1527 name, 1528 tag, 1529 type_, 1530 .. 1531 } => { 1532 if type_.is_result() { 1533 if tag == "Ok" { 1534 tracker.ok_used = true; 1535 } else { 1536 tracker.error_used = true; 1537 } 1538 } 1539 1540 // If there's no arguments and the type is a function that takes 1541 // arguments then this is the constructor being referenced, not the 1542 // function being called. 1543 if let Some(arity) = type_.fn_arity() { 1544 if args.is_empty() && arity != 0 { 1545 let arity = arity as u16; 1546 return Ok(record_constructor( 1547 type_.clone(), 1548 None, 1549 name, 1550 arity, 1551 tracker, 1552 )); 1553 } 1554 } 1555 1556 let field_values: Vec<_> = args 1557 .iter() 1558 .map(|arg| guard_constant_expression(assignments, tracker, &arg.value)) 1559 .try_collect()?; 1560 Ok(construct_record( 1561 module.as_ref().map(|(module, _)| module.as_str()), 1562 name, 1563 field_values, 1564 )) 1565 } 1566 1567 Constant::BitArray { segments, .. } => bit_array(tracker, segments, |tracker, constant| { 1568 guard_constant_expression(assignments, tracker, constant) 1569 }), 1570 1571 Constant::Var { name, .. } => Ok(assignments 1572 .iter() 1573 .find(|assignment| assignment.name == name) 1574 .map(|assignment| assignment.subject.clone()) 1575 .unwrap_or_else(|| maybe_escape_identifier(name).to_doc())), 1576 1577 expression => constant_expression(Context::Function, tracker, expression), 1578 } 1579} 1580 1581#[derive(Debug, Clone, Copy)] 1582/// The context where the constant expression is used, it might be inside a 1583/// function call, or in the definition of another constant. 1584/// 1585/// Based on the context we might want to annotate pure function calls as 1586/// "@__PURE__". 1587/// 1588pub enum Context { 1589 Constant, 1590 Function, 1591} 1592 1593pub(crate) fn constant_expression<'a>( 1594 context: Context, 1595 tracker: &mut UsageTracker, 1596 expression: &'a TypedConstant, 1597) -> Output<'a> { 1598 match expression { 1599 Constant::Int { value, .. } => Ok(int(value)), 1600 Constant::Float { value, .. } => Ok(float(value)), 1601 Constant::String { value, .. } => Ok(string(value)), 1602 Constant::Tuple { elements, .. } => array( 1603 elements 1604 .iter() 1605 .map(|element| constant_expression(context, tracker, element)), 1606 ), 1607 1608 Constant::List { elements, .. } => { 1609 tracker.list_used = true; 1610 let list = list( 1611 elements 1612 .iter() 1613 .map(|element| constant_expression(context, tracker, element)), 1614 )?; 1615 1616 match context { 1617 Context::Constant => Ok(docvec!["/* @__PURE__ */ ", list]), 1618 Context::Function => Ok(list), 1619 } 1620 } 1621 1622 Constant::Record { type_, name, .. } if type_.is_bool() && name == "True" => { 1623 Ok("true".to_doc()) 1624 } 1625 Constant::Record { type_, name, .. } if type_.is_bool() && name == "False" => { 1626 Ok("false".to_doc()) 1627 } 1628 Constant::Record { type_, .. } if type_.is_nil() => Ok("undefined".to_doc()), 1629 1630 Constant::Record { 1631 args, 1632 module, 1633 name, 1634 tag, 1635 type_, 1636 .. 1637 } => { 1638 if type_.is_result() { 1639 if tag == "Ok" { 1640 tracker.ok_used = true; 1641 } else { 1642 tracker.error_used = true; 1643 } 1644 } 1645 1646 // If there's no arguments and the type is a function that takes 1647 // arguments then this is the constructor being referenced, not the 1648 // function being called. 1649 if let Some(arity) = type_.fn_arity() { 1650 if args.is_empty() && arity != 0 { 1651 let arity = arity as u16; 1652 return Ok(record_constructor( 1653 type_.clone(), 1654 None, 1655 name, 1656 arity, 1657 tracker, 1658 )); 1659 } 1660 } 1661 1662 let field_values: Vec<_> = args 1663 .iter() 1664 .map(|arg| constant_expression(context, tracker, &arg.value)) 1665 .try_collect()?; 1666 1667 let constructor = construct_record( 1668 module.as_ref().map(|(module, _)| module.as_str()), 1669 name, 1670 field_values, 1671 ); 1672 match context { 1673 Context::Constant => Ok(docvec!["/* @__PURE__ */ ", constructor]), 1674 Context::Function => Ok(constructor), 1675 } 1676 } 1677 1678 Constant::BitArray { segments, .. } => { 1679 let bit_array = bit_array(tracker, segments, |tracker, expr| { 1680 constant_expression(context, tracker, expr) 1681 })?; 1682 match context { 1683 Context::Constant => Ok(docvec!["/* @__PURE__ */ ", bit_array]), 1684 Context::Function => Ok(bit_array), 1685 } 1686 } 1687 1688 Constant::Var { name, module, .. } => Ok({ 1689 match module { 1690 None => maybe_escape_identifier(name).to_doc(), 1691 Some((module, _)) => { 1692 // JS keywords can be accessed here, but we must escape anyway 1693 // as we escape when exporting such names in the first place, 1694 // and the imported name has to match the exported name. 1695 docvec!["$", module, ".", maybe_escape_identifier(name)] 1696 } 1697 } 1698 }), 1699 1700 Constant::StringConcatenation { left, right, .. } => { 1701 let left = constant_expression(context, tracker, left)?; 1702 let right = constant_expression(context, tracker, right)?; 1703 Ok(docvec![left, " + ", right]) 1704 } 1705 1706 Constant::Invalid { .. } => panic!("invalid constants should not reach code generation"), 1707 } 1708} 1709 1710fn bit_array<'a>( 1711 tracker: &mut UsageTracker, 1712 segments: &'a [TypedConstantBitArraySegment], 1713 mut constant_expr_fun: impl FnMut(&mut UsageTracker, &'a TypedConstant) -> Output<'a>, 1714) -> Output<'a> { 1715 use BitArrayOption as Opt; 1716 1717 tracker.bit_array_literal_used = true; 1718 let segments_array = array(segments.iter().map(|segment| { 1719 let value = constant_expr_fun(tracker, &segment.value)?; 1720 1721 if segment.has_native_option() { 1722 return Err(Error::Unsupported { 1723 feature: "This bit array segment option".into(), 1724 location: segment.location, 1725 }); 1726 } 1727 1728 match segment.options.as_slice() { 1729 // Int segment 1730 _ if segment.type_.is_int() => { 1731 let details = 1732 sized_bit_array_segment_details(segment, tracker, &mut constant_expr_fun)?; 1733 match (details.size_value, segment.value.as_ref()) { 1734 (Some(size_value), Constant::Int { int_value, .. }) 1735 if size_value <= SAFE_INT_SEGMENT_MAX_SIZE.into() 1736 && (&size_value % BigInt::from(8) == BigInt::ZERO) => 1737 { 1738 let bytes = bit_array_segment_int_value_to_bytes( 1739 int_value.clone(), 1740 size_value, 1741 segment.endianness(), 1742 )?; 1743 1744 Ok(u8_slice(&bytes)) 1745 } 1746 1747 (Some(size_value), _) if size_value == 8.into() => Ok(value), 1748 1749 (Some(size_value), _) if size_value <= 0.into() => Ok(nil()), 1750 1751 _ => { 1752 tracker.sized_integer_segment_used = true; 1753 let size = details.size; 1754 let is_big = bool(segment.endianness().is_big()); 1755 Ok(docvec!["sizedInt(", value, ", ", size, ", ", is_big, ")"]) 1756 } 1757 } 1758 } 1759 1760 // Float segments 1761 _ if segment.type_.is_float() => { 1762 tracker.float_bit_array_segment_used = true; 1763 let details = 1764 sized_bit_array_segment_details(segment, tracker, &mut constant_expr_fun)?; 1765 let size = details.size; 1766 let is_big = bool(segment.endianness().is_big()); 1767 Ok(docvec!["sizedFloat(", value, ", ", size, ", ", is_big, ")"]) 1768 } 1769 1770 // UTF8 strings 1771 [Opt::Utf8 { .. }] => { 1772 tracker.string_bit_array_segment_used = true; 1773 Ok(docvec!["stringBits(", value, ")"]) 1774 } 1775 1776 // UTF8 codepoints 1777 [Opt::Utf8Codepoint { .. }] => { 1778 tracker.codepoint_bit_array_segment_used = true; 1779 Ok(docvec!["codepointBits(", value, ")"]) 1780 } 1781 1782 // Bit arrays 1783 [Opt::Bits { .. }] => Ok(value), 1784 1785 // Bit arrays with explicit size. The explicit size slices the bit array to the 1786 // specified size. A runtime exception is thrown if the size exceeds the number 1787 // of bits in the bit array. 1788 [Opt::Bits { .. }, Opt::Size { value: size, .. }] 1789 | [Opt::Size { value: size, .. }, Opt::Bits { .. }] => match &**size { 1790 Constant::Int { value: size, .. } => { 1791 tracker.bit_array_slice_used = true; 1792 Ok(docvec!["bitArraySlice(", value, ", 0, ", size, ")"]) 1793 } 1794 1795 _ => Err(Error::Unsupported { 1796 feature: "This bit array segment option".into(), 1797 location: segment.location, 1798 }), 1799 }, 1800 1801 // Anything else 1802 _ => Err(Error::Unsupported { 1803 feature: "This bit array segment option".into(), 1804 location: segment.location, 1805 }), 1806 } 1807 }))?; 1808 1809 Ok(docvec!["toBitArray(", segments_array, ")"]) 1810} 1811 1812#[derive(Debug)] 1813struct SizedBitArraySegmentDetails<'a> { 1814 size: Document<'a>, 1815 /// The size of the bit array segment stored as a BigInt. 1816 /// This has a value when the segment's size is known at compile time. 1817 size_value: Option<BigInt>, 1818} 1819 1820fn sized_bit_array_segment_details<'a>( 1821 segment: &'a TypedConstantBitArraySegment, 1822 tracker: &mut UsageTracker, 1823 constant_expr_fun: &mut impl FnMut(&mut UsageTracker, &'a TypedConstant) -> Output<'a>, 1824) -> Result<SizedBitArraySegmentDetails<'a>, Error> { 1825 let size = segment.size(); 1826 let unit = segment.unit(); 1827 let (size_value, size) = match size { 1828 Some(Constant::Int { int_value, .. }) => { 1829 let size_value = int_value * unit; 1830 let size = eco_format!("{}", size_value).to_doc(); 1831 (Some(size_value), size) 1832 } 1833 1834 Some(size) => { 1835 let mut size = constant_expr_fun(tracker, size)?; 1836 1837 if unit != 1 { 1838 size = size.group().append(" * ".to_doc().append(unit.to_doc())); 1839 } 1840 1841 (None, size) 1842 } 1843 1844 None => { 1845 let size_value: usize = if segment.type_.is_int() { 8 } else { 64 }; 1846 (Some(BigInt::from(size_value)), docvec![size_value]) 1847 } 1848 }; 1849 1850 Ok(SizedBitArraySegmentDetails { size, size_value }) 1851} 1852 1853pub fn string(value: &str) -> Document<'_> { 1854 if value.contains('\n') { 1855 EcoString::from(value.replace('\n', r"\n")) 1856 .to_doc() 1857 .surround("\"", "\"") 1858 } else { 1859 value.to_doc().surround("\"", "\"") 1860 } 1861} 1862 1863pub fn array<'a, Elements: IntoIterator<Item = Output<'a>>>(elements: Elements) -> Output<'a> { 1864 let elements = Itertools::intersperse(elements.into_iter(), Ok(break_(",", ", "))) 1865 .collect::<Result<Vec<_>, _>>()?; 1866 if elements.is_empty() { 1867 // Do not add a trailing comma since that adds an 'undefined' element 1868 Ok("[]".to_doc()) 1869 } else { 1870 Ok(docvec![ 1871 "[", 1872 docvec![break_("", ""), elements].nest(INDENT), 1873 break_(",", ""), 1874 "]" 1875 ] 1876 .group()) 1877 } 1878} 1879 1880fn list<'a, I: IntoIterator<Item = Output<'a>>>(elements: I) -> Output<'a> 1881where 1882 I::IntoIter: DoubleEndedIterator + ExactSizeIterator, 1883{ 1884 let array = array(elements); 1885 Ok(docvec!["toList(", array?, ")"]) 1886} 1887 1888fn prepend<'a, I: IntoIterator<Item = Output<'a>>>(elements: I, tail: Document<'a>) -> Output<'a> 1889where 1890 I::IntoIter: DoubleEndedIterator + ExactSizeIterator, 1891{ 1892 elements.into_iter().rev().try_fold(tail, |tail, element| { 1893 let args = call_arguments([element, Ok(tail)])?; 1894 Ok(docvec!["listPrepend", args]) 1895 }) 1896} 1897 1898fn call_arguments<'a, Elements: IntoIterator<Item = Output<'a>>>(elements: Elements) -> Output<'a> { 1899 let elements = Itertools::intersperse(elements.into_iter(), Ok(break_(",", ", "))) 1900 .collect::<Result<Vec<_>, _>>()? 1901 .to_doc(); 1902 if elements.is_empty() { 1903 return Ok("()".to_doc()); 1904 } 1905 Ok(docvec![ 1906 "(", 1907 docvec![break_("", ""), elements].nest(INDENT), 1908 break_(",", ""), 1909 ")" 1910 ] 1911 .group()) 1912} 1913 1914fn construct_record<'a>( 1915 module: Option<&'a str>, 1916 name: &'a str, 1917 arguments: impl IntoIterator<Item = Document<'a>>, 1918) -> Document<'a> { 1919 let mut any_arguments = false; 1920 let arguments = join( 1921 arguments.into_iter().inspect(|_| { 1922 any_arguments = true; 1923 }), 1924 break_(",", ", "), 1925 ); 1926 let arguments = docvec![break_("", ""), arguments].nest(INDENT); 1927 let name = if let Some(module) = module { 1928 docvec!["$", module, ".", name] 1929 } else { 1930 name.to_doc() 1931 }; 1932 if any_arguments { 1933 docvec!["new ", name, "(", arguments, break_(",", ""), ")"].group() 1934 } else { 1935 docvec!["new ", name, "()"] 1936 } 1937} 1938 1939impl TypedExpr { 1940 fn handles_own_return(&self) -> bool { 1941 match self { 1942 TypedExpr::Todo { .. } 1943 | TypedExpr::Call { .. } 1944 | TypedExpr::Case { .. } 1945 | TypedExpr::Panic { .. } 1946 | TypedExpr::Block { .. } 1947 | TypedExpr::Echo { .. } 1948 | TypedExpr::Pipeline { .. } 1949 | TypedExpr::RecordUpdate { .. } => true, 1950 1951 TypedExpr::Int { .. } 1952 | TypedExpr::Float { .. } 1953 | TypedExpr::String { .. } 1954 | TypedExpr::Var { .. } 1955 | TypedExpr::Fn { .. } 1956 | TypedExpr::List { .. } 1957 | TypedExpr::BinOp { .. } 1958 | TypedExpr::RecordAccess { .. } 1959 | TypedExpr::ModuleSelect { .. } 1960 | TypedExpr::Tuple { .. } 1961 | TypedExpr::TupleIndex { .. } 1962 | TypedExpr::BitArray { .. } 1963 | TypedExpr::NegateBool { .. } 1964 | TypedExpr::NegateInt { .. } 1965 | TypedExpr::Invalid { .. } => false, 1966 } 1967 } 1968} 1969 1970impl BinOp { 1971 fn is_operator_to_wrap(&self) -> bool { 1972 match self { 1973 BinOp::And 1974 | BinOp::Or 1975 | BinOp::Eq 1976 | BinOp::NotEq 1977 | BinOp::LtInt 1978 | BinOp::LtEqInt 1979 | BinOp::LtFloat 1980 | BinOp::LtEqFloat 1981 | BinOp::GtEqInt 1982 | BinOp::GtInt 1983 | BinOp::GtEqFloat 1984 | BinOp::GtFloat 1985 | BinOp::AddInt 1986 | BinOp::AddFloat 1987 | BinOp::SubInt 1988 | BinOp::SubFloat 1989 | BinOp::MultFloat 1990 | BinOp::DivInt 1991 | BinOp::DivFloat 1992 | BinOp::RemainderInt 1993 | BinOp::Concatenate => true, 1994 BinOp::MultInt => false, 1995 } 1996 } 1997} 1998 1999pub fn is_js_scalar(t: Arc<Type>) -> bool { 2000 t.is_int() || t.is_float() || t.is_bool() || t.is_nil() || t.is_string() 2001} 2002 2003fn requires_semicolon(statement: &TypedStatement) -> bool { 2004 match statement { 2005 Statement::Expression( 2006 TypedExpr::Int { .. } 2007 | TypedExpr::Fn { .. } 2008 | TypedExpr::Var { .. } 2009 | TypedExpr::List { .. } 2010 | TypedExpr::Call { .. } 2011 | TypedExpr::Echo { .. } 2012 | TypedExpr::Float { .. } 2013 | TypedExpr::String { .. } 2014 | TypedExpr::BinOp { .. } 2015 | TypedExpr::Tuple { .. } 2016 | TypedExpr::NegateInt { .. } 2017 | TypedExpr::BitArray { .. } 2018 | TypedExpr::TupleIndex { .. } 2019 | TypedExpr::NegateBool { .. } 2020 | TypedExpr::RecordAccess { .. } 2021 | TypedExpr::ModuleSelect { .. } 2022 | TypedExpr::Block { .. }, 2023 ) => true, 2024 2025 Statement::Expression( 2026 TypedExpr::Todo { .. } 2027 | TypedExpr::Case { .. } 2028 | TypedExpr::Panic { .. } 2029 | TypedExpr::Pipeline { .. } 2030 | TypedExpr::RecordUpdate { .. } 2031 | TypedExpr::Invalid { .. }, 2032 ) => false, 2033 2034 Statement::Assignment(_) => false, 2035 Statement::Use(_) => false, 2036 } 2037} 2038 2039/// Wrap a document in an immediately invoked function expression 2040fn immediately_invoked_function_expression_document(document: Document<'_>) -> Document<'_> { 2041 docvec![ 2042 docvec!["(() => {", break_("", " "), document].nest(INDENT), 2043 break_("", " "), 2044 "})()", 2045 ] 2046 .group() 2047} 2048 2049fn record_constructor<'a>( 2050 type_: Arc<Type>, 2051 qualifier: Option<&'a str>, 2052 name: &'a str, 2053 arity: u16, 2054 tracker: &mut UsageTracker, 2055) -> Document<'a> { 2056 if qualifier.is_none() && type_.is_result_constructor() { 2057 if name == "Ok" { 2058 tracker.ok_used = true; 2059 } else if name == "Error" { 2060 tracker.error_used = true; 2061 } 2062 } 2063 if type_.is_bool() && name == "True" { 2064 "true".to_doc() 2065 } else if type_.is_bool() { 2066 "false".to_doc() 2067 } else if type_.is_nil() { 2068 "undefined".to_doc() 2069 } else if arity == 0 { 2070 match qualifier { 2071 Some(module) => docvec!["new $", module, ".", name, "()"], 2072 None => docvec!["new ", name, "()"], 2073 } 2074 } else { 2075 let vars = (0..arity).map(|i| eco_format!("var{i}").to_doc()); 2076 let body = docvec![ 2077 "return ", 2078 construct_record(qualifier, name, vars.clone()), 2079 ";" 2080 ]; 2081 docvec![ 2082 docvec![wrap_args(vars), " => {", break_("", " "), body] 2083 .nest(INDENT) 2084 .append(break_("", " ")) 2085 .group(), 2086 "}", 2087 ] 2088 } 2089} 2090 2091fn u8_slice<'a>(bytes: &[u8]) -> Document<'a> { 2092 let s: EcoString = bytes 2093 .iter() 2094 .map(u8::to_string) 2095 .collect::<Vec<_>>() 2096 .join(", ") 2097 .into(); 2098 2099 docvec![s] 2100}