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