this repo has no description
at wasm 2586 lines 95 kB view raw
1use num_bigint::BigInt; 2use vec1::Vec1; 3 4use super::{decision::ASSIGNMENT_VAR, *}; 5use crate::{ 6 ast::*, 7 exhaustiveness::StringEncoding, 8 line_numbers::LineNumbers, 9 pretty::*, 10 type_::{ 11 ModuleValueConstructor, Type, TypedCallArg, ValueConstructor, ValueConstructorVariant, 12 }, 13}; 14use std::sync::Arc; 15 16#[derive(Debug, Clone)] 17pub enum Position { 18 /// We are compiling the last expression in a function, meaning that it should 19 /// use `return` to return the value it produces from the function. 20 Tail, 21 /// We are inside a function, but the value of this expression isn't being 22 /// used, so we don't need to do anything with the returned value. 23 Statement, 24 /// The value of this expression needs to be used inside another expression, 25 /// so we need to use the value that is returned by this expression. 26 Expression(Ordering), 27 /// We are compiling an expression inside a block, meaning we must assign 28 /// to the `_block` variable at the end of the scope, because blocks are not 29 /// expressions in JS. 30 /// Since JS doesn't have variable shadowing, we must store the name of the 31 /// variable being used, which will include the incrementing counter. 32 /// For example, `block$2` 33 Assign(EcoString), 34} 35 36impl Position { 37 /// Returns `true` if the position is [`Tail`]. 38 /// 39 /// [`Tail`]: Position::Tail 40 #[must_use] 41 pub fn is_tail(&self) -> bool { 42 matches!(self, Self::Tail) 43 } 44 45 #[must_use] 46 pub fn ordering(&self) -> Ordering { 47 match self { 48 Self::Expression(ordering) => *ordering, 49 Self::Tail | Self::Assign(_) | Self::Statement => Ordering::Loose, 50 } 51 } 52} 53 54#[derive(Debug, Clone, Copy)] 55/// Determines whether we can lift blocks into statement level instead of using 56/// immediately invoked function expressions. Consider the following piece of code: 57/// 58/// ```gleam 59/// some_function(function_with_side_effect(), { 60/// let a = 10 61/// other_function_with_side_effects(a) 62/// }) 63/// ``` 64/// Here, if we lift the block that is the second argument of the function, we 65/// would end up running `other_function_with_side_effects` before 66/// `function_with_side_effects`. This would be invalid, as code in Gleam should be 67/// evaluated left-to-right, top-to-bottom. In this case, the ordering would be 68/// `Strict`, indicating that we cannot lift the block. 69/// 70/// However, in this example: 71/// 72/// ```gleam 73/// let value = !{ 74/// let value = False 75/// some_function_with_side_effect() 76/// value 77/// } 78/// ``` 79/// The only expression is the block, meaning it can be safely lifted without 80/// changing the evaluation order of the program. So the ordering is `Loose`. 81/// 82pub enum Ordering { 83 Strict, 84 Loose, 85} 86 87/// Tracking where the current function is a module function or an anonymous function. 88#[derive(Debug)] 89enum CurrentFunction { 90 /// The current function is a module function 91 /// 92 /// ```gleam 93 /// pub fn main() -> Nil { 94 /// // we are here 95 /// } 96 /// ``` 97 Module, 98 99 /// The current function is a module function, but one of its arguments shadows 100 /// the reference to itself so it cannot recurse. 101 /// 102 /// ```gleam 103 /// pub fn main(main: fn() -> Nil) -> Nil { 104 /// // we are here 105 /// } 106 /// ``` 107 ModuleWithShadowingArgument, 108 109 /// The current function is an anonymous function 110 /// 111 /// ```gleam 112 /// pub fn main() -> Nil { 113 /// fn() { 114 /// // we are here 115 /// } 116 /// } 117 /// ``` 118 Anonymous, 119} 120 121impl CurrentFunction { 122 #[inline] 123 fn can_recurse(&self) -> bool { 124 match self { 125 CurrentFunction::Module => true, 126 CurrentFunction::ModuleWithShadowingArgument => false, 127 CurrentFunction::Anonymous => false, 128 } 129 } 130} 131 132#[derive(Debug)] 133pub(crate) struct Generator<'module, 'ast> { 134 module_name: EcoString, 135 src_path: EcoString, 136 line_numbers: &'module LineNumbers, 137 function_name: EcoString, 138 function_arguments: Vec<Option<&'module EcoString>>, 139 current_function: CurrentFunction, 140 pub current_scope_vars: im::HashMap<EcoString, usize>, 141 pub function_position: Position, 142 pub scope_position: Position, 143 // We register whether these features are used within an expression so that 144 // the module generator can output a suitable function if it is needed. 145 pub tracker: &'module mut UsageTracker, 146 // We track whether tail call recursion is used so that we can render a loop 147 // at the top level of the function to use in place of pushing new stack 148 // frames. 149 pub tail_recursion_used: bool, 150 /// Statements to be compiled when lifting blocks into statement scope. 151 /// For example, when compiling the following code: 152 /// ```gleam 153 /// let a = { 154 /// let b = 1 155 /// b + 1 156 /// } 157 /// ``` 158 /// There will be 2 items in `statement_level`: The first will be `let _block;` 159 /// The second will be the generated code for the block being assigned to `a`. 160 /// This lets use return `_block` as the value that the block evaluated to, 161 /// while still including the necessary code in the output at the right place. 162 /// 163 /// Once the `let` statement has compiled its value, it will add anything accumulated 164 /// in `statement_level` to the generated code, so it will result in: 165 /// 166 /// ```javascript 167 /// let _block; 168 /// {...} 169 /// let a = _block; 170 /// ``` 171 /// 172 statement_level: Vec<Document<'ast>>, 173 174 /// This will be true if we've generated a `let assert` statement that we know 175 /// is guaranteed to throw. 176 /// This means we can stop code generation for all the following statements 177 /// in the same block! 178 pub let_assert_always_panics: bool, 179} 180 181impl<'module, 'a> Generator<'module, 'a> { 182 #[allow(clippy::too_many_arguments)] // TODO: FIXME 183 pub fn new( 184 module_name: EcoString, 185 src_path: EcoString, 186 line_numbers: &'module LineNumbers, 187 function_name: EcoString, 188 function_arguments: Vec<Option<&'module EcoString>>, 189 tracker: &'module mut UsageTracker, 190 mut current_scope_vars: im::HashMap<EcoString, usize>, 191 ) -> Self { 192 let mut current_function = CurrentFunction::Module; 193 for &name in function_arguments.iter().flatten() { 194 // Initialise the function arguments 195 let _ = current_scope_vars.insert(name.clone(), 0); 196 197 // If any of the function arguments shadow the current function then 198 // recursion is no longer possible. 199 if function_name.as_ref() == name { 200 current_function = CurrentFunction::ModuleWithShadowingArgument; 201 } 202 } 203 Self { 204 tracker, 205 module_name, 206 src_path, 207 line_numbers, 208 function_name, 209 function_arguments, 210 tail_recursion_used: false, 211 current_scope_vars, 212 current_function, 213 function_position: Position::Tail, 214 scope_position: Position::Tail, 215 statement_level: Vec::new(), 216 let_assert_always_panics: false, 217 } 218 } 219 220 pub fn local_var(&mut self, name: &EcoString) -> EcoString { 221 match self.current_scope_vars.get(name) { 222 None => { 223 let _ = self.current_scope_vars.insert(name.clone(), 0); 224 maybe_escape_identifier(name) 225 } 226 Some(0) => maybe_escape_identifier(name), 227 Some(n) if name == "$" => eco_format!("${n}"), 228 Some(n) => eco_format!("{name}${n}"), 229 } 230 } 231 232 pub fn next_local_var(&mut self, name: &EcoString) -> EcoString { 233 let next = self.current_scope_vars.get(name).map_or(0, |i| i + 1); 234 let _ = self.current_scope_vars.insert(name.clone(), next); 235 self.local_var(name) 236 } 237 238 pub fn function_body( 239 &mut self, 240 body: &'a [TypedStatement], 241 arguments: &'a [TypedArg], 242 ) -> Document<'a> { 243 let body = self.statements(body); 244 if self.tail_recursion_used { 245 self.tail_call_loop(body, arguments) 246 } else { 247 body 248 } 249 } 250 251 fn tail_call_loop(&mut self, body: Document<'a>, arguments: &'a [TypedArg]) -> Document<'a> { 252 let loop_assignments = concat(arguments.iter().flat_map(Arg::get_variable_name).map( 253 |name| { 254 let var = maybe_escape_identifier(name); 255 docvec!["let ", var, " = loop$", name, ";", line()] 256 }, 257 )); 258 docvec![ 259 "while (true) {", 260 docvec![line(), loop_assignments, body].nest(INDENT), 261 line(), 262 "}" 263 ] 264 } 265 266 fn statement(&mut self, statement: &'a TypedStatement) -> Document<'a> { 267 let expression_doc = match statement { 268 Statement::Expression(expression) => self.expression(expression), 269 Statement::Assignment(assignment) => self.assignment(assignment), 270 Statement::Use(use_) => self.expression(&use_.call), 271 Statement::Assert(assert) => self.assert(assert), 272 }; 273 self.add_statement_level(expression_doc) 274 } 275 276 fn add_statement_level(&mut self, expression: Document<'a>) -> Document<'a> { 277 if self.statement_level.is_empty() { 278 expression 279 } else { 280 let mut statements = std::mem::take(&mut self.statement_level); 281 statements.push(expression); 282 join(statements, line()) 283 } 284 } 285 286 pub fn expression(&mut self, expression: &'a TypedExpr) -> Document<'a> { 287 let document = match expression { 288 TypedExpr::String { value, .. } => string(value), 289 290 TypedExpr::Int { value, .. } => int(value), 291 TypedExpr::Float { value, .. } => float(value), 292 293 TypedExpr::List { elements, tail, .. } => { 294 self.not_in_tail_position(Some(Ordering::Strict), |this| match tail { 295 Some(tail) => { 296 this.tracker.prepend_used = true; 297 let tail = this.wrap_expression(tail); 298 prepend( 299 elements.iter().map(|element| this.wrap_expression(element)), 300 tail, 301 ) 302 } 303 None => { 304 this.tracker.list_used = true; 305 list(elements.iter().map(|element| this.wrap_expression(element))) 306 } 307 }) 308 } 309 310 TypedExpr::Tuple { elements, .. } => self.tuple(elements), 311 TypedExpr::TupleIndex { tuple, index, .. } => self.tuple_index(tuple, *index), 312 313 TypedExpr::Case { 314 subjects, 315 clauses, 316 compiled_case, 317 .. 318 } => decision::case(compiled_case, clauses, subjects, self), 319 320 TypedExpr::Call { fun, arguments, .. } => self.call(fun, arguments), 321 TypedExpr::Fn { 322 arguments, body, .. 323 } => self.fn_(arguments, body), 324 325 TypedExpr::RecordAccess { record, label, .. } => self.record_access(record, label), 326 TypedExpr::RecordUpdate { 327 record_assignment, 328 constructor, 329 arguments, 330 .. 331 } => self.record_update(record_assignment, constructor, arguments), 332 333 TypedExpr::Var { 334 name, constructor, .. 335 } => self.variable(name, constructor), 336 337 TypedExpr::Pipeline { 338 first_value, 339 assignments, 340 finally, 341 .. 342 } => self.pipeline(first_value, assignments.as_slice(), finally), 343 344 TypedExpr::Block { statements, .. } => self.block(statements), 345 346 TypedExpr::BinOp { 347 name, left, right, .. 348 } => self.bin_op(name, left, right), 349 350 TypedExpr::Todo { 351 message, location, .. 352 } => self.todo(message.as_ref().map(|m| &**m), location), 353 354 TypedExpr::Panic { 355 location, message, .. 356 } => self.panic(location, message.as_ref().map(|m| &**m)), 357 358 TypedExpr::BitArray { segments, .. } => self.bit_array(segments), 359 360 TypedExpr::ModuleSelect { 361 module_alias, 362 label, 363 constructor, 364 .. 365 } => self.module_select(module_alias, label, constructor), 366 367 TypedExpr::NegateBool { value, .. } => self.negate_with("!", value), 368 369 TypedExpr::NegateInt { value, .. } => self.negate_with("- ", value), 370 371 TypedExpr::Echo { 372 expression, 373 message, 374 location, 375 .. 376 } => { 377 let expression = expression 378 .as_ref() 379 .expect("echo with no expression outside of pipe"); 380 let expresion_doc = 381 self.not_in_tail_position(None, |this| this.wrap_expression(expression)); 382 self.echo(expresion_doc, message.as_deref(), location) 383 } 384 385 TypedExpr::Invalid { .. } => { 386 panic!("invalid expressions should not reach code generation") 387 } 388 }; 389 if expression.handles_own_return() { 390 document 391 } else { 392 self.wrap_return(document) 393 } 394 } 395 396 fn negate_with(&mut self, with: &'static str, value: &'a TypedExpr) -> Document<'a> { 397 self.not_in_tail_position(None, |this| docvec![with, this.wrap_expression(value)]) 398 } 399 400 fn bit_array(&mut self, segments: &'a [TypedExprBitArraySegment]) -> Document<'a> { 401 self.tracker.bit_array_literal_used = true; 402 403 // Collect all the values used in segments. 404 let segments_array = array(segments.iter().map(|segment| { 405 let value = self.not_in_tail_position(Some(Ordering::Strict), |this| { 406 this.wrap_expression(&segment.value) 407 }); 408 409 let details = self.bit_array_segment_details(segment); 410 411 match details.type_ { 412 BitArraySegmentType::BitArray => { 413 if segment.size().is_some() { 414 self.tracker.bit_array_slice_used = true; 415 docvec!["bitArraySlice(", value, ", 0, ", details.size, ")"] 416 } else { 417 value 418 } 419 } 420 BitArraySegmentType::Int => match (details.size_value, segment.value.as_ref()) { 421 (Some(size_value), TypedExpr::Int { int_value, .. }) 422 if size_value <= SAFE_INT_SEGMENT_MAX_SIZE.into() 423 && (&size_value % BigInt::from(8) == BigInt::ZERO) => 424 { 425 let bytes = bit_array_segment_int_value_to_bytes( 426 int_value.clone(), 427 size_value, 428 segment.endianness(), 429 ); 430 431 u8_slice(&bytes) 432 } 433 434 (Some(size_value), _) if size_value == 8.into() => value, 435 436 (Some(size_value), _) if size_value <= 0.into() => nil(), 437 438 _ => { 439 self.tracker.sized_integer_segment_used = true; 440 let size = details.size; 441 let is_big = bool(segment.endianness().is_big()); 442 docvec!["sizedInt(", value, ", ", size, ", ", is_big, ")"] 443 } 444 }, 445 BitArraySegmentType::Float => { 446 self.tracker.float_bit_array_segment_used = true; 447 let size = details.size; 448 let is_big = bool(details.endianness.is_big()); 449 docvec!["sizedFloat(", value, ", ", size, ", ", is_big, ")"] 450 } 451 BitArraySegmentType::String(StringEncoding::Utf8) => { 452 self.tracker.string_bit_array_segment_used = true; 453 docvec!["stringBits(", value, ")"] 454 } 455 BitArraySegmentType::String(StringEncoding::Utf16) => { 456 self.tracker.string_utf16_bit_array_segment_used = true; 457 let is_big = bool(details.endianness.is_big()); 458 docvec!["stringToUtf16(", value, ", ", is_big, ")"] 459 } 460 BitArraySegmentType::String(StringEncoding::Utf32) => { 461 self.tracker.string_utf32_bit_array_segment_used = true; 462 let is_big = bool(details.endianness.is_big()); 463 docvec!["stringToUtf32(", value, ", ", is_big, ")"] 464 } 465 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf8) => { 466 self.tracker.codepoint_bit_array_segment_used = true; 467 docvec!["codepointBits(", value, ")"] 468 } 469 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf16) => { 470 self.tracker.codepoint_utf16_bit_array_segment_used = true; 471 let is_big = bool(details.endianness.is_big()); 472 docvec!["codepointToUtf16(", value, ", ", is_big, ")"] 473 } 474 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf32) => { 475 self.tracker.codepoint_utf32_bit_array_segment_used = true; 476 let is_big = bool(details.endianness.is_big()); 477 docvec!["codepointToUtf32(", value, ", ", is_big, ")"] 478 } 479 } 480 })); 481 482 docvec!["toBitArray(", segments_array, ")"] 483 } 484 485 fn bit_array_segment_details( 486 &mut self, 487 segment: &'a TypedExprBitArraySegment, 488 ) -> BitArraySegmentDetails<'a> { 489 let size = segment.size(); 490 let unit = segment.unit(); 491 let (size_value, size) = match size { 492 Some(TypedExpr::Int { int_value, .. }) => { 493 let size_value = int_value * unit; 494 let size = eco_format!("{}", size_value).to_doc(); 495 (Some(size_value), size) 496 } 497 Some(size) => { 498 let mut size = self.not_in_tail_position(Some(Ordering::Strict), |this| { 499 this.wrap_expression(size) 500 }); 501 502 if unit != 1 { 503 size = size.group().append(" * ".to_doc().append(unit.to_doc())); 504 } 505 506 (None, size) 507 } 508 509 None => { 510 let size_value: usize = if segment.type_.is_int() { 8 } else { 64 }; 511 (Some(BigInt::from(size_value)), docvec![size_value]) 512 } 513 }; 514 515 let type_ = BitArraySegmentType::from_segment(segment); 516 517 BitArraySegmentDetails { 518 type_, 519 size, 520 size_value, 521 endianness: segment.endianness(), 522 } 523 } 524 525 pub fn wrap_return(&mut self, document: Document<'a>) -> Document<'a> { 526 match &self.scope_position { 527 Position::Tail => docvec!["return ", document, ";"], 528 Position::Expression(_) | Position::Statement => document, 529 Position::Assign(name) => docvec![name.clone(), " = ", document, ";"], 530 } 531 } 532 533 pub fn not_in_tail_position<CompileFn, Output>( 534 &mut self, 535 // If ordering is None, it is inherited from the parent scope. 536 // It will be None in cases like `!x`, where `x` can be lifted 537 // only if the ordering is already loose. 538 ordering: Option<Ordering>, 539 compile: CompileFn, 540 ) -> Output 541 where 542 CompileFn: Fn(&mut Self) -> Output, 543 { 544 let new_ordering = ordering.unwrap_or(self.scope_position.ordering()); 545 546 let function_position = std::mem::replace( 547 &mut self.function_position, 548 Position::Expression(new_ordering), 549 ); 550 let scope_position = 551 std::mem::replace(&mut self.scope_position, Position::Expression(new_ordering)); 552 553 let result = compile(self); 554 555 self.function_position = function_position; 556 self.scope_position = scope_position; 557 result 558 } 559 560 /// Use the `_block` variable if the expression is JS statement. 561 pub fn wrap_expression(&mut self, expression: &'a TypedExpr) -> Document<'a> { 562 match (expression, &self.scope_position) { 563 (_, Position::Tail | Position::Assign(_)) => self.expression(expression), 564 ( 565 TypedExpr::Panic { .. } 566 | TypedExpr::Todo { .. } 567 | TypedExpr::Case { .. } 568 | TypedExpr::Pipeline { .. } 569 | TypedExpr::RecordUpdate { 570 // Record updates that assign a variable generate multiple statements 571 record_assignment: Some(_), 572 .. 573 }, 574 Position::Expression(Ordering::Loose), 575 ) => self.wrap_block(|this| this.expression(expression)), 576 ( 577 TypedExpr::Panic { .. } 578 | TypedExpr::Todo { .. } 579 | TypedExpr::Case { .. } 580 | TypedExpr::Pipeline { .. } 581 | TypedExpr::RecordUpdate { 582 // Record updates that assign a variable generate multiple statements 583 record_assignment: Some(_), 584 .. 585 }, 586 Position::Expression(Ordering::Strict), 587 ) => self.immediately_invoked_function_expression(expression, |this, expr| { 588 this.expression(expr) 589 }), 590 _ => self.expression(expression), 591 } 592 } 593 594 /// Wrap an expression using the `_block` variable if required due to being 595 /// a JS statement, or in parens if required due to being an operator or 596 /// a function literal. 597 pub fn child_expression(&mut self, expression: &'a TypedExpr) -> Document<'a> { 598 match expression { 599 TypedExpr::BinOp { name, .. } if name.is_operator_to_wrap() => {} 600 TypedExpr::Fn { .. } => {} 601 602 _ => return self.wrap_expression(expression), 603 } 604 605 let document = self.expression(expression); 606 match &self.scope_position { 607 // Here the document is a return statement: `return <expr>;` 608 // or an assignment: `_block = <expr>;` 609 Position::Tail | Position::Assign(_) | Position::Statement => document, 610 Position::Expression(_) => docvec!["(", document, ")"], 611 } 612 } 613 614 /// Wrap an expression in an immediately invoked function expression 615 fn immediately_invoked_function_expression<T, ToDoc>( 616 &mut self, 617 statements: &'a T, 618 to_doc: ToDoc, 619 ) -> Document<'a> 620 where 621 ToDoc: FnOnce(&mut Self, &'a T) -> Document<'a>, 622 { 623 // Save initial state 624 let scope_position = std::mem::replace(&mut self.scope_position, Position::Tail); 625 let statement_level = std::mem::take(&mut self.statement_level); 626 627 // Set state for in this iife 628 let current_scope_vars = self.current_scope_vars.clone(); 629 630 // Generate the expression 631 let result = to_doc(self, statements); 632 let doc = self.add_statement_level(result); 633 let doc = immediately_invoked_function_expression_document(doc); 634 635 // Reset 636 self.current_scope_vars = current_scope_vars; 637 self.scope_position = scope_position; 638 self.statement_level = statement_level; 639 640 self.wrap_return(doc) 641 } 642 643 fn wrap_block<CompileFn>(&mut self, compile: CompileFn) -> Document<'a> 644 where 645 CompileFn: Fn(&mut Self) -> Document<'a>, 646 { 647 let block_variable = self.next_local_var(&BLOCK_VARIABLE.into()); 648 649 // Save initial state 650 let scope_position = std::mem::replace( 651 &mut self.scope_position, 652 Position::Assign(block_variable.clone()), 653 ); 654 let function_position = std::mem::replace( 655 &mut self.function_position, 656 Position::Expression(Ordering::Strict), 657 ); 658 659 // Generate the expression 660 let statement_doc = compile(self); 661 662 // Reset 663 self.scope_position = scope_position; 664 self.function_position = function_position; 665 666 self.statement_level 667 .push(docvec!["let ", block_variable.clone(), ";"]); 668 self.statement_level.push(statement_doc); 669 670 self.wrap_return(block_variable.to_doc()) 671 } 672 673 fn variable(&mut self, name: &'a EcoString, constructor: &'a ValueConstructor) -> Document<'a> { 674 match &constructor.variant { 675 ValueConstructorVariant::LocalConstant { literal } => { 676 self.constant_expression(Context::Function, literal) 677 } 678 ValueConstructorVariant::Record { arity, .. } => { 679 let type_ = constructor.type_.clone(); 680 let tracker = &mut self.tracker; 681 record_constructor(type_, None, name, *arity, tracker) 682 } 683 ValueConstructorVariant::ModuleFn { .. } 684 | ValueConstructorVariant::ModuleConstant { .. } 685 | ValueConstructorVariant::LocalVariable { .. } => self.local_var(name).to_doc(), 686 } 687 } 688 689 fn pipeline( 690 &mut self, 691 first_value: &'a TypedPipelineAssignment, 692 assignments: &'a [(TypedPipelineAssignment, PipelineAssignmentKind)], 693 finally: &'a TypedExpr, 694 ) -> Document<'a> { 695 let count = assignments.len(); 696 let mut documents = Vec::with_capacity((count + 2) * 2); 697 698 let all_assignments = std::iter::once(first_value) 699 .chain(assignments.iter().map(|(assignment, _kind)| assignment)); 700 701 let mut latest_local_var: Option<EcoString> = None; 702 for assignment in all_assignments { 703 match assignment.value.as_ref() { 704 // An echo in a pipeline won't result in an assignment, instead it 705 // just prints the previous variable assigned in the pipeline. 706 TypedExpr::Echo { 707 expression: None, 708 message, 709 location, 710 .. 711 } => documents.push(self.not_in_tail_position(Some(Ordering::Strict), |this| { 712 let var = latest_local_var 713 .as_ref() 714 .expect("echo with no previous step in a pipe"); 715 this.echo(var.to_doc(), message.as_deref(), location) 716 })), 717 718 // Otherwise we assign the intermediate pipe value to a variable. 719 _ => { 720 let assignment_document = self 721 .not_in_tail_position(Some(Ordering::Strict), |this| { 722 this.simple_variable_assignment(&assignment.name, &assignment.value) 723 }); 724 documents.push(self.add_statement_level(assignment_document)); 725 latest_local_var = Some(self.local_var(&assignment.name)); 726 } 727 } 728 729 documents.push(line()); 730 } 731 732 match finally { 733 TypedExpr::Echo { 734 expression: None, 735 message, 736 location, 737 .. 738 } => { 739 let var = latest_local_var.expect("echo with no previous step in a pipe"); 740 documents.push(self.echo(var.to_doc(), message.as_deref(), location)); 741 } 742 _ => { 743 let finally = self.expression(finally); 744 documents.push(self.add_statement_level(finally)) 745 } 746 } 747 748 documents.to_doc().force_break() 749 } 750 751 pub(crate) fn expression_flattening_blocks( 752 &mut self, 753 expression: &'a TypedExpr, 754 ) -> Document<'a> { 755 match expression { 756 TypedExpr::Block { statements, .. } => self.statements(statements), 757 _ => { 758 let expression_document = self.expression(expression); 759 self.add_statement_level(expression_document) 760 } 761 } 762 } 763 764 fn block(&mut self, statements: &'a Vec1<TypedStatement>) -> Document<'a> { 765 if statements.len() == 1 { 766 match statements.first() { 767 Statement::Expression(expression) => return self.child_expression(expression), 768 769 Statement::Assignment(assignment) => match &assignment.kind { 770 AssignmentKind::Let | AssignmentKind::Generated => { 771 return self.child_expression(&assignment.value); 772 } 773 // We can't just return the right-hand side of a `let assert` 774 // assignment; we still need to check that the pattern matches. 775 AssignmentKind::Assert { .. } => {} 776 }, 777 778 Statement::Use(use_) => return self.child_expression(&use_.call), 779 780 // Similar to `let assert`, we can't immediately return the value 781 // that is asserted; we have to actually perform the assertion. 782 Statement::Assert(_) => {} 783 } 784 } 785 match &self.scope_position { 786 Position::Tail | Position::Assign(_) | Position::Statement => { 787 self.block_document(statements) 788 } 789 Position::Expression(Ordering::Strict) => self 790 .immediately_invoked_function_expression(statements, |this, statements| { 791 this.statements(statements) 792 }), 793 Position::Expression(Ordering::Loose) => self.wrap_block(|this| { 794 // Save previous scope 795 let current_scope_vars = this.current_scope_vars.clone(); 796 797 let document = this.block_document(statements); 798 799 // Restore previous state 800 this.current_scope_vars = current_scope_vars; 801 802 document 803 }), 804 } 805 } 806 807 fn block_document(&mut self, statements: &'a Vec1<TypedStatement>) -> Document<'a> { 808 let statements = self.statements(statements); 809 docvec!["{", docvec![line(), statements].nest(INDENT), line(), "}"] 810 } 811 812 fn statements(&mut self, statements: &'a [TypedStatement]) -> Document<'a> { 813 // If there are any statements that need to be printed at statement level, that's 814 // for an outer scope so we don't want to print them inside this one. 815 let statement_level = std::mem::take(&mut self.statement_level); 816 let count = statements.len(); 817 let mut documents = Vec::with_capacity(count * 3); 818 for (i, statement) in statements.iter().enumerate() { 819 if i + 1 < count { 820 let function_position = 821 std::mem::replace(&mut self.function_position, Position::Statement); 822 let scope_position = 823 std::mem::replace(&mut self.scope_position, Position::Statement); 824 825 documents.push(self.statement(statement)); 826 827 self.function_position = function_position; 828 self.scope_position = scope_position; 829 830 if requires_semicolon(statement) { 831 documents.push(";".to_doc()); 832 } 833 documents.push(line()); 834 } else { 835 documents.push(self.statement(statement)); 836 } 837 838 // If we've generated code for a statement that always throws, we 839 // can skip code generation for all the following ones. 840 if self.let_assert_always_panics { 841 self.let_assert_always_panics = false; 842 break; 843 } 844 } 845 self.statement_level = statement_level; 846 if count == 1 { 847 documents.to_doc() 848 } else { 849 documents.to_doc().force_break() 850 } 851 } 852 853 fn simple_variable_assignment( 854 &mut self, 855 name: &'a EcoString, 856 value: &'a TypedExpr, 857 ) -> Document<'a> { 858 // Subject must be rendered before the variable for variable numbering 859 let subject = 860 self.not_in_tail_position(Some(Ordering::Loose), |this| this.wrap_expression(value)); 861 let js_name = self.next_local_var(name); 862 let assignment = docvec!["let ", js_name.clone(), " = ", subject, ";"]; 863 let assignment = match &self.scope_position { 864 Position::Expression(_) | Position::Statement => assignment, 865 Position::Tail => docvec![assignment, line(), "return ", js_name, ";"], 866 Position::Assign(block_variable) => docvec![ 867 assignment, 868 line(), 869 block_variable.clone(), 870 " = ", 871 js_name, 872 ";" 873 ], 874 }; 875 876 assignment.force_break() 877 } 878 879 fn assignment(&mut self, assignment: &'a TypedAssignment) -> Document<'a> { 880 let TypedAssignment { 881 pattern, 882 kind, 883 value, 884 compiled_case, 885 annotation: _, 886 location: _, 887 } = assignment; 888 889 // In case the pattern is just a variable, we special case it to 890 // generate just a simple assignment instead of using the decision tree 891 // for the code generation step. 892 if let TypedPattern::Variable { name, .. } = pattern { 893 return self.simple_variable_assignment(name, value); 894 } 895 896 decision::let_(compiled_case, value, kind, self, pattern) 897 } 898 899 fn assert(&mut self, assert: &'a TypedAssert) -> Document<'a> { 900 let TypedAssert { 901 location, 902 value, 903 message, 904 } = assert; 905 906 let message = match message { 907 Some(m) => self.not_in_tail_position( 908 Some(Ordering::Strict), 909 |this: &mut Generator<'module, 'a>| this.expression(m), 910 ), 911 None => string("Assertion failed."), 912 }; 913 914 let check = self.not_in_tail_position(Some(Ordering::Loose), |this| { 915 this.assert_check(value, &message, *location) 916 }); 917 918 match &self.scope_position { 919 Position::Expression(_) | Position::Statement => check, 920 Position::Tail | Position::Assign(_) => { 921 docvec![check, line(), self.wrap_return("undefined".to_doc())] 922 } 923 } 924 } 925 926 fn assert_check( 927 &mut self, 928 subject: &'a TypedExpr, 929 message: &Document<'a>, 930 location: SrcSpan, 931 ) -> Document<'a> { 932 let (subject_document, mut fields) = match subject { 933 TypedExpr::Call { fun, arguments, .. } => { 934 let argument_variables = arguments 935 .iter() 936 .map(|element| { 937 self.not_in_tail_position(Some(Ordering::Strict), |this| { 938 this.assign_to_variable(&element.value) 939 }) 940 }) 941 .collect_vec(); 942 ( 943 self.call_with_doc_arguments(fun, argument_variables.clone()), 944 vec![ 945 ("kind", string("function_call")), 946 ( 947 "arguments", 948 array(argument_variables.into_iter().zip(arguments).map( 949 |(variable, argument)| { 950 self.asserted_expression( 951 AssertExpression::from_expression(&argument.value), 952 Some(variable), 953 argument.location(), 954 ) 955 }, 956 )), 957 ), 958 ], 959 ) 960 } 961 962 TypedExpr::BinOp { 963 name, left, right, .. 964 } => { 965 match name { 966 BinOp::And => return self.assert_and(left, right, message, location), 967 BinOp::Or => return self.assert_or(left, right, message, location), 968 _ => {} 969 } 970 971 let left_document = self.not_in_tail_position(Some(Ordering::Loose), |this| { 972 this.assign_to_variable(left) 973 }); 974 let right_document = self.not_in_tail_position(Some(Ordering::Loose), |this| { 975 this.assign_to_variable(right) 976 }); 977 978 ( 979 self.bin_op_with_doc_operands( 980 *name, 981 left_document.clone(), 982 right_document.clone(), 983 &left.type_(), 984 ) 985 .surround("(", ")"), 986 vec![ 987 ("kind", string("binary_operator")), 988 ("operator", string(name.name())), 989 ( 990 "left", 991 self.asserted_expression( 992 AssertExpression::from_expression(left), 993 Some(left_document), 994 left.location(), 995 ), 996 ), 997 ( 998 "right", 999 self.asserted_expression( 1000 AssertExpression::from_expression(right), 1001 Some(right_document), 1002 right.location(), 1003 ), 1004 ), 1005 ], 1006 ) 1007 } 1008 1009 _ => ( 1010 self.wrap_expression(subject), 1011 vec![ 1012 ("kind", string("expression")), 1013 ( 1014 "expression", 1015 self.asserted_expression( 1016 AssertExpression::from_expression(subject), 1017 Some("false".to_doc()), 1018 subject.location(), 1019 ), 1020 ), 1021 ], 1022 ), 1023 }; 1024 1025 fields.push(("start", location.start.to_doc())); 1026 fields.push(("end", subject.location().end.to_doc())); 1027 fields.push(("expression_start", subject.location().start.to_doc())); 1028 1029 docvec![ 1030 "if (", 1031 docvec!["!", subject_document].nest(INDENT), 1032 break_("", ""), 1033 ") {", 1034 docvec![ 1035 line(), 1036 self.throw_error("assert", message, location, fields), 1037 ] 1038 .nest(INDENT), 1039 line(), 1040 "}", 1041 ] 1042 .group() 1043 } 1044 1045 /// In Gleam, the `&&` operator is short-circuiting, meaning that we can't 1046 /// pre-evaluate both sides of it, and use them in the exception that is 1047 /// thrown. 1048 /// Instead, we need to implement this short-circuiting logic ourself. 1049 /// 1050 /// If we short-circuit, we must leave the second expression unevaluated, 1051 /// and signal that using the `unevaluated` variant, as detailed in the 1052 /// exception format. For the first expression, we know it must be `false`, 1053 /// otherwise we would have continued by evaluating the second expression. 1054 /// 1055 /// Similarly, if we do evaluate the second expression and fail, we know 1056 /// that the first expression must have evaluated to `true`, and the second 1057 /// to `false`. This way, we avoid needing to evaluate either expression 1058 /// twice. 1059 /// 1060 /// The generated code then looks something like this: 1061 /// ```javascript 1062 /// if (expr1) { 1063 /// if (!expr2) { 1064 /// <throw exception> 1065 /// } 1066 /// } else { 1067 /// <throw exception> 1068 /// } 1069 /// ``` 1070 /// 1071 fn assert_and( 1072 &mut self, 1073 left: &'a TypedExpr, 1074 right: &'a TypedExpr, 1075 message: &Document<'a>, 1076 location: SrcSpan, 1077 ) -> Document<'a> { 1078 let left_kind = AssertExpression::from_expression(left); 1079 let right_kind = AssertExpression::from_expression(right); 1080 1081 let fields_if_short_circuiting = vec![ 1082 ("kind", string("binary_operator")), 1083 ("operator", string("&&")), 1084 ( 1085 "left", 1086 self.asserted_expression(left_kind, Some("false".to_doc()), left.location()), 1087 ), 1088 ( 1089 "right", 1090 self.asserted_expression(AssertExpression::Unevaluated, None, right.location()), 1091 ), 1092 ("start", location.start.to_doc()), 1093 ("end", right.location().end.to_doc()), 1094 ("expression_start", left.location().start.to_doc()), 1095 ]; 1096 1097 let fields = vec![ 1098 ("kind", string("binary_operator")), 1099 ("operator", string("&&")), 1100 ( 1101 "left", 1102 self.asserted_expression(left_kind, Some("true".to_doc()), left.location()), 1103 ), 1104 ( 1105 "right", 1106 self.asserted_expression(right_kind, Some("false".to_doc()), right.location()), 1107 ), 1108 ("start", location.start.to_doc()), 1109 ("end", right.location().end.to_doc()), 1110 ("expression_start", left.location().start.to_doc()), 1111 ]; 1112 1113 let left_value = 1114 self.not_in_tail_position(Some(Ordering::Loose), |this| this.wrap_expression(left)); 1115 1116 let right_value = 1117 self.not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(right)); 1118 1119 let right_check = docvec![ 1120 line(), 1121 "if (", 1122 docvec!["!", right_value].nest(INDENT), 1123 ") {", 1124 docvec![ 1125 line(), 1126 self.throw_error("assert", message, location, fields) 1127 ] 1128 .nest(INDENT), 1129 line(), 1130 "}", 1131 ]; 1132 1133 docvec![ 1134 "if (", 1135 left_value.nest(INDENT), 1136 ") {", 1137 right_check.nest(INDENT), 1138 line(), 1139 "} else {", 1140 docvec![ 1141 line(), 1142 self.throw_error("assert", message, location, fields_if_short_circuiting) 1143 ] 1144 .nest(INDENT), 1145 line(), 1146 "}" 1147 ] 1148 } 1149 1150 /// Similar to `&&`, `||` is also short-circuiting in Gleam. However, if `||` 1151 /// short-circuits, that's because the first expression evaluated to `true`, 1152 /// meaning the whole assertion succeeds. This allows us to directly use the 1153 /// `||` operator in JavaScript. 1154 /// 1155 /// The only difference is that due to the nature of `||`, if the assertion fails, 1156 /// we know that both sides must have evaluated to `false`, so we don't 1157 /// need to store the values of them in variables beforehand. 1158 fn assert_or( 1159 &mut self, 1160 left: &'a TypedExpr, 1161 right: &'a TypedExpr, 1162 message: &Document<'a>, 1163 location: SrcSpan, 1164 ) -> Document<'a> { 1165 let fields = vec![ 1166 ("kind", string("binary_operator")), 1167 ("operator", string("||")), 1168 ( 1169 "left", 1170 self.asserted_expression( 1171 AssertExpression::from_expression(left), 1172 Some("false".to_doc()), 1173 left.location(), 1174 ), 1175 ), 1176 ( 1177 "right", 1178 self.asserted_expression( 1179 AssertExpression::from_expression(right), 1180 Some("false".to_doc()), 1181 right.location(), 1182 ), 1183 ), 1184 ("start", location.start.to_doc()), 1185 ("end", right.location().end.to_doc()), 1186 ("expression_start", left.location().start.to_doc()), 1187 ]; 1188 1189 let left_value = 1190 self.not_in_tail_position(Some(Ordering::Loose), |this| this.child_expression(left)); 1191 1192 let right_value = 1193 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right)); 1194 1195 docvec![ 1196 line(), 1197 "if (", 1198 docvec!["!(", left_value, " || ", right_value, ")"].nest(INDENT), 1199 ") {", 1200 docvec![ 1201 line(), 1202 self.throw_error("assert", message, location, fields) 1203 ] 1204 .nest(INDENT), 1205 line(), 1206 "}", 1207 ] 1208 } 1209 1210 fn assign_to_variable(&mut self, value: &'a TypedExpr) -> Document<'a> { 1211 match value { 1212 TypedExpr::Var { .. } => self.expression(value), 1213 _ => { 1214 let value = self.wrap_expression(value); 1215 let variable = self.next_local_var(&ASSIGNMENT_VAR.into()); 1216 let assignment = docvec!["let ", variable.clone(), " = ", value, ";"]; 1217 self.statement_level.push(assignment); 1218 variable.to_doc() 1219 } 1220 } 1221 } 1222 1223 fn asserted_expression( 1224 &mut self, 1225 kind: AssertExpression, 1226 value: Option<Document<'a>>, 1227 location: SrcSpan, 1228 ) -> Document<'a> { 1229 let kind = match kind { 1230 AssertExpression::Literal => string("literal"), 1231 AssertExpression::Expression => string("expression"), 1232 AssertExpression::Unevaluated => string("unevaluated"), 1233 }; 1234 1235 let start = location.start.to_doc(); 1236 let end = location.end.to_doc(); 1237 let items = if let Some(value) = value { 1238 vec![ 1239 ("kind", kind), 1240 ("value", value), 1241 ("start", start), 1242 ("end", end), 1243 ] 1244 } else { 1245 vec![("kind", kind), ("start", start), ("end", end)] 1246 }; 1247 1248 wrap_object( 1249 items 1250 .into_iter() 1251 .map(|(key, value)| (key.to_doc(), Some(value))), 1252 ) 1253 } 1254 1255 fn tuple(&mut self, elements: &'a [TypedExpr]) -> Document<'a> { 1256 self.not_in_tail_position(Some(Ordering::Strict), |this| { 1257 array(elements.iter().map(|element| this.wrap_expression(element))) 1258 }) 1259 } 1260 1261 fn call(&mut self, fun: &'a TypedExpr, arguments: &'a [TypedCallArg]) -> Document<'a> { 1262 let arguments = arguments 1263 .iter() 1264 .map(|element| { 1265 self.not_in_tail_position(Some(Ordering::Strict), |this| { 1266 this.wrap_expression(&element.value) 1267 }) 1268 }) 1269 .collect_vec(); 1270 1271 self.call_with_doc_arguments(fun, arguments) 1272 } 1273 1274 fn call_with_doc_arguments( 1275 &mut self, 1276 fun: &'a TypedExpr, 1277 arguments: Vec<Document<'a>>, 1278 ) -> Document<'a> { 1279 match fun { 1280 // Qualified record construction 1281 TypedExpr::ModuleSelect { 1282 constructor: ModuleValueConstructor::Record { name, .. }, 1283 module_alias, 1284 .. 1285 } => self.wrap_return(construct_record(Some(module_alias), name, arguments)), 1286 1287 // Record construction 1288 TypedExpr::Var { 1289 constructor: 1290 ValueConstructor { 1291 variant: ValueConstructorVariant::Record { .. }, 1292 type_, 1293 .. 1294 }, 1295 name, 1296 .. 1297 } => { 1298 if type_.is_result_constructor() { 1299 if name == "Ok" { 1300 self.tracker.ok_used = true; 1301 } else if name == "Error" { 1302 self.tracker.error_used = true; 1303 } 1304 } 1305 self.wrap_return(construct_record(None, name, arguments)) 1306 } 1307 1308 // Tail call optimisation. If we are calling the current function 1309 // and we are in tail position we can avoid creating a new stack 1310 // frame, enabling recursion with constant memory usage. 1311 TypedExpr::Var { name, .. } 1312 if self.function_name == *name 1313 && self.current_function.can_recurse() 1314 && self.function_position.is_tail() 1315 && self.current_scope_vars.get(name) == Some(&0) => 1316 { 1317 let mut docs = Vec::with_capacity(arguments.len() * 4); 1318 // Record that tail recursion is happening so that we know to 1319 // render the loop at the top level of the function. 1320 self.tail_recursion_used = true; 1321 1322 for (i, (element, argument)) in arguments 1323 .into_iter() 1324 .zip(&self.function_arguments) 1325 .enumerate() 1326 { 1327 if i != 0 { 1328 docs.push(line()); 1329 } 1330 // Create an assignment for each variable created by the function arguments 1331 if let Some(name) = argument { 1332 docs.push("loop$".to_doc()); 1333 docs.push(name.to_doc()); 1334 docs.push(" = ".to_doc()); 1335 } 1336 // Render the value given to the function. Even if it is not 1337 // assigned we still render it because the expression may 1338 // have some side effects. 1339 docs.push(element); 1340 docs.push(";".to_doc()); 1341 } 1342 docs.to_doc() 1343 } 1344 1345 _ => { 1346 let fun = self.not_in_tail_position(None, |this| -> Document<'_> { 1347 let is_fn_literal = matches!(fun, TypedExpr::Fn { .. }); 1348 let fun = this.wrap_expression(fun); 1349 if is_fn_literal { 1350 docvec!["(", fun, ")"] 1351 } else { 1352 fun 1353 } 1354 }); 1355 let arguments = call_arguments(arguments); 1356 self.wrap_return(docvec![fun, arguments]) 1357 } 1358 } 1359 } 1360 1361 fn fn_(&mut self, arguments: &'a [TypedArg], body: &'a [TypedStatement]) -> Document<'a> { 1362 // New function, this is now the tail position 1363 let function_position = std::mem::replace(&mut self.function_position, Position::Tail); 1364 let scope_position = std::mem::replace(&mut self.scope_position, Position::Tail); 1365 1366 // And there's a new scope 1367 let scope = self.current_scope_vars.clone(); 1368 for name in arguments.iter().flat_map(Arg::get_variable_name) { 1369 let _ = self.current_scope_vars.insert(name.clone(), 0); 1370 } 1371 1372 // This is a new function so track that so that we don't 1373 // mistakenly trigger tail call optimisation 1374 let mut current_function = CurrentFunction::Anonymous; 1375 std::mem::swap(&mut self.current_function, &mut current_function); 1376 1377 // Generate the function body 1378 let result = self.statements(body); 1379 1380 // Reset function name, scope, and tail position tracking 1381 self.function_position = function_position; 1382 self.scope_position = scope_position; 1383 self.current_scope_vars = scope; 1384 std::mem::swap(&mut self.current_function, &mut current_function); 1385 1386 docvec![ 1387 docvec![ 1388 fun_arguments(arguments, false), 1389 " => {", 1390 break_("", " "), 1391 result 1392 ] 1393 .nest(INDENT) 1394 .append(break_("", " ")) 1395 .group(), 1396 "}", 1397 ] 1398 } 1399 1400 fn record_access(&mut self, record: &'a TypedExpr, label: &'a str) -> Document<'a> { 1401 self.not_in_tail_position(None, |this| { 1402 let record = this.wrap_expression(record); 1403 docvec![record, ".", maybe_escape_property(label)] 1404 }) 1405 } 1406 1407 fn record_update( 1408 &mut self, 1409 record: &'a Option<Box<TypedAssignment>>, 1410 constructor: &'a TypedExpr, 1411 arguments: &'a [TypedCallArg], 1412 ) -> Document<'a> { 1413 match record.as_ref() { 1414 Some(record) => docvec![ 1415 self.not_in_tail_position(None, |this| this.assignment(record)), 1416 line(), 1417 self.call(constructor, arguments), 1418 ], 1419 None => self.call(constructor, arguments), 1420 } 1421 } 1422 1423 fn tuple_index(&mut self, tuple: &'a TypedExpr, index: u64) -> Document<'a> { 1424 self.not_in_tail_position(None, |this| { 1425 let tuple = this.wrap_expression(tuple); 1426 docvec![tuple, eco_format!("[{index}]")] 1427 }) 1428 } 1429 1430 fn bin_op( 1431 &mut self, 1432 name: &'a BinOp, 1433 left: &'a TypedExpr, 1434 right: &'a TypedExpr, 1435 ) -> Document<'a> { 1436 match name { 1437 BinOp::And => self.print_bin_op(left, right, "&&"), 1438 BinOp::Or => self.print_bin_op(left, right, "||"), 1439 BinOp::LtInt | BinOp::LtFloat => self.print_bin_op(left, right, "<"), 1440 BinOp::LtEqInt | BinOp::LtEqFloat => self.print_bin_op(left, right, "<="), 1441 BinOp::Eq => self.equal(left, right, true), 1442 BinOp::NotEq => self.equal(left, right, false), 1443 BinOp::GtInt | BinOp::GtFloat => self.print_bin_op(left, right, ">"), 1444 BinOp::GtEqInt | BinOp::GtEqFloat => self.print_bin_op(left, right, ">="), 1445 BinOp::Concatenate | BinOp::AddInt | BinOp::AddFloat => { 1446 self.print_bin_op(left, right, "+") 1447 } 1448 BinOp::SubInt | BinOp::SubFloat => self.print_bin_op(left, right, "-"), 1449 BinOp::MultInt | BinOp::MultFloat => self.print_bin_op(left, right, "*"), 1450 BinOp::RemainderInt => self.remainder_int(left, right), 1451 BinOp::DivInt => self.div_int(left, right), 1452 BinOp::DivFloat => self.div_float(left, right), 1453 } 1454 } 1455 1456 fn div_int(&mut self, left: &'a TypedExpr, right: &'a TypedExpr) -> Document<'a> { 1457 let left_doc = 1458 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left)); 1459 let right_doc = 1460 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right)); 1461 1462 // If we have a constant value divided by zero then it's safe to replace 1463 // it directly with 0. 1464 if left.is_literal() && right.zero_compile_time_number() { 1465 "0".to_doc() 1466 } else if right.non_zero_compile_time_number() { 1467 let division = if let TypedExpr::BinOp { .. } = left { 1468 docvec![left_doc.surround("(", ")"), " / ", right_doc] 1469 } else { 1470 docvec![left_doc, " / ", right_doc] 1471 }; 1472 docvec!["globalThis.Math.trunc", wrap_arguments([division])] 1473 } else { 1474 self.tracker.int_division_used = true; 1475 docvec!["divideInt", wrap_arguments([left_doc, right_doc])] 1476 } 1477 } 1478 1479 fn remainder_int(&mut self, left: &'a TypedExpr, right: &'a TypedExpr) -> Document<'a> { 1480 let left_doc = 1481 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left)); 1482 let right_doc = 1483 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right)); 1484 1485 // If we have a constant value divided by zero then it's safe to replace 1486 // it directly with 0. 1487 if left.is_literal() && right.zero_compile_time_number() { 1488 "0".to_doc() 1489 } else if right.non_zero_compile_time_number() { 1490 if let TypedExpr::BinOp { .. } = left { 1491 docvec![left_doc.surround("(", ")"), " % ", right_doc] 1492 } else { 1493 docvec![left_doc, " % ", right_doc] 1494 } 1495 } else { 1496 self.tracker.int_remainder_used = true; 1497 docvec!["remainderInt", wrap_arguments([left_doc, right_doc])] 1498 } 1499 } 1500 1501 fn div_float(&mut self, left: &'a TypedExpr, right: &'a TypedExpr) -> Document<'a> { 1502 let left_doc = 1503 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left)); 1504 let right_doc = 1505 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right)); 1506 1507 // If we have a constant value divided by zero then it's safe to replace 1508 // it directly with 0. 1509 if left.is_literal() && right.zero_compile_time_number() { 1510 "0.0".to_doc() 1511 } else if right.non_zero_compile_time_number() { 1512 if let TypedExpr::BinOp { .. } = left { 1513 docvec![left_doc.surround("(", ")"), " / ", right_doc] 1514 } else { 1515 docvec![left_doc, " / ", right_doc] 1516 } 1517 } else { 1518 self.tracker.float_division_used = true; 1519 docvec!["divideFloat", wrap_arguments([left_doc, right_doc])] 1520 } 1521 } 1522 1523 fn equal( 1524 &mut self, 1525 left: &'a TypedExpr, 1526 right: &'a TypedExpr, 1527 should_be_equal: bool, 1528 ) -> Document<'a> { 1529 // If it is a simple scalar type then we can use JS' reference identity 1530 if is_js_scalar(left.type_()) { 1531 let left_doc = self 1532 .not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left)); 1533 let right_doc = self 1534 .not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right)); 1535 let operator = if should_be_equal { " === " } else { " !== " }; 1536 return docvec![left_doc, operator, right_doc]; 1537 } 1538 1539 // Other types must be compared using structural equality 1540 let left = 1541 self.not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(left)); 1542 let right = 1543 self.not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(right)); 1544 self.prelude_equal_call(should_be_equal, left, right) 1545 } 1546 1547 fn equal_with_doc_operands( 1548 &mut self, 1549 left: Document<'a>, 1550 right: Document<'a>, 1551 type_: Arc<Type>, 1552 should_be_equal: bool, 1553 ) -> Document<'a> { 1554 // If it is a simple scalar type then we can use JS' reference identity 1555 if is_js_scalar(type_) { 1556 let operator = if should_be_equal { " === " } else { " !== " }; 1557 return docvec![left, operator, right]; 1558 } 1559 1560 // Other types must be compared using structural equality 1561 self.prelude_equal_call(should_be_equal, left, right) 1562 } 1563 1564 pub(super) fn prelude_equal_call( 1565 &mut self, 1566 should_be_equal: bool, 1567 left: Document<'a>, 1568 right: Document<'a>, 1569 ) -> Document<'a> { 1570 // Record that we need to import the prelude's isEqual function into the module 1571 self.tracker.object_equality_used = true; 1572 // Construct the call 1573 let arguments = wrap_arguments([left, right]); 1574 let operator = if should_be_equal { 1575 "isEqual" 1576 } else { 1577 "!isEqual" 1578 }; 1579 docvec![operator, arguments] 1580 } 1581 1582 fn print_bin_op( 1583 &mut self, 1584 left: &'a TypedExpr, 1585 right: &'a TypedExpr, 1586 op: &'a str, 1587 ) -> Document<'a> { 1588 let left = 1589 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left)); 1590 let right = 1591 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right)); 1592 docvec![left, " ", op, " ", right] 1593 } 1594 1595 pub(super) fn bin_op_with_doc_operands( 1596 &mut self, 1597 name: BinOp, 1598 left: Document<'a>, 1599 right: Document<'a>, 1600 type_: &Arc<Type>, 1601 ) -> Document<'a> { 1602 match name { 1603 BinOp::And => docvec![left, " && ", right], 1604 BinOp::Or => docvec![left, " || ", right], 1605 BinOp::LtInt | BinOp::LtFloat => docvec![left, " < ", right], 1606 BinOp::LtEqInt | BinOp::LtEqFloat => docvec![left, " <= ", right], 1607 BinOp::Eq => self.equal_with_doc_operands(left, right, type_.clone(), true), 1608 BinOp::NotEq => self.equal_with_doc_operands(left, right, type_.clone(), false), 1609 BinOp::GtInt | BinOp::GtFloat => docvec![left, " > ", right], 1610 BinOp::GtEqInt | BinOp::GtEqFloat => docvec![left, " >= ", right], 1611 BinOp::Concatenate | BinOp::AddInt | BinOp::AddFloat => { 1612 docvec![left, " + ", right] 1613 } 1614 BinOp::SubInt | BinOp::SubFloat => docvec![left, " - ", right], 1615 BinOp::MultInt | BinOp::MultFloat => docvec![left, " * ", right], 1616 BinOp::RemainderInt => { 1617 self.tracker.int_remainder_used = true; 1618 docvec!["remainderInt", wrap_arguments([left, right])] 1619 } 1620 BinOp::DivInt => { 1621 self.tracker.int_division_used = true; 1622 docvec!["divideInt", wrap_arguments([left, right])] 1623 } 1624 BinOp::DivFloat => { 1625 self.tracker.float_division_used = true; 1626 docvec!["divideFloat", wrap_arguments([left, right])] 1627 } 1628 } 1629 } 1630 1631 fn todo(&mut self, message: Option<&'a TypedExpr>, location: &'a SrcSpan) -> Document<'a> { 1632 let message = match message { 1633 Some(m) => self.not_in_tail_position(None, |this| this.wrap_expression(m)), 1634 None => string("`todo` expression evaluated. This code has not yet been implemented."), 1635 }; 1636 self.throw_error("todo", &message, *location, vec![]) 1637 } 1638 1639 fn panic(&mut self, location: &'a SrcSpan, message: Option<&'a TypedExpr>) -> Document<'a> { 1640 let message = match message { 1641 Some(m) => self.not_in_tail_position(None, |this| this.wrap_expression(m)), 1642 None => string("`panic` expression evaluated."), 1643 }; 1644 self.throw_error("panic", &message, *location, vec![]) 1645 } 1646 1647 pub(crate) fn throw_error<Fields>( 1648 &mut self, 1649 error_name: &'a str, 1650 message: &Document<'a>, 1651 location: SrcSpan, 1652 fields: Fields, 1653 ) -> Document<'a> 1654 where 1655 Fields: IntoIterator<Item = (&'a str, Document<'a>)>, 1656 { 1657 self.tracker.make_error_used = true; 1658 let module = self.module_name.clone().to_doc().surround('"', '"'); 1659 let function = self.function_name.clone().to_doc().surround("\"", "\""); 1660 let line = self.line_numbers.line_number(location.start).to_doc(); 1661 let fields = wrap_object(fields.into_iter().map(|(k, v)| (k.to_doc(), Some(v)))); 1662 1663 docvec![ 1664 "throw makeError", 1665 wrap_arguments([ 1666 string(error_name), 1667 "FILEPATH".to_doc(), 1668 module, 1669 line, 1670 function, 1671 message.clone(), 1672 fields 1673 ]), 1674 ] 1675 } 1676 1677 fn module_select( 1678 &mut self, 1679 module: &'a str, 1680 label: &'a EcoString, 1681 constructor: &'a ModuleValueConstructor, 1682 ) -> Document<'a> { 1683 match constructor { 1684 ModuleValueConstructor::Fn { .. } | ModuleValueConstructor::Constant { .. } => { 1685 docvec!["$", module, ".", maybe_escape_identifier(label)] 1686 } 1687 1688 ModuleValueConstructor::Record { 1689 name, arity, type_, .. 1690 } => record_constructor(type_.clone(), Some(module), name, *arity, self.tracker), 1691 } 1692 } 1693 1694 fn echo( 1695 &mut self, 1696 expression: Document<'a>, 1697 message: Option<&'a TypedExpr>, 1698 location: &'a SrcSpan, 1699 ) -> Document<'a> { 1700 self.tracker.echo_used = true; 1701 1702 let message = match message { 1703 Some(message) => self 1704 .not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(message)), 1705 None => "undefined".to_doc(), 1706 }; 1707 1708 let echo_arguments = call_arguments(vec![ 1709 expression, 1710 message, 1711 self.src_path.clone().to_doc(), 1712 self.line_numbers.line_number(location.start).to_doc(), 1713 ]); 1714 self.wrap_return(docvec!["echo", echo_arguments]) 1715 } 1716 1717 pub(crate) fn constant_expression( 1718 &mut self, 1719 context: Context, 1720 expression: &'a TypedConstant, 1721 ) -> Document<'a> { 1722 match expression { 1723 Constant::Int { value, .. } => int(value), 1724 Constant::Float { value, .. } => float(value), 1725 Constant::String { value, .. } => string(value), 1726 Constant::Tuple { elements, .. } => array( 1727 elements 1728 .iter() 1729 .map(|element| self.constant_expression(context, element)), 1730 ), 1731 1732 Constant::List { elements, .. } => { 1733 self.tracker.list_used = true; 1734 let list = list( 1735 elements 1736 .iter() 1737 .map(|element| self.constant_expression(context, element)), 1738 ); 1739 1740 match context { 1741 Context::Constant => docvec!["/* @__PURE__ */ ", list], 1742 Context::Function => list, 1743 } 1744 } 1745 1746 Constant::Record { type_, name, .. } if type_.is_bool() && name == "True" => { 1747 "true".to_doc() 1748 } 1749 Constant::Record { type_, name, .. } if type_.is_bool() && name == "False" => { 1750 "false".to_doc() 1751 } 1752 Constant::Record { type_, .. } if type_.is_nil() => "undefined".to_doc(), 1753 1754 Constant::Record { 1755 arguments, 1756 module, 1757 name, 1758 tag, 1759 type_, 1760 .. 1761 } => { 1762 if module.is_none() && type_.is_result() { 1763 if tag == "Ok" { 1764 self.tracker.ok_used = true; 1765 } else { 1766 self.tracker.error_used = true; 1767 } 1768 } 1769 1770 // If there's no arguments and the type is a function that takes 1771 // arguments then this is the constructor being referenced, not the 1772 // function being called. 1773 if let Some(arity) = type_.fn_arity() 1774 && arguments.is_empty() 1775 && arity != 0 1776 { 1777 let arity = arity as u16; 1778 return record_constructor(type_.clone(), None, name, arity, self.tracker); 1779 } 1780 1781 let field_values = arguments 1782 .iter() 1783 .map(|argument| self.constant_expression(context, &argument.value)) 1784 .collect_vec(); 1785 1786 let constructor = construct_record( 1787 module.as_ref().map(|(module, _)| module.as_str()), 1788 name, 1789 field_values, 1790 ); 1791 match context { 1792 Context::Constant => docvec!["/* @__PURE__ */ ", constructor], 1793 Context::Function => constructor, 1794 } 1795 } 1796 1797 Constant::BitArray { segments, .. } => { 1798 let bit_array = self.constant_bit_array(segments, context); 1799 match context { 1800 Context::Constant => docvec!["/* @__PURE__ */ ", bit_array], 1801 Context::Function => bit_array, 1802 } 1803 } 1804 1805 Constant::Var { name, module, .. } => { 1806 match module { 1807 None => maybe_escape_identifier(name).to_doc(), 1808 Some((module, _)) => { 1809 // JS keywords can be accessed here, but we must escape anyway 1810 // as we escape when exporting such names in the first place, 1811 // and the imported name has to match the exported name. 1812 docvec!["$", module, ".", maybe_escape_identifier(name)] 1813 } 1814 } 1815 } 1816 1817 Constant::StringConcatenation { left, right, .. } => { 1818 let left = self.constant_expression(context, left); 1819 let right = self.constant_expression(context, right); 1820 docvec![left, " + ", right] 1821 } 1822 1823 Constant::Invalid { .. } => { 1824 panic!("invalid constants should not reach code generation") 1825 } 1826 } 1827 } 1828 1829 fn constant_bit_array( 1830 &mut self, 1831 segments: &'a [TypedConstantBitArraySegment], 1832 context: Context, 1833 ) -> Document<'a> { 1834 self.tracker.bit_array_literal_used = true; 1835 let segments_array = array(segments.iter().map(|segment| { 1836 let value = self.constant_expression(Context::Constant, &segment.value); 1837 1838 let details = self.constant_bit_array_segment_details(segment, context); 1839 1840 match details.type_ { 1841 BitArraySegmentType::BitArray => { 1842 if segment.size().is_some() { 1843 self.tracker.bit_array_slice_used = true; 1844 docvec!["bitArraySlice(", value, ", 0, ", details.size, ")"] 1845 } else { 1846 value 1847 } 1848 } 1849 BitArraySegmentType::Int => match (details.size_value, segment.value.as_ref()) { 1850 (Some(size_value), Constant::Int { int_value, .. }) 1851 if size_value <= SAFE_INT_SEGMENT_MAX_SIZE.into() 1852 && (&size_value % BigInt::from(8) == BigInt::ZERO) => 1853 { 1854 let bytes = bit_array_segment_int_value_to_bytes( 1855 int_value.clone(), 1856 size_value, 1857 segment.endianness(), 1858 ); 1859 1860 u8_slice(&bytes) 1861 } 1862 1863 (Some(size_value), _) if size_value == 8.into() => value, 1864 1865 (Some(size_value), _) if size_value <= 0.into() => nil(), 1866 1867 _ => { 1868 self.tracker.sized_integer_segment_used = true; 1869 let size = details.size; 1870 let is_big = bool(segment.endianness().is_big()); 1871 docvec!["sizedInt(", value, ", ", size, ", ", is_big, ")"] 1872 } 1873 }, 1874 BitArraySegmentType::Float => { 1875 self.tracker.float_bit_array_segment_used = true; 1876 let size = details.size; 1877 let is_big = bool(details.endianness.is_big()); 1878 docvec!["sizedFloat(", value, ", ", size, ", ", is_big, ")"] 1879 } 1880 BitArraySegmentType::String(StringEncoding::Utf8) => { 1881 self.tracker.string_bit_array_segment_used = true; 1882 docvec!["stringBits(", value, ")"] 1883 } 1884 BitArraySegmentType::String(StringEncoding::Utf16) => { 1885 self.tracker.string_utf16_bit_array_segment_used = true; 1886 let is_big = bool(details.endianness.is_big()); 1887 docvec!["stringToUtf16(", value, ", ", is_big, ")"] 1888 } 1889 BitArraySegmentType::String(StringEncoding::Utf32) => { 1890 self.tracker.string_utf32_bit_array_segment_used = true; 1891 let is_big = bool(details.endianness.is_big()); 1892 docvec!["stringToUtf32(", value, ", ", is_big, ")"] 1893 } 1894 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf8) => { 1895 self.tracker.codepoint_bit_array_segment_used = true; 1896 docvec!["codepointBits(", value, ")"] 1897 } 1898 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf16) => { 1899 self.tracker.codepoint_utf16_bit_array_segment_used = true; 1900 let is_big = bool(details.endianness.is_big()); 1901 docvec!["codepointToUtf16(", value, ", ", is_big, ")"] 1902 } 1903 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf32) => { 1904 self.tracker.codepoint_utf32_bit_array_segment_used = true; 1905 let is_big = bool(details.endianness.is_big()); 1906 docvec!["codepointToUtf32(", value, ", ", is_big, ")"] 1907 } 1908 } 1909 })); 1910 1911 docvec!["toBitArray(", segments_array, ")"] 1912 } 1913 1914 fn constant_bit_array_segment_details( 1915 &mut self, 1916 segment: &'a TypedConstantBitArraySegment, 1917 context: Context, 1918 ) -> BitArraySegmentDetails<'a> { 1919 let size = segment.size(); 1920 let unit = segment.unit(); 1921 let (size_value, size) = match size { 1922 Some(Constant::Int { int_value, .. }) => { 1923 let size_value = int_value * unit; 1924 let size = eco_format!("{}", size_value).to_doc(); 1925 (Some(size_value), size) 1926 } 1927 1928 Some(size) => { 1929 let mut size = self.constant_expression(context, size); 1930 if unit != 1 { 1931 size = size.group().append(" * ".to_doc().append(unit.to_doc())); 1932 } 1933 1934 (None, size) 1935 } 1936 1937 None => { 1938 let size_value: usize = if segment.type_.is_int() { 8 } else { 64 }; 1939 (Some(BigInt::from(size_value)), docvec![size_value]) 1940 } 1941 }; 1942 1943 let type_ = BitArraySegmentType::from_segment(segment); 1944 1945 BitArraySegmentDetails { 1946 type_, 1947 size, 1948 size_value, 1949 endianness: segment.endianness(), 1950 } 1951 } 1952 1953 pub(crate) fn guard(&mut self, guard: &'a TypedClauseGuard) -> Document<'a> { 1954 match guard { 1955 ClauseGuard::Block { value, .. } => self.guard(value).surround("(", ")"), 1956 1957 ClauseGuard::Equals { left, right, .. } if is_js_scalar(left.type_()) => { 1958 let left = self.wrapped_guard(left); 1959 let right = self.wrapped_guard(right); 1960 docvec![left, " === ", right] 1961 } 1962 1963 ClauseGuard::NotEquals { left, right, .. } if is_js_scalar(left.type_()) => { 1964 let left = self.wrapped_guard(left); 1965 let right = self.wrapped_guard(right); 1966 docvec![left, " !== ", right] 1967 } 1968 1969 ClauseGuard::Equals { left, right, .. } => { 1970 let left = self.guard(left); 1971 let right = self.guard(right); 1972 self.prelude_equal_call(true, left, right) 1973 } 1974 1975 ClauseGuard::NotEquals { left, right, .. } => { 1976 let left = self.guard(left); 1977 let right = self.guard(right); 1978 self.prelude_equal_call(false, left, right) 1979 } 1980 1981 ClauseGuard::GtFloat { left, right, .. } | ClauseGuard::GtInt { left, right, .. } => { 1982 let left = self.wrapped_guard(left); 1983 let right = self.wrapped_guard(right); 1984 docvec![left, " > ", right] 1985 } 1986 1987 ClauseGuard::GtEqFloat { left, right, .. } 1988 | ClauseGuard::GtEqInt { left, right, .. } => { 1989 let left = self.wrapped_guard(left); 1990 let right = self.wrapped_guard(right); 1991 docvec![left, " >= ", right] 1992 } 1993 1994 ClauseGuard::LtFloat { left, right, .. } | ClauseGuard::LtInt { left, right, .. } => { 1995 let left = self.wrapped_guard(left); 1996 let right = self.wrapped_guard(right); 1997 docvec![left, " < ", right] 1998 } 1999 2000 ClauseGuard::LtEqFloat { left, right, .. } 2001 | ClauseGuard::LtEqInt { left, right, .. } => { 2002 let left = self.wrapped_guard(left); 2003 let right = self.wrapped_guard(right); 2004 docvec![left, " <= ", right] 2005 } 2006 2007 ClauseGuard::AddFloat { left, right, .. } | ClauseGuard::AddInt { left, right, .. } => { 2008 let left = self.wrapped_guard(left); 2009 let right = self.wrapped_guard(right); 2010 docvec![left, " + ", right] 2011 } 2012 2013 ClauseGuard::SubFloat { left, right, .. } | ClauseGuard::SubInt { left, right, .. } => { 2014 let left = self.wrapped_guard(left); 2015 let right = self.wrapped_guard(right); 2016 docvec![left, " - ", right] 2017 } 2018 2019 ClauseGuard::MultFloat { left, right, .. } 2020 | ClauseGuard::MultInt { left, right, .. } => { 2021 let left = self.wrapped_guard(left); 2022 let right = self.wrapped_guard(right); 2023 docvec![left, " * ", right] 2024 } 2025 2026 ClauseGuard::DivFloat { left, right, .. } => { 2027 let left = self.wrapped_guard(left); 2028 let right = self.wrapped_guard(right); 2029 self.tracker.float_division_used = true; 2030 docvec!["divideFloat", wrap_arguments([left, right])] 2031 } 2032 2033 ClauseGuard::DivInt { left, right, .. } => { 2034 let left = self.wrapped_guard(left); 2035 let right = self.wrapped_guard(right); 2036 self.tracker.int_division_used = true; 2037 docvec!["divideInt", wrap_arguments([left, right])] 2038 } 2039 2040 ClauseGuard::RemainderInt { left, right, .. } => { 2041 let left = self.wrapped_guard(left); 2042 let right = self.wrapped_guard(right); 2043 self.tracker.int_remainder_used = true; 2044 docvec!["remainderInt", wrap_arguments([left, right])] 2045 } 2046 2047 ClauseGuard::Or { left, right, .. } => { 2048 let left = self.wrapped_guard(left); 2049 let right = self.wrapped_guard(right); 2050 docvec![left, " || ", right] 2051 } 2052 2053 ClauseGuard::And { left, right, .. } => { 2054 let left = self.wrapped_guard(left); 2055 let right = self.wrapped_guard(right); 2056 docvec![left, " && ", right] 2057 } 2058 2059 ClauseGuard::Var { name, .. } => self.local_var(name).to_doc(), 2060 2061 ClauseGuard::TupleIndex { tuple, index, .. } => { 2062 docvec![self.guard(tuple,), "[", index, "]"] 2063 } 2064 2065 ClauseGuard::FieldAccess { 2066 label, container, .. 2067 } => docvec![self.guard(container), ".", maybe_escape_property(label)], 2068 2069 ClauseGuard::ModuleSelect { 2070 module_alias, 2071 label, 2072 .. 2073 } => docvec!["$", module_alias, ".", label], 2074 2075 ClauseGuard::Not { expression, .. } => docvec!["!", self.guard(expression,)], 2076 2077 ClauseGuard::Constant(constant) => self.guard_constant_expression(constant), 2078 } 2079 } 2080 2081 fn wrapped_guard(&mut self, guard: &'a TypedClauseGuard) -> Document<'a> { 2082 match guard { 2083 ClauseGuard::Var { .. } 2084 | ClauseGuard::TupleIndex { .. } 2085 | ClauseGuard::Constant(_) 2086 | ClauseGuard::Not { .. } 2087 | ClauseGuard::FieldAccess { .. } 2088 | ClauseGuard::Block { .. } => self.guard(guard), 2089 2090 ClauseGuard::Equals { .. } 2091 | ClauseGuard::NotEquals { .. } 2092 | ClauseGuard::GtInt { .. } 2093 | ClauseGuard::GtEqInt { .. } 2094 | ClauseGuard::LtInt { .. } 2095 | ClauseGuard::LtEqInt { .. } 2096 | ClauseGuard::GtFloat { .. } 2097 | ClauseGuard::GtEqFloat { .. } 2098 | ClauseGuard::LtFloat { .. } 2099 | ClauseGuard::LtEqFloat { .. } 2100 | ClauseGuard::AddInt { .. } 2101 | ClauseGuard::AddFloat { .. } 2102 | ClauseGuard::SubInt { .. } 2103 | ClauseGuard::SubFloat { .. } 2104 | ClauseGuard::MultInt { .. } 2105 | ClauseGuard::MultFloat { .. } 2106 | ClauseGuard::DivInt { .. } 2107 | ClauseGuard::DivFloat { .. } 2108 | ClauseGuard::RemainderInt { .. } 2109 | ClauseGuard::Or { .. } 2110 | ClauseGuard::And { .. } 2111 | ClauseGuard::ModuleSelect { .. } => docvec!["(", self.guard(guard), ")"], 2112 } 2113 } 2114 2115 fn guard_constant_expression(&mut self, expression: &'a TypedConstant) -> Document<'a> { 2116 match expression { 2117 Constant::Tuple { elements, .. } => array( 2118 elements 2119 .iter() 2120 .map(|element| self.guard_constant_expression(element)), 2121 ), 2122 2123 Constant::List { elements, .. } => { 2124 self.tracker.list_used = true; 2125 list( 2126 elements 2127 .iter() 2128 .map(|element| self.guard_constant_expression(element)), 2129 ) 2130 } 2131 Constant::Record { type_, name, .. } if type_.is_bool() && name == "True" => { 2132 "true".to_doc() 2133 } 2134 Constant::Record { type_, name, .. } if type_.is_bool() && name == "False" => { 2135 "false".to_doc() 2136 } 2137 Constant::Record { type_, .. } if type_.is_nil() => "undefined".to_doc(), 2138 2139 Constant::Record { 2140 arguments, 2141 module, 2142 name, 2143 tag, 2144 type_, 2145 .. 2146 } => { 2147 if module.is_none() && type_.is_result() { 2148 if tag == "Ok" { 2149 self.tracker.ok_used = true; 2150 } else { 2151 self.tracker.error_used = true; 2152 } 2153 } 2154 2155 // If there's no arguments and the type is a function that takes 2156 // arguments then this is the constructor being referenced, not the 2157 // function being called. 2158 if let Some(arity) = type_.fn_arity() 2159 && arguments.is_empty() 2160 && arity != 0 2161 { 2162 let arity = arity as u16; 2163 return record_constructor(type_.clone(), None, name, arity, self.tracker); 2164 } 2165 2166 let field_values = arguments 2167 .iter() 2168 .map(|argument| self.guard_constant_expression(&argument.value)) 2169 .collect_vec(); 2170 construct_record( 2171 module.as_ref().map(|(module, _)| module.as_str()), 2172 name, 2173 field_values, 2174 ) 2175 } 2176 2177 Constant::BitArray { segments, .. } => { 2178 self.constant_bit_array(segments, Context::Function) 2179 } 2180 2181 Constant::Var { name, .. } => self.local_var(name).to_doc(), 2182 2183 expression => self.constant_expression(Context::Function, expression), 2184 } 2185 } 2186} 2187 2188#[derive(Clone, Copy)] 2189enum AssertExpression { 2190 Literal, 2191 Expression, 2192 Unevaluated, 2193} 2194 2195impl AssertExpression { 2196 fn from_expression(expression: &TypedExpr) -> Self { 2197 if expression.is_literal() { 2198 Self::Literal 2199 } else { 2200 Self::Expression 2201 } 2202 } 2203} 2204 2205pub fn int(value: &str) -> Document<'_> { 2206 eco_string_int(value.into()) 2207} 2208 2209pub fn eco_string_int<'a>(value: EcoString) -> Document<'a> { 2210 let mut out = EcoString::with_capacity(value.len()); 2211 2212 if value.starts_with('-') { 2213 out.push('-'); 2214 } else if value.starts_with('+') { 2215 out.push('+'); 2216 }; 2217 let value = value.trim_start_matches(['+', '-'].as_ref()); 2218 2219 let value = if value.starts_with("0x") { 2220 out.push_str("0x"); 2221 value.trim_start_matches("0x") 2222 } else if value.starts_with("0o") { 2223 out.push_str("0o"); 2224 value.trim_start_matches("0o") 2225 } else if value.starts_with("0b") { 2226 out.push_str("0b"); 2227 value.trim_start_matches("0b") 2228 } else { 2229 value 2230 }; 2231 2232 let value = value.trim_start_matches('0'); 2233 if value.is_empty() { 2234 out.push('0'); 2235 } 2236 2237 // If the number starts with a `0` then an underscore, the `0` will be stripped, 2238 // leaving the number to look something like `_1_2_3`, which is not valid syntax. 2239 // Therefore, we strip the `_` to avoid this case. 2240 let value = value.trim_start_matches('_'); 2241 2242 out.push_str(value); 2243 2244 out.to_doc() 2245} 2246 2247pub fn float(value: &str) -> Document<'_> { 2248 let mut out = EcoString::with_capacity(value.len()); 2249 2250 if value.starts_with('-') { 2251 out.push('-'); 2252 } else if value.starts_with('+') { 2253 out.push('+'); 2254 }; 2255 let value = value.trim_start_matches(['+', '-'].as_ref()); 2256 2257 let value = value.trim_start_matches('0'); 2258 if value.starts_with(['.', 'e', 'E']) { 2259 out.push('0'); 2260 } 2261 out.push_str(value); 2262 2263 out.to_doc() 2264} 2265 2266/// The context where the constant expression is used, it might be inside a 2267/// function call, or in the definition of another constant. 2268/// 2269/// Based on the context we might want to annotate pure function calls as 2270/// "@__PURE__". 2271/// 2272#[derive(Debug, Clone, Copy)] 2273pub enum Context { 2274 Constant, 2275 Function, 2276} 2277 2278#[derive(Debug)] 2279struct BitArraySegmentDetails<'a> { 2280 type_: BitArraySegmentType, 2281 size: Document<'a>, 2282 /// The size of the bit array segment stored as a BigInt. 2283 /// This has a value when the segment's size is known at compile time. 2284 size_value: Option<BigInt>, 2285 endianness: Endianness, 2286} 2287 2288#[derive(Debug, Clone, Copy)] 2289enum BitArraySegmentType { 2290 BitArray, 2291 Int, 2292 Float, 2293 String(StringEncoding), 2294 UtfCodepoint(StringEncoding), 2295} 2296 2297impl BitArraySegmentType { 2298 fn from_segment<Value>(segment: &BitArraySegment<Value, Arc<Type>>) -> Self { 2299 if segment.type_.is_int() { 2300 BitArraySegmentType::Int 2301 } else if segment.type_.is_float() { 2302 BitArraySegmentType::Float 2303 } else if segment.type_.is_bit_array() { 2304 BitArraySegmentType::BitArray 2305 } else if segment.type_.is_string() { 2306 let encoding = if segment.has_utf16_option() { 2307 StringEncoding::Utf16 2308 } else if segment.has_utf32_option() { 2309 StringEncoding::Utf32 2310 } else { 2311 StringEncoding::Utf8 2312 }; 2313 BitArraySegmentType::String(encoding) 2314 } else if segment.type_.is_utf_codepoint() { 2315 let encoding = if segment.has_utf16_codepoint_option() { 2316 StringEncoding::Utf16 2317 } else if segment.has_utf32_codepoint_option() { 2318 StringEncoding::Utf32 2319 } else { 2320 StringEncoding::Utf8 2321 }; 2322 BitArraySegmentType::UtfCodepoint(encoding) 2323 } else { 2324 panic!( 2325 "Invalid bit array segment type reached code generation: {:?}", 2326 segment.type_ 2327 ); 2328 } 2329 } 2330} 2331 2332pub fn string(value: &str) -> Document<'_> { 2333 if value.contains('\n') { 2334 EcoString::from(value.replace('\n', r"\n")) 2335 .to_doc() 2336 .surround("\"", "\"") 2337 } else { 2338 value.to_doc().surround("\"", "\"") 2339 } 2340} 2341 2342pub(crate) fn array<'a, Elements: IntoIterator<Item = Document<'a>>>( 2343 elements: Elements, 2344) -> Document<'a> { 2345 let elements = Itertools::intersperse(elements.into_iter(), break_(",", ", ")).collect_vec(); 2346 if elements.is_empty() { 2347 // Do not add a trailing comma since that adds an 'undefined' element 2348 "[]".to_doc() 2349 } else { 2350 docvec![ 2351 "[", 2352 docvec![break_("", ""), elements].nest(INDENT), 2353 break_(",", ""), 2354 "]" 2355 ] 2356 .group() 2357 } 2358} 2359 2360pub(crate) fn list<'a, I: IntoIterator<Item = Document<'a>>>(elements: I) -> Document<'a> 2361where 2362 I::IntoIter: DoubleEndedIterator + ExactSizeIterator, 2363{ 2364 let array = array(elements); 2365 docvec!["toList(", array, ")"] 2366} 2367 2368fn prepend<'a, I: IntoIterator<Item = Document<'a>>>( 2369 elements: I, 2370 tail: Document<'a>, 2371) -> Document<'a> 2372where 2373 I::IntoIter: DoubleEndedIterator + ExactSizeIterator, 2374{ 2375 elements.into_iter().rev().fold(tail, |tail, element| { 2376 let arguments = call_arguments([element, tail]); 2377 docvec!["listPrepend", arguments] 2378 }) 2379} 2380 2381fn call_arguments<'a, Elements: IntoIterator<Item = Document<'a>>>( 2382 elements: Elements, 2383) -> Document<'a> { 2384 let elements = Itertools::intersperse(elements.into_iter(), break_(",", ", ")) 2385 .collect_vec() 2386 .to_doc(); 2387 if elements.is_empty() { 2388 return "()".to_doc(); 2389 } 2390 docvec![ 2391 "(", 2392 docvec![break_("", ""), elements].nest(INDENT), 2393 break_(",", ""), 2394 ")" 2395 ] 2396 .group() 2397} 2398 2399pub(crate) fn construct_record<'a>( 2400 module: Option<&'a str>, 2401 name: &'a str, 2402 arguments: impl IntoIterator<Item = Document<'a>>, 2403) -> Document<'a> { 2404 let mut any_arguments = false; 2405 let arguments = join( 2406 arguments.into_iter().inspect(|_| { 2407 any_arguments = true; 2408 }), 2409 break_(",", ", "), 2410 ); 2411 let arguments = docvec![break_("", ""), arguments].nest(INDENT); 2412 let name = if let Some(module) = module { 2413 docvec!["$", module, ".", name] 2414 } else { 2415 name.to_doc() 2416 }; 2417 if any_arguments { 2418 docvec!["new ", name, "(", arguments, break_(",", ""), ")"].group() 2419 } else { 2420 docvec!["new ", name, "()"] 2421 } 2422} 2423 2424impl TypedExpr { 2425 fn handles_own_return(&self) -> bool { 2426 match self { 2427 TypedExpr::Todo { .. } 2428 | TypedExpr::Call { .. } 2429 | TypedExpr::Case { .. } 2430 | TypedExpr::Panic { .. } 2431 | TypedExpr::Block { .. } 2432 | TypedExpr::Echo { .. } 2433 | TypedExpr::Pipeline { .. } 2434 | TypedExpr::RecordUpdate { .. } => true, 2435 2436 TypedExpr::Int { .. } 2437 | TypedExpr::Float { .. } 2438 | TypedExpr::String { .. } 2439 | TypedExpr::Var { .. } 2440 | TypedExpr::Fn { .. } 2441 | TypedExpr::List { .. } 2442 | TypedExpr::BinOp { .. } 2443 | TypedExpr::RecordAccess { .. } 2444 | TypedExpr::ModuleSelect { .. } 2445 | TypedExpr::Tuple { .. } 2446 | TypedExpr::TupleIndex { .. } 2447 | TypedExpr::BitArray { .. } 2448 | TypedExpr::NegateBool { .. } 2449 | TypedExpr::NegateInt { .. } 2450 | TypedExpr::Invalid { .. } => false, 2451 } 2452 } 2453} 2454 2455impl BinOp { 2456 fn is_operator_to_wrap(&self) -> bool { 2457 match self { 2458 BinOp::And 2459 | BinOp::Or 2460 | BinOp::Eq 2461 | BinOp::NotEq 2462 | BinOp::LtInt 2463 | BinOp::LtEqInt 2464 | BinOp::LtFloat 2465 | BinOp::LtEqFloat 2466 | BinOp::GtEqInt 2467 | BinOp::GtInt 2468 | BinOp::GtEqFloat 2469 | BinOp::GtFloat 2470 | BinOp::AddInt 2471 | BinOp::AddFloat 2472 | BinOp::SubInt 2473 | BinOp::SubFloat 2474 | BinOp::MultFloat 2475 | BinOp::DivInt 2476 | BinOp::DivFloat 2477 | BinOp::RemainderInt 2478 | BinOp::Concatenate => true, 2479 BinOp::MultInt => false, 2480 } 2481 } 2482} 2483 2484pub fn is_js_scalar(t: Arc<Type>) -> bool { 2485 t.is_int() || t.is_float() || t.is_bool() || t.is_nil() || t.is_string() 2486} 2487 2488fn requires_semicolon(statement: &TypedStatement) -> bool { 2489 match statement { 2490 Statement::Expression( 2491 TypedExpr::Int { .. } 2492 | TypedExpr::Fn { .. } 2493 | TypedExpr::Var { .. } 2494 | TypedExpr::List { .. } 2495 | TypedExpr::Call { .. } 2496 | TypedExpr::Echo { .. } 2497 | TypedExpr::Float { .. } 2498 | TypedExpr::String { .. } 2499 | TypedExpr::BinOp { .. } 2500 | TypedExpr::Tuple { .. } 2501 | TypedExpr::NegateInt { .. } 2502 | TypedExpr::BitArray { .. } 2503 | TypedExpr::TupleIndex { .. } 2504 | TypedExpr::NegateBool { .. } 2505 | TypedExpr::RecordAccess { .. } 2506 | TypedExpr::ModuleSelect { .. } 2507 | TypedExpr::Block { .. }, 2508 ) => true, 2509 2510 Statement::Expression( 2511 TypedExpr::Todo { .. } 2512 | TypedExpr::Case { .. } 2513 | TypedExpr::Panic { .. } 2514 | TypedExpr::Pipeline { .. } 2515 | TypedExpr::RecordUpdate { .. } 2516 | TypedExpr::Invalid { .. }, 2517 ) => false, 2518 2519 Statement::Assignment(_) => false, 2520 Statement::Use(_) => false, 2521 Statement::Assert(_) => false, 2522 } 2523} 2524 2525/// Wrap a document in an immediately invoked function expression 2526fn immediately_invoked_function_expression_document(document: Document<'_>) -> Document<'_> { 2527 docvec![ 2528 docvec!["(() => {", break_("", " "), document].nest(INDENT), 2529 break_("", " "), 2530 "})()", 2531 ] 2532 .group() 2533} 2534 2535pub(crate) fn record_constructor<'a>( 2536 type_: Arc<Type>, 2537 qualifier: Option<&'a str>, 2538 name: &'a str, 2539 arity: u16, 2540 tracker: &mut UsageTracker, 2541) -> Document<'a> { 2542 if qualifier.is_none() && type_.is_result_constructor() { 2543 if name == "Ok" { 2544 tracker.ok_used = true; 2545 } else if name == "Error" { 2546 tracker.error_used = true; 2547 } 2548 } 2549 if type_.is_bool() && name == "True" { 2550 "true".to_doc() 2551 } else if type_.is_bool() { 2552 "false".to_doc() 2553 } else if type_.is_nil() { 2554 "undefined".to_doc() 2555 } else if arity == 0 { 2556 match qualifier { 2557 Some(module) => docvec!["new $", module, ".", name, "()"], 2558 None => docvec!["new ", name, "()"], 2559 } 2560 } else { 2561 let vars = (0..arity).map(|i| eco_format!("var{i}").to_doc()); 2562 let body = docvec![ 2563 "return ", 2564 construct_record(qualifier, name, vars.clone()), 2565 ";" 2566 ]; 2567 docvec![ 2568 docvec![wrap_arguments(vars), " => {", break_("", " "), body] 2569 .nest(INDENT) 2570 .append(break_("", " ")) 2571 .group(), 2572 "}", 2573 ] 2574 } 2575} 2576 2577fn u8_slice<'a>(bytes: &[u8]) -> Document<'a> { 2578 let s: EcoString = bytes 2579 .iter() 2580 .map(u8::to_string) 2581 .collect::<Vec<_>>() 2582 .join(", ") 2583 .into(); 2584 2585 docvec![s] 2586}