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