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