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
141 kB 3865 lines
1// SPDX-License-Identifier: Apache-2.0 2// SPDX-FileCopyrightText: 2021 The Gleam contributors 3 4use num_bigint::BigInt; 5use vec1::Vec1; 6 7use super::{decision::ASSIGNMENT_VAR, *}; 8use crate::{ 9 ast::*, 10 exhaustiveness::StringEncoding, 11 line_numbers::LineNumbers, 12 type_::{ 13 ModuleValueConstructor, Type, TypedCallArg, ValueConstructor, ValueConstructorVariant, 14 }, 15}; 16use pretty_arena::*; 17use std::sync::Arc; 18 19#[derive(Debug, Clone)] 20pub enum Position { 21 /// We are compiling the last expression in a function, meaning that it should 22 /// use `return` to return the value it produces from the function. 23 Tail, 24 /// We are inside a function, but the value of this expression isn't being 25 /// used, so we don't need to do anything with the returned value. 26 Statement, 27 /// The value of this expression needs to be used inside another expression, 28 /// so we need to use the value that is returned by this expression. 29 Expression(Ordering), 30 /// We are compiling an expression inside a block, meaning we must assign 31 /// to the `_block` variable at the end of the scope, because blocks are not 32 /// expressions in JS. 33 /// Since JS doesn't have variable shadowing, we must store the name of the 34 /// variable being used, which will include the incrementing counter. 35 /// For example, `block$2` 36 Assign(EcoString), 37} 38 39impl Position { 40 /// Returns `true` if the position is [`Tail`]. 41 /// 42 /// [`Tail`]: Position::Tail 43 #[must_use] 44 pub fn is_tail(&self) -> bool { 45 matches!(self, Self::Tail) 46 } 47 48 #[must_use] 49 pub fn ordering(&self) -> Ordering { 50 match self { 51 Self::Expression(ordering) => *ordering, 52 Self::Tail | Self::Assign(_) | Self::Statement => Ordering::Loose, 53 } 54 } 55} 56 57#[derive(Debug, Clone, Copy)] 58/// Determines whether we can lift blocks into statement level instead of using 59/// immediately invoked function expressions. Consider the following piece of code: 60/// 61/// ```gleam 62/// some_function(function_with_side_effect(), { 63/// let a = 10 64/// other_function_with_side_effects(a) 65/// }) 66/// ``` 67/// Here, if we lift the block that is the second argument of the function, we 68/// would end up running `other_function_with_side_effects` before 69/// `function_with_side_effects`. This would be invalid, as code in Gleam should be 70/// evaluated left-to-right, top-to-bottom. In this case, the ordering would be 71/// `Strict`, indicating that we cannot lift the block. 72/// 73/// However, in this example: 74/// 75/// ```gleam 76/// let value = !{ 77/// let value = False 78/// some_function_with_side_effect() 79/// value 80/// } 81/// ``` 82/// The only expression is the block, meaning it can be safely lifted without 83/// changing the evaluation order of the program. So the ordering is `Loose`. 84/// 85pub enum Ordering { 86 Strict, 87 Loose, 88} 89 90/// Tracking where the current function is a module function or an anonymous function. 91#[derive(Debug)] 92enum CurrentFunction { 93 /// The current function is a module function 94 /// 95 /// ```gleam 96 /// pub fn main() -> Nil { 97 /// // we are here 98 /// } 99 /// ``` 100 Module, 101 102 /// The current function is a module function, but one of its arguments shadows 103 /// the reference to itself so it cannot recurse. 104 /// 105 /// ```gleam 106 /// pub fn main(main: fn() -> Nil) -> Nil { 107 /// // we are here 108 /// } 109 /// ``` 110 ModuleWithShadowingArgument, 111 112 /// The current function is an anonymous function 113 /// 114 /// ```gleam 115 /// pub fn main() -> Nil { 116 /// fn() { 117 /// // we are here 118 /// } 119 /// } 120 /// ``` 121 Anonymous, 122} 123 124impl CurrentFunction { 125 #[inline] 126 fn can_recurse(&self) -> bool { 127 match self { 128 CurrentFunction::Module => true, 129 CurrentFunction::ModuleWithShadowingArgument => false, 130 CurrentFunction::Anonymous => false, 131 } 132 } 133} 134 135/// The variables in scope while generating the code for a function. 136/// 137/// User variables are tracked in a map keyed by name, so a shadowed name can be 138/// given a unique JavaScript identifier. The compiler synthesised variables 139/// (the `$` case subject, `_pipe`, `_block`, ...) are held in their own fields 140/// instead. They can't be referenced by user code, and a branch that generates 141/// directly into its enclosing scope needs to restore the user variables while 142/// leaving the synthesised counters advanced, so that later code doesn't 143/// redeclare one of them. 144#[derive(Debug, Clone, Default)] 145pub(crate) struct Scope { 146 user_variables: im::HashMap<EcoString, usize>, 147 /// The highest suffix handed out for each user variable still declared in 148 /// the current JS scope, kept across a directly matching `case` branch so a 149 /// variable that leaked out of it is not redeclared by a later `let`. 150 high_water: im::HashMap<EcoString, usize>, 151 assignment: Option<usize>, 152 pipe: Option<usize>, 153 block: Option<usize>, 154 use_assignment: Option<usize>, 155 record_update: Option<usize>, 156 capture: Option<usize>, 157 assert_subject: Option<usize>, 158 assert_fail: Option<usize>, 159} 160 161impl Scope { 162 fn new(user_variables: im::HashMap<EcoString, usize>) -> Self { 163 Self { 164 user_variables, 165 ..Self::default() 166 } 167 } 168 169 /// The counter for a variable name, whether user or synthesised. 170 fn counter(&self, name: &str) -> Option<usize> { 171 match name { 172 ASSIGNMENT_VAR => self.assignment, 173 PIPE_VARIABLE => self.pipe, 174 BLOCK_VARIABLE => self.block, 175 USE_ASSIGNMENT_VARIABLE => self.use_assignment, 176 RECORD_UPDATE_VARIABLE => self.record_update, 177 CAPTURE_VARIABLE => self.capture, 178 ASSERT_SUBJECT_VARIABLE => self.assert_subject, 179 ASSERT_FAIL_VARIABLE => self.assert_fail, 180 _ => self.user_variables.get(name).copied(), 181 } 182 } 183 184 /// Set the counter for a variable name, whether user or synthesised. 185 pub(crate) fn set_counter(&mut self, name: &EcoString, value: usize) { 186 match name.as_str() { 187 ASSIGNMENT_VAR => self.assignment = Some(value), 188 PIPE_VARIABLE => self.pipe = Some(value), 189 BLOCK_VARIABLE => self.block = Some(value), 190 USE_ASSIGNMENT_VARIABLE => self.use_assignment = Some(value), 191 RECORD_UPDATE_VARIABLE => self.record_update = Some(value), 192 CAPTURE_VARIABLE => self.capture = Some(value), 193 ASSERT_SUBJECT_VARIABLE => self.assert_subject = Some(value), 194 ASSERT_FAIL_VARIABLE => self.assert_fail = Some(value), 195 _ => { 196 let _ = self.user_variables.insert(name.clone(), value); 197 } 198 } 199 } 200 201 /// Advance the counter for a name to its next suffix, skipping any suffix 202 /// already handed out for it in the current scope so a name that leaked out 203 /// of a directly matching branch can't be redeclared. 204 fn advance_counter(&mut self, name: &EcoString) { 205 let in_scope = self.counter(name).map_or(0, |i| i + 1); 206 let high_water = self.high_water.get(name).map_or(0, |i| i + 1); 207 let next = in_scope.max(high_water); 208 self.set_counter(name, next); 209 // Only user variables leak out of a directly matching branch; the 210 // synthesised counters survive the restore on their own. 211 if self.user_variables.contains_key(name) { 212 let _ = self.high_water.insert(name.clone(), next); 213 } 214 } 215 216 /// The user variables currently in scope. 217 pub(crate) fn user_variables(&self) -> &im::HashMap<EcoString, usize> { 218 &self.user_variables 219 } 220 221 /// Restore previously saved user variables, reverting any counters advanced 222 /// during the branch to their earlier values. Variables introduced in the 223 /// branch with no earlier binding are kept, and the synthesised counters are 224 /// left untouched. 225 pub(crate) fn restore_user_variables(&mut self, previous: &im::HashMap<EcoString, usize>) { 226 self.user_variables.extend(previous.clone()); 227 } 228} 229 230#[derive(Debug)] 231pub(crate) struct Generator<'module, 'ast, 'doc> { 232 module_name: EcoString, 233 src_path: EcoString, 234 line_numbers: &'module LineNumbers, 235 function_name: EcoString, 236 function_arguments: Vec<Option<&'module EcoString>>, 237 current_function: CurrentFunction, 238 pub current_scope: Scope, 239 pub function_position: Position, 240 pub scope_position: Position, 241 // We register whether these features are used within an expression so that 242 // the module generator can output a suitable function if it is needed. 243 pub tracker: &'module mut UsageTracker, 244 // We track whether tail call recursion is used so that we can render a loop 245 // at the top level of the function to use in place of pushing new stack 246 // frames. 247 pub tail_recursion_used: bool, 248 /// Statements to be compiled when lifting blocks into statement scope. 249 /// For example, when compiling the following code: 250 /// ```gleam 251 /// let a = { 252 /// let b = 1 253 /// b + 1 254 /// } 255 /// ``` 256 /// There will be 2 items in `statement_level`: The first will be `let _block;` 257 /// The second will be the generated code for the block being assigned to `a`. 258 /// This lets use return `_block` as the value that the block evaluated to, 259 /// while still including the necessary code in the output at the right place. 260 /// 261 /// Once the `let` statement has compiled its value, it will add anything accumulated 262 /// in `statement_level` to the generated code, so it will result in: 263 /// 264 /// ```javascript 265 /// let _block; 266 /// {...} 267 /// let a = _block; 268 /// ``` 269 /// 270 statement_level: Vec<Document<'ast, 'doc>>, 271 272 /// This will be true if we've generated a `let assert` statement that we know 273 /// is guaranteed to throw. 274 /// This means we can stop code generation for all the following statements 275 /// in the same block! 276 pub let_assert_always_panics: bool, 277 278 pub source_map_builder: Option<Rc<RefCell<DebugIgnore<sourcemap::SourceMapBuilder>>>>, 279} 280 281impl<'module, 'a, 'doc> Generator<'module, 'a, 'doc> { 282 #[allow(clippy::too_many_arguments)] // TODO: FIXME 283 pub fn new( 284 module_name: EcoString, 285 src_path: EcoString, 286 line_numbers: &'module LineNumbers, 287 function_name: EcoString, 288 function_arguments: Vec<Option<&'module EcoString>>, 289 tracker: &'module mut UsageTracker, 290 initial_scope_vars: im::HashMap<EcoString, usize>, 291 source_map_builder: Option<Rc<RefCell<DebugIgnore<sourcemap::SourceMapBuilder>>>>, 292 ) -> Self { 293 let mut current_scope = Scope::new(initial_scope_vars); 294 let mut current_function = CurrentFunction::Module; 295 for &name in function_arguments.iter().flatten() { 296 // Initialise the function arguments 297 current_scope.set_counter(name, 0); 298 299 // If any of the function arguments shadow the current function then 300 // recursion is no longer possible. 301 if function_name.as_ref() == name { 302 current_function = CurrentFunction::ModuleWithShadowingArgument; 303 } 304 } 305 Self { 306 tracker, 307 module_name, 308 src_path, 309 line_numbers, 310 function_name, 311 function_arguments, 312 tail_recursion_used: false, 313 current_scope, 314 current_function, 315 function_position: Position::Tail, 316 scope_position: Position::Tail, 317 statement_level: Vec::new(), 318 let_assert_always_panics: false, 319 source_map_builder, 320 } 321 } 322 323 pub fn local_var(&mut self, name: &EcoString) -> EcoString { 324 match self.current_scope.counter(name) { 325 None => { 326 self.current_scope.set_counter(name, 0); 327 maybe_escape_identifier(name) 328 } 329 Some(0) => maybe_escape_identifier(name), 330 Some(n) if name == "$" => eco_format!("${n}"), 331 Some(n) => eco_format!("{name}${n}"), 332 } 333 } 334 335 pub fn next_local_var(&mut self, name: &EcoString) -> EcoString { 336 self.current_scope.advance_counter(name); 337 self.local_var(name) 338 } 339 340 pub fn function_body( 341 &mut self, 342 arena: &'doc DocumentArena<'a, 'doc>, 343 body: &'a [TypedStatement], 344 arguments: &'a [TypedArg], 345 ) -> Document<'a, 'doc> { 346 let body = self.statements(arena, body); 347 if self.tail_recursion_used { 348 self.tail_call_loop(arena, body, arguments) 349 } else { 350 body 351 } 352 } 353 354 fn tail_call_loop( 355 &mut self, 356 arena: &'doc DocumentArena<'a, 'doc>, 357 body: Document<'a, 'doc>, 358 arguments: &'a [TypedArg], 359 ) -> Document<'a, 'doc> { 360 let loop_assignments = arena.concat(arguments.iter().flat_map(|arg| { 361 arg.get_variable_name().map(|name| { 362 let var = maybe_escape_identifier(name); 363 docvec![ 364 arena, 365 self.source_map_tracker(arena, arg.location.start), 366 LET_SPACE_DOCUMENT, 367 var, 368 SPACE_EQUAL_LOOP_DOLLAR_DOCUMENT, 369 name, 370 SEMICOLON_DOCUMENT, 371 LINE_DOCUMENT 372 ] 373 }) 374 })); 375 docvec![ 376 arena, 377 WHILE_TRUE_OPEN_PAREN_DOCUMENT, 378 docvec![arena, LINE_DOCUMENT, loop_assignments, body].nest(arena, INDENT), 379 LINE_DOCUMENT, 380 CLOSE_CURLY_DOCUMENT 381 ] 382 } 383 384 fn statement( 385 &mut self, 386 arena: &'doc DocumentArena<'a, 'doc>, 387 statement: &'a TypedStatement, 388 ) -> Document<'a, 'doc> { 389 let expression_doc = match statement { 390 Statement::Expression(expression) => self.expression(arena, expression), 391 Statement::Assignment(assignment) => self.assignment(arena, assignment), 392 Statement::Use(use_) => self.expression(arena, &use_.call), 393 Statement::Assert(assert) => self.assert(arena, assert), 394 }; 395 self.add_statement_level(arena, expression_doc) 396 } 397 398 fn add_statement_level( 399 &mut self, 400 arena: &'doc DocumentArena<'a, 'doc>, 401 expression: Document<'a, 'doc>, 402 ) -> Document<'a, 'doc> { 403 if self.statement_level.is_empty() { 404 expression 405 } else { 406 let mut statements = std::mem::take(&mut self.statement_level); 407 statements.push(expression); 408 arena.join(statements, LINE_DOCUMENT) 409 } 410 } 411 412 pub fn expression( 413 &mut self, 414 arena: &'doc DocumentArena<'a, 'doc>, 415 expression: &'a TypedExpr, 416 ) -> Document<'a, 'doc> { 417 let mut document = match expression { 418 TypedExpr::String { value, .. } => string(arena, value), 419 420 TypedExpr::Int { value, .. } => int(arena, value), 421 TypedExpr::Float { float_value, .. } => float_from_value(arena, float_value.value()), 422 423 TypedExpr::List { elements, tail, .. } => { 424 self.not_in_tail_position(Some(Ordering::Strict), |this| match tail { 425 Some(tail) => { 426 this.tracker.prepend_used = true; 427 let tail = this.wrap_expression(arena, tail); 428 prepend( 429 arena, 430 elements 431 .iter() 432 .map(|element| this.wrap_expression(arena, element)), 433 tail, 434 ) 435 } 436 None if elements.is_empty() => this.empty_list(), 437 None => { 438 this.tracker.list_used = true; 439 list( 440 arena, 441 elements 442 .iter() 443 .map(|element| this.wrap_expression(arena, element)), 444 ) 445 } 446 }) 447 } 448 449 TypedExpr::Tuple { elements, .. } => self.tuple(arena, elements), 450 TypedExpr::TupleIndex { tuple, index, .. } => self.tuple_index(arena, tuple, *index), 451 452 TypedExpr::Case { 453 subjects, 454 clauses, 455 compiled_case, 456 .. 457 } => decision::case(arena, compiled_case, clauses, subjects, self), 458 459 TypedExpr::Call { fun, arguments, .. } => self.call(arena, fun, arguments), 460 TypedExpr::Fn { 461 arguments, 462 body, 463 kind, 464 .. 465 } => self.fn_(arena, arguments, body, kind), 466 467 TypedExpr::RecordAccess { record, label, .. } => { 468 self.record_access(arena, record, label) 469 } 470 471 TypedExpr::PositionalAccess { record, index, .. } => { 472 self.positional_access(arena, record, *index) 473 } 474 475 TypedExpr::RecordUpdate { 476 updated_record_assigned_name, 477 updated_record, 478 constructor, 479 arguments, 480 .. 481 } => self.record_update( 482 arena, 483 updated_record_assigned_name, 484 updated_record, 485 constructor, 486 arguments, 487 ), 488 489 TypedExpr::Var { 490 name, constructor, .. 491 } => self.variable(arena, name, constructor), 492 493 TypedExpr::Pipeline { 494 first_value, 495 assignments, 496 finally, 497 .. 498 } => self.pipeline(arena, first_value, assignments.as_slice(), finally), 499 500 TypedExpr::Block { statements, .. } => self.block(arena, statements), 501 502 TypedExpr::BinOp { 503 operator, 504 left, 505 right, 506 .. 507 } => self.bin_op(arena, operator, left, right), 508 509 TypedExpr::Todo { 510 message, location, .. 511 } => self.todo(arena, message.as_ref().map(|m| &**m), location), 512 513 TypedExpr::Panic { 514 location, message, .. 515 } => self.panic(arena, location, message.as_ref().map(|m| &**m)), 516 517 TypedExpr::BitArray { segments, .. } => self.bit_array(arena, segments), 518 519 TypedExpr::ModuleSelect { 520 module_alias, 521 label, 522 constructor, 523 .. 524 } => self.module_select(arena, module_alias, label, constructor), 525 526 TypedExpr::NegateBool { value, .. } => { 527 self.negate_with(arena, EXCLAMATION_MARK_DOCUMENT, value) 528 } 529 530 TypedExpr::NegateInt { value, .. } => { 531 self.negate_with(arena, MINUS_SPACE_DOCUMENT, value) 532 } 533 534 TypedExpr::Echo { 535 expression, 536 message, 537 location, 538 .. 539 } => { 540 let expression = expression 541 .as_ref() 542 .expect("echo with no expression outside of pipe"); 543 let expresion_doc = 544 self.not_in_tail_position(None, |this| this.wrap_expression(arena, expression)); 545 self.echo(arena, expresion_doc, message.as_deref(), location) 546 } 547 548 TypedExpr::Invalid { .. } => { 549 panic!("invalid expressions should not reach code generation") 550 } 551 }; 552 if let Position::Statement = self.scope_position 553 && expression_requires_semicolon(expression) 554 { 555 document = document.append(arena, SEMICOLON_DOCUMENT); 556 } 557 if expression.handles_own_return() { 558 docvec![ 559 arena, 560 self.source_map_tracker(arena, expression.location().start), 561 document 562 ] 563 } else { 564 docvec![ 565 arena, 566 self.source_map_tracker(arena, expression.location().start), 567 self.wrap_return(arena, document) 568 ] 569 } 570 } 571 572 /// Return the singleton empty list; all empty lists are the same underlying 573 /// reference, which makes comparison faster. 574 fn empty_list(&mut self) -> Document<'a, 'doc> { 575 self.tracker.list_empty_const_used = true; 576 DOLLAR_LIST_DOLLAR_EMPTY_DOLLAR_CONST_DOCUMENT 577 } 578 579 fn negate_with( 580 &mut self, 581 arena: &'doc DocumentArena<'a, 'doc>, 582 with: Document<'a, 'doc>, 583 value: &'a TypedExpr, 584 ) -> Document<'a, 'doc> { 585 self.not_in_tail_position(None, |this| { 586 docvec![arena, with, this.wrap_expression(arena, value)] 587 }) 588 } 589 590 fn bit_array( 591 &mut self, 592 arena: &'doc DocumentArena<'a, 'doc>, 593 segments: &'a [TypedExprBitArraySegment], 594 ) -> Document<'a, 'doc> { 595 self.tracker.bit_array_literal_used = true; 596 597 // Collect all the values used in segments. 598 let segments_array = array( 599 arena, 600 segments.iter().map(|segment| { 601 let value = self.not_in_tail_position(Some(Ordering::Strict), |this| { 602 this.wrap_expression(arena, &segment.value) 603 }); 604 605 let details = self.bit_array_segment_details(arena, segment); 606 607 match details.type_ { 608 BitArraySegmentType::BitArray => { 609 if segment.size().is_some() { 610 self.tracker.bit_array_slice_used = true; 611 docvec![ 612 arena, 613 BIT_ARRAY_SLICE_OPEN_PAREN_DOCUMENT, 614 value, 615 COMMA_ZERO_COMMA_SPACE, 616 details.size, 617 CLOSE_PAREN_DOCUMENT 618 ] 619 } else { 620 value 621 } 622 } 623 BitArraySegmentType::Int => { 624 match (details.size_value, segment.value.as_ref()) { 625 (Some(size_value), TypedExpr::Int { int_value, .. }) 626 if size_value <= SAFE_INT_SEGMENT_MAX_SIZE.into() 627 && (&size_value % BigInt::from(8) == BigInt::ZERO) => 628 { 629 let bytes = bit_array_segment_int_value_to_bytes( 630 int_value.clone(), 631 size_value, 632 segment.endianness(), 633 ); 634 635 u8_slice(arena, &bytes) 636 } 637 638 (Some(size_value), _) if size_value == 8.into() => value, 639 640 (Some(size_value), _) if size_value <= 0.into() => EMPTY_DOCUMENT, 641 642 _ => { 643 self.tracker.sized_integer_segment_used = true; 644 let size = details.size; 645 let is_big = bool(segment.endianness().is_big()); 646 docvec![ 647 arena, 648 SIZED_INT_OPEN_PAREN_DOCUMENT, 649 value, 650 COMMA_SPACE_DOCUMENT, 651 size, 652 COMMA_SPACE_DOCUMENT, 653 is_big, 654 CLOSE_PAREN_DOCUMENT 655 ] 656 } 657 } 658 } 659 BitArraySegmentType::Float => { 660 self.tracker.float_bit_array_segment_used = true; 661 let size = details.size; 662 let is_big = bool(details.endianness.is_big()); 663 docvec![ 664 arena, 665 SIZED_FLOAT_OPEN_PAREN_DOCUMENT, 666 value, 667 COMMA_SPACE_DOCUMENT, 668 size, 669 COMMA_SPACE_DOCUMENT, 670 is_big, 671 CLOSE_PAREN_DOCUMENT 672 ] 673 } 674 BitArraySegmentType::String(StringEncoding::Utf8) => { 675 self.tracker.string_bit_array_segment_used = true; 676 docvec![ 677 arena, 678 STRING_BITS_OPEN_PAREN_DOCUMENT, 679 value, 680 CLOSE_PAREN_DOCUMENT 681 ] 682 } 683 BitArraySegmentType::String(StringEncoding::Utf16) => { 684 self.tracker.string_utf16_bit_array_segment_used = true; 685 let is_big = bool(details.endianness.is_big()); 686 docvec![ 687 arena, 688 STRING_TO_UTF16_OPEN_PAREN_DOCUMENT, 689 value, 690 COMMA_SPACE_DOCUMENT, 691 is_big, 692 CLOSE_PAREN_DOCUMENT 693 ] 694 } 695 BitArraySegmentType::String(StringEncoding::Utf32) => { 696 self.tracker.string_utf32_bit_array_segment_used = true; 697 let is_big = bool(details.endianness.is_big()); 698 docvec![ 699 arena, 700 STRING_TO_UTF32_OPEN_PAREN_DOCUMENT, 701 value, 702 COMMA_SPACE_DOCUMENT, 703 is_big, 704 CLOSE_PAREN_DOCUMENT 705 ] 706 } 707 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf8) => { 708 self.tracker.codepoint_bit_array_segment_used = true; 709 docvec![ 710 arena, 711 CODEPOINT_BITS_OPEN_PAREN_DOCUMENT, 712 value, 713 CLOSE_PAREN_DOCUMENT 714 ] 715 } 716 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf16) => { 717 self.tracker.codepoint_utf16_bit_array_segment_used = true; 718 let is_big = bool(details.endianness.is_big()); 719 docvec![ 720 arena, 721 CODEPOINT_TO_UTF16_OPEN_PAREN_DOCUMENT, 722 value, 723 COMMA_SPACE_DOCUMENT, 724 is_big, 725 CLOSE_PAREN_DOCUMENT 726 ] 727 } 728 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf32) => { 729 self.tracker.codepoint_utf32_bit_array_segment_used = true; 730 let is_big = bool(details.endianness.is_big()); 731 docvec![ 732 arena, 733 CODEPOINT_TO_UTF32_OPEN_PAREN_DOCUMENT, 734 value, 735 COMMA_SPACE_DOCUMENT, 736 is_big, 737 CLOSE_PAREN_DOCUMENT 738 ] 739 } 740 } 741 }), 742 ); 743 744 docvec![ 745 arena, 746 TO_BIT_ARRAY_OPEN_PAREN_DOCUMENT, 747 segments_array, 748 CLOSE_PAREN_DOCUMENT 749 ] 750 } 751 752 fn bit_array_segment_details( 753 &mut self, 754 arena: &'doc DocumentArena<'a, 'doc>, 755 segment: &'a TypedExprBitArraySegment, 756 ) -> BitArraySegmentDetails<'a, 'doc> { 757 let size = segment.size(); 758 let unit = segment.unit(); 759 let (size_value, size) = match size { 760 Some(TypedExpr::Int { int_value, .. }) => { 761 let size_value = int_value * unit; 762 let size = eco_format!("{size_value}").to_doc(arena); 763 (Some(size_value), size) 764 } 765 Some(size) => { 766 let mut size = self.not_in_tail_position(Some(Ordering::Strict), |this| { 767 this.wrap_expression(arena, size) 768 }); 769 770 if unit != 1 { 771 size = size.group(arena).append( 772 arena, 773 SPACE_TIMES_SPACE_DOCUMENT.append(arena, unit.to_doc(arena)), 774 ); 775 } 776 777 (None, size) 778 } 779 780 None => { 781 let size_value: usize = if segment.type_.is_int() { 8 } else { 64 }; 782 (Some(BigInt::from(size_value)), size_value.to_doc(arena)) 783 } 784 }; 785 786 let type_ = BitArraySegmentType::from_segment(segment); 787 788 BitArraySegmentDetails { 789 type_, 790 size, 791 size_value, 792 endianness: segment.endianness(), 793 } 794 } 795 796 pub fn wrap_return( 797 &mut self, 798 arena: &'doc DocumentArena<'a, 'doc>, 799 document: Document<'a, 'doc>, 800 ) -> Document<'a, 'doc> { 801 match &self.scope_position { 802 Position::Tail => docvec![arena, RETURN_SPACE_DOCUMENT, document, SEMICOLON_DOCUMENT], 803 Position::Expression(_) | Position::Statement => document, 804 Position::Assign(name) => docvec![ 805 arena, 806 name.clone(), 807 SPACE_EQUAL_SPACE_DOCUMENT, 808 document, 809 SEMICOLON_DOCUMENT 810 ], 811 } 812 } 813 814 pub fn not_in_tail_position<CompileFn, Output>( 815 &mut self, 816 // If ordering is None, it is inherited from the parent scope. 817 // It will be None in cases like `!x`, where `x` can be lifted 818 // only if the ordering is already loose. 819 ordering: Option<Ordering>, 820 compile: CompileFn, 821 ) -> Output 822 where 823 CompileFn: Fn(&mut Self) -> Output, 824 { 825 let new_ordering = ordering.unwrap_or(self.scope_position.ordering()); 826 827 let function_position = std::mem::replace( 828 &mut self.function_position, 829 Position::Expression(new_ordering), 830 ); 831 let scope_position = 832 std::mem::replace(&mut self.scope_position, Position::Expression(new_ordering)); 833 834 let result = compile(self); 835 836 self.function_position = function_position; 837 self.scope_position = scope_position; 838 result 839 } 840 841 /// Use the `_block` variable if the expression is JS statement. 842 pub fn wrap_expression( 843 &mut self, 844 arena: &'doc DocumentArena<'a, 'doc>, 845 expression: &'a TypedExpr, 846 ) -> Document<'a, 'doc> { 847 match (expression, &self.scope_position) { 848 (_, Position::Tail | Position::Assign(_)) => self.expression(arena, expression), 849 ( 850 TypedExpr::Panic { .. } 851 | TypedExpr::Todo { .. } 852 | TypedExpr::Case { .. } 853 | TypedExpr::Pipeline { .. } 854 | TypedExpr::RecordUpdate { 855 // Record updates that assign a variable generate multiple statements 856 updated_record_assigned_name: Some(_), 857 .. 858 }, 859 Position::Expression(Ordering::Loose), 860 ) => self.wrap_block(arena, |this| this.expression(arena, expression)), 861 ( 862 TypedExpr::Panic { .. } 863 | TypedExpr::Todo { .. } 864 | TypedExpr::Case { .. } 865 | TypedExpr::Pipeline { .. } 866 | TypedExpr::RecordUpdate { 867 // Record updates that assign a variable generate multiple statements 868 updated_record_assigned_name: Some(_), 869 .. 870 }, 871 Position::Expression(Ordering::Strict), 872 ) => self.immediately_invoked_function_expression( 873 arena, 874 expression, 875 |this, expression| this.expression(arena, expression), 876 ), 877 _ => self.expression(arena, expression), 878 } 879 } 880 881 /// Wrap an expression using the `_block` variable if required due to being 882 /// a JS statement, or in parens if required due to being an operator or 883 /// a function literal. 884 pub fn child_expression( 885 &mut self, 886 arena: &'doc DocumentArena<'a, 'doc>, 887 expression: &'a TypedExpr, 888 ) -> Document<'a, 'doc> { 889 match expression { 890 TypedExpr::BinOp { operator, .. } if operator.is_operator_to_wrap() => {} 891 TypedExpr::Fn { .. } => {} 892 893 TypedExpr::Int { .. } 894 | TypedExpr::Float { .. } 895 | TypedExpr::String { .. } 896 | TypedExpr::Block { .. } 897 | TypedExpr::Pipeline { .. } 898 | TypedExpr::Var { .. } 899 | TypedExpr::List { .. } 900 | TypedExpr::Call { .. } 901 | TypedExpr::BinOp { .. } 902 | TypedExpr::Case { .. } 903 | TypedExpr::RecordAccess { .. } 904 | TypedExpr::PositionalAccess { .. } 905 | TypedExpr::ModuleSelect { .. } 906 | TypedExpr::Tuple { .. } 907 | TypedExpr::TupleIndex { .. } 908 | TypedExpr::Todo { .. } 909 | TypedExpr::Panic { .. } 910 | TypedExpr::Echo { .. } 911 | TypedExpr::BitArray { .. } 912 | TypedExpr::RecordUpdate { .. } 913 | TypedExpr::NegateBool { .. } 914 | TypedExpr::NegateInt { .. } 915 | TypedExpr::Invalid { .. } => return self.wrap_expression(arena, expression), 916 } 917 918 let document = self.expression(arena, expression); 919 match &self.scope_position { 920 // Here the document is a return statement: `return <expr>;` 921 // or an assignment: `_block = <expr>;` 922 Position::Tail | Position::Assign(_) | Position::Statement => document, 923 Position::Expression(_) => { 924 docvec![arena, OPEN_PAREN_DOCUMENT, document, CLOSE_PAREN_DOCUMENT] 925 } 926 } 927 } 928 929 /// Wrap an expression in an immediately invoked function expression 930 fn immediately_invoked_function_expression<T, ToDoc>( 931 &mut self, 932 arena: &'doc DocumentArena<'a, 'doc>, 933 statements: &'a T, 934 to_doc: ToDoc, 935 ) -> Document<'a, 'doc> 936 where 937 ToDoc: FnOnce(&mut Self, &'a T) -> Document<'a, 'doc>, 938 { 939 // Save initial state 940 let scope_position = std::mem::replace(&mut self.scope_position, Position::Tail); 941 let statement_level = std::mem::take(&mut self.statement_level); 942 943 // Set state for in this iife 944 let current_scope = self.current_scope.clone(); 945 946 // Generate the expression 947 let result = to_doc(self, statements); 948 let doc = self.add_statement_level(arena, result); 949 let doc = immediately_invoked_function_expression_document(arena, doc); 950 951 // Reset 952 self.current_scope = current_scope; 953 self.scope_position = scope_position; 954 self.statement_level = statement_level; 955 956 self.wrap_return(arena, doc) 957 } 958 959 fn wrap_block<CompileFn>( 960 &mut self, 961 arena: &'doc DocumentArena<'a, 'doc>, 962 compile: CompileFn, 963 ) -> Document<'a, 'doc> 964 where 965 CompileFn: Fn(&mut Self) -> Document<'a, 'doc>, 966 { 967 let block_variable = self.next_local_var(&BLOCK_VARIABLE.into()); 968 969 // Save initial state 970 let scope_position = std::mem::replace( 971 &mut self.scope_position, 972 Position::Assign(block_variable.clone()), 973 ); 974 let function_position = std::mem::replace( 975 &mut self.function_position, 976 Position::Expression(Ordering::Strict), 977 ); 978 979 // Generate the expression 980 let statement_doc = compile(self); 981 982 // Reset 983 self.scope_position = scope_position; 984 self.function_position = function_position; 985 986 self.statement_level.push(docvec![ 987 arena, 988 LET_SPACE_DOCUMENT, 989 block_variable.clone(), 990 SEMICOLON_DOCUMENT 991 ]); 992 self.statement_level.push(statement_doc); 993 994 self.wrap_return(arena, block_variable.to_doc(arena)) 995 } 996 997 fn variable( 998 &mut self, 999 arena: &'doc DocumentArena<'a, 'doc>, 1000 name: &'a EcoString, 1001 constructor: &'a ValueConstructor, 1002 ) -> Document<'a, 'doc> { 1003 match &constructor.variant { 1004 ValueConstructorVariant::Record { 1005 arity, 1006 name: variant_name, 1007 .. 1008 } => { 1009 let type_ = constructor.type_.clone(); 1010 self.record_constructor(arena, type_, None, variant_name, name, *arity) 1011 } 1012 ValueConstructorVariant::ModuleFn { .. } 1013 | ValueConstructorVariant::ModuleConstant { .. } 1014 | ValueConstructorVariant::LocalVariable { .. } => self.local_var(name).to_doc(arena), 1015 } 1016 } 1017 1018 fn pipeline( 1019 &mut self, 1020 arena: &'doc DocumentArena<'a, 'doc>, 1021 first_value: &'a TypedPipelineAssignment, 1022 assignments: &'a [(TypedPipelineAssignment, PipelineAssignmentKind)], 1023 finally: &'a TypedExpr, 1024 ) -> Document<'a, 'doc> { 1025 let count = assignments.len(); 1026 let mut documents = Vec::with_capacity((count + 2) * 2); 1027 1028 let all_assignments = std::iter::once(first_value) 1029 .chain(assignments.iter().map(|(assignment, _kind)| assignment)); 1030 1031 let mut latest_local_var: Option<EcoString> = None; 1032 for assignment in all_assignments { 1033 // An echo in a pipeline won't result in an assignment, instead it 1034 // just prints the previous variable assigned in the pipeline. 1035 if let TypedExpr::Echo { 1036 expression: None, 1037 message, 1038 location, 1039 .. 1040 } = assignment.value.as_ref() 1041 { 1042 documents.push(self.not_in_tail_position(Some(Ordering::Strict), |this| { 1043 let var = latest_local_var 1044 .as_ref() 1045 .expect("echo with no previous step in a pipe"); 1046 this.echo(arena, var.to_doc(arena), message.as_deref(), location) 1047 })); 1048 documents.push(SEMICOLON_DOCUMENT); 1049 } else { 1050 // Otherwise we assign the intermediate pipe value to a variable. 1051 let assignment_document = 1052 self.not_in_tail_position(Some(Ordering::Strict), |this| { 1053 this.simple_variable_assignment( 1054 arena, 1055 &assignment.name, 1056 &assignment.value, 1057 assignment.location, 1058 ) 1059 }); 1060 documents.push(self.add_statement_level(arena, assignment_document)); 1061 latest_local_var = Some(self.local_var(&assignment.name)); 1062 } 1063 1064 documents.push(LINE_DOCUMENT); 1065 } 1066 1067 if let TypedExpr::Echo { 1068 expression: None, 1069 message, 1070 location, 1071 .. 1072 } = finally 1073 { 1074 let var = latest_local_var.expect("echo with no previous step in a pipe"); 1075 documents.push(self.echo(arena, var.to_doc(arena), message.as_deref(), location)); 1076 match &self.scope_position { 1077 Position::Statement => documents.push(SEMICOLON_DOCUMENT), 1078 Position::Expression(_) | Position::Tail | Position::Assign(_) => {} 1079 } 1080 } else { 1081 let finally_doc = self.expression(arena, finally); 1082 documents.push(self.add_statement_level(arena, finally_doc)); 1083 } 1084 1085 arena.concat(documents).force_break(arena) 1086 } 1087 1088 pub(crate) fn expression_flattening_blocks( 1089 &mut self, 1090 arena: &'doc DocumentArena<'a, 'doc>, 1091 expression: &'a TypedExpr, 1092 ) -> Document<'a, 'doc> { 1093 if let TypedExpr::Block { statements, .. } = expression { 1094 self.statements(arena, statements) 1095 } else { 1096 self.expression(arena, expression) 1097 } 1098 } 1099 1100 fn block( 1101 &mut self, 1102 arena: &'doc DocumentArena<'a, 'doc>, 1103 statements: &'a Vec1<TypedStatement>, 1104 ) -> Document<'a, 'doc> { 1105 if statements.len() == 1 { 1106 match statements.first() { 1107 Statement::Expression(expression) => { 1108 return self.child_expression(arena, expression); 1109 } 1110 1111 Statement::Assignment(assignment) => match &assignment.kind { 1112 AssignmentKind::Let | AssignmentKind::Generated => { 1113 return self.child_expression(arena, &assignment.value); 1114 } 1115 // We can't just return the right-hand side of a `let assert` 1116 // assignment; we still need to check that the pattern matches. 1117 AssignmentKind::Assert { .. } => {} 1118 }, 1119 1120 Statement::Use(use_) => return self.child_expression(arena, &use_.call), 1121 1122 // Similar to `let assert`, we can't immediately return the value 1123 // that is asserted; we have to actually perform the assertion. 1124 Statement::Assert(_) => {} 1125 } 1126 } 1127 match &self.scope_position { 1128 Position::Tail | Position::Assign(_) | Position::Statement => { 1129 self.block_document(arena, statements) 1130 } 1131 Position::Expression(Ordering::Strict) => self.immediately_invoked_function_expression( 1132 arena, 1133 statements, 1134 |this, statements| this.statements(arena, statements), 1135 ), 1136 Position::Expression(Ordering::Loose) => self.wrap_block(arena, |this| { 1137 // Save previous scope 1138 let current_scope = this.current_scope.clone(); 1139 1140 let document = this.block_document(arena, statements); 1141 1142 // Restore previous state 1143 this.current_scope = current_scope; 1144 1145 document 1146 }), 1147 } 1148 } 1149 1150 fn block_document( 1151 &mut self, 1152 arena: &'doc DocumentArena<'a, 'doc>, 1153 statements: &'a Vec1<TypedStatement>, 1154 ) -> Document<'a, 'doc> { 1155 let statements = self.statements(arena, statements); 1156 docvec![ 1157 arena, 1158 OPEN_CURLY_DOCUMENT, 1159 docvec![arena, LINE_DOCUMENT, statements].nest(arena, INDENT), 1160 LINE_DOCUMENT, 1161 CLOSE_CURLY_DOCUMENT 1162 ] 1163 } 1164 1165 fn statements( 1166 &mut self, 1167 arena: &'doc DocumentArena<'a, 'doc>, 1168 statements: &'a [TypedStatement], 1169 ) -> Document<'a, 'doc> { 1170 // If there are any statements that need to be printed at statement level, that's 1171 // for an outer scope so we don't want to print them inside this one. 1172 let statement_level = std::mem::take(&mut self.statement_level); 1173 let count = statements.len(); 1174 let mut documents = Vec::with_capacity(count * 3); 1175 for (i, statement) in statements.iter().enumerate() { 1176 if i + 1 < count { 1177 let function_position = 1178 std::mem::replace(&mut self.function_position, Position::Statement); 1179 let scope_position = 1180 std::mem::replace(&mut self.scope_position, Position::Statement); 1181 1182 documents.push(self.statement(arena, statement)); 1183 1184 self.function_position = function_position; 1185 self.scope_position = scope_position; 1186 1187 documents.push(LINE_DOCUMENT); 1188 } else { 1189 documents.push(self.statement(arena, statement)); 1190 } 1191 1192 // If we've generated code for a statement that always throws, we 1193 // can skip code generation for all the following ones. 1194 if self.let_assert_always_panics { 1195 self.let_assert_always_panics = false; 1196 break; 1197 } 1198 } 1199 self.statement_level = statement_level; 1200 if count == 1 { 1201 arena.concat(documents) 1202 } else { 1203 arena.concat(documents).force_break(arena) 1204 } 1205 } 1206 1207 fn simple_variable_assignment( 1208 &mut self, 1209 arena: &'doc DocumentArena<'a, 'doc>, 1210 name: &'a EcoString, 1211 value: &'a TypedExpr, 1212 location: SrcSpan, 1213 ) -> Document<'a, 'doc> { 1214 // Subject must be rendered before the variable for variable numbering 1215 let subject = self.not_in_tail_position(Some(Ordering::Loose), |this| { 1216 this.wrap_expression(arena, value) 1217 }); 1218 let js_name = self.next_local_var(name); 1219 let assignment = docvec![ 1220 arena, 1221 self.source_map_tracker(arena, location.start), 1222 LET_SPACE_DOCUMENT, 1223 js_name.clone(), 1224 SPACE_EQUAL_SPACE_DOCUMENT, 1225 subject, 1226 SEMICOLON_DOCUMENT 1227 ]; 1228 let assignment = match &self.scope_position { 1229 Position::Expression(_) | Position::Statement => assignment, 1230 Position::Tail => docvec![ 1231 arena, 1232 assignment, 1233 LINE_DOCUMENT, 1234 RETURN_SPACE_DOCUMENT, 1235 js_name, 1236 SEMICOLON_DOCUMENT 1237 ], 1238 Position::Assign(block_variable) => docvec![ 1239 arena, 1240 assignment, 1241 LINE_DOCUMENT, 1242 block_variable.clone(), 1243 SPACE_EQUAL_SPACE_DOCUMENT, 1244 js_name, 1245 SEMICOLON_DOCUMENT 1246 ], 1247 }; 1248 1249 assignment.force_break(arena) 1250 } 1251 1252 fn assignment( 1253 &mut self, 1254 arena: &'doc DocumentArena<'a, 'doc>, 1255 assignment: &'a TypedAssignment, 1256 ) -> Document<'a, 'doc> { 1257 let TypedAssignment { 1258 pattern, 1259 kind, 1260 value, 1261 compiled_case, 1262 location, 1263 annotation: _, 1264 } = assignment; 1265 1266 // In case the pattern is just a variable, we special case it to 1267 // generate just a simple assignment instead of using the decision tree 1268 // for the code generation step. 1269 if let TypedPattern::Variable { name, .. } = pattern { 1270 return self.simple_variable_assignment(arena, name, value, *location); 1271 } 1272 1273 docvec![ 1274 arena, 1275 self.source_map_tracker(arena, location.start), 1276 decision::let_(arena, compiled_case, value, kind, self, pattern) 1277 ] 1278 } 1279 1280 fn assert( 1281 &mut self, 1282 arena: &'doc DocumentArena<'a, 'doc>, 1283 assert: &'a TypedAssert, 1284 ) -> Document<'a, 'doc> { 1285 let TypedAssert { 1286 location, 1287 value, 1288 message, 1289 } = assert; 1290 1291 let message = match message { 1292 Some(message) => self.not_in_tail_position(Some(Ordering::Strict), |this| { 1293 this.expression(arena, message) 1294 }), 1295 None => string(arena, "Assertion failed."), 1296 }; 1297 1298 let check = self.not_in_tail_position(Some(Ordering::Loose), |this| { 1299 this.assert_check(arena, value, &message, *location) 1300 }); 1301 1302 match &self.scope_position { 1303 Position::Expression(_) | Position::Statement => check, 1304 Position::Tail | Position::Assign(_) => { 1305 docvec![ 1306 arena, 1307 check, 1308 LINE_DOCUMENT, 1309 self.wrap_return(arena, UNDEFINED_DOCUMENT) 1310 ] 1311 } 1312 } 1313 } 1314 1315 fn assert_check( 1316 &mut self, 1317 arena: &'doc DocumentArena<'a, 'doc>, 1318 subject: &'a TypedExpr, 1319 message: &Document<'a, 'doc>, 1320 location: SrcSpan, 1321 ) -> Document<'a, 'doc> { 1322 let (subject_document, mut fields) = match subject { 1323 TypedExpr::Call { fun, arguments, .. } => { 1324 let argument_variables = arguments 1325 .iter() 1326 .map(|element| { 1327 self.not_in_tail_position(Some(Ordering::Strict), |this| { 1328 this.assign_to_variable(arena, &element.value) 1329 }) 1330 }) 1331 .collect_vec(); 1332 ( 1333 self.call_with_doc_arguments(arena, fun, argument_variables.clone()), 1334 vec![ 1335 ("kind", string(arena, "function_call")), 1336 ( 1337 "arguments", 1338 array( 1339 arena, 1340 argument_variables.into_iter().zip(arguments).map( 1341 |(variable, argument)| { 1342 self.asserted_expression( 1343 arena, 1344 AssertExpression::from_expression(&argument.value), 1345 Some(variable), 1346 argument.location(), 1347 ) 1348 }, 1349 ), 1350 ), 1351 ), 1352 ], 1353 ) 1354 } 1355 1356 TypedExpr::BinOp { 1357 operator, 1358 left, 1359 right, 1360 .. 1361 } => { 1362 match operator { 1363 BinOp::And => return self.assert_and(arena, left, right, message, location), 1364 BinOp::Or => return self.assert_or(arena, left, right, message, location), 1365 BinOp::Eq 1366 | BinOp::NotEq 1367 | BinOp::LtInt 1368 | BinOp::LtEqInt 1369 | BinOp::LtFloat 1370 | BinOp::LtEqFloat 1371 | BinOp::GtEqInt 1372 | BinOp::GtInt 1373 | BinOp::GtEqFloat 1374 | BinOp::GtFloat 1375 | BinOp::AddInt 1376 | BinOp::AddFloat 1377 | BinOp::SubInt 1378 | BinOp::SubFloat 1379 | BinOp::MultInt 1380 | BinOp::MultFloat 1381 | BinOp::DivInt 1382 | BinOp::DivFloat 1383 | BinOp::RemainderInt 1384 | BinOp::Concatenate => {} 1385 } 1386 1387 let left_document = self.not_in_tail_position(Some(Ordering::Loose), |this| { 1388 this.assign_to_variable(arena, left) 1389 }); 1390 let right_document = self.not_in_tail_position(Some(Ordering::Loose), |this| { 1391 this.assign_to_variable(arena, right) 1392 }); 1393 1394 ( 1395 self.bin_op_with_doc_operands( 1396 arena, 1397 *operator, 1398 left_document, 1399 right_document, 1400 &left.type_(), 1401 ) 1402 .surround(arena, OPEN_PAREN_DOCUMENT, CLOSE_PAREN_DOCUMENT), 1403 vec![ 1404 ("kind", string(arena, "binary_operator")), 1405 ("operator", string(arena, operator.name())), 1406 ( 1407 "left", 1408 self.asserted_expression( 1409 arena, 1410 AssertExpression::from_expression(left), 1411 Some(left_document), 1412 left.location(), 1413 ), 1414 ), 1415 ( 1416 "right", 1417 self.asserted_expression( 1418 arena, 1419 AssertExpression::from_expression(right), 1420 Some(right_document), 1421 right.location(), 1422 ), 1423 ), 1424 ], 1425 ) 1426 } 1427 1428 TypedExpr::Int { .. } 1429 | TypedExpr::Float { .. } 1430 | TypedExpr::String { .. } 1431 | TypedExpr::Block { .. } 1432 | TypedExpr::Pipeline { .. } 1433 | TypedExpr::Var { .. } 1434 | TypedExpr::Fn { .. } 1435 | TypedExpr::List { .. } 1436 | TypedExpr::Case { .. } 1437 | TypedExpr::RecordAccess { .. } 1438 | TypedExpr::PositionalAccess { .. } 1439 | TypedExpr::ModuleSelect { .. } 1440 | TypedExpr::Tuple { .. } 1441 | TypedExpr::TupleIndex { .. } 1442 | TypedExpr::Todo { .. } 1443 | TypedExpr::Panic { .. } 1444 | TypedExpr::Echo { .. } 1445 | TypedExpr::BitArray { .. } 1446 | TypedExpr::RecordUpdate { .. } 1447 | TypedExpr::NegateBool { .. } 1448 | TypedExpr::NegateInt { .. } 1449 | TypedExpr::Invalid { .. } => ( 1450 self.wrap_expression(arena, subject), 1451 vec![ 1452 ("kind", string(arena, "expression")), 1453 ( 1454 "expression", 1455 self.asserted_expression( 1456 arena, 1457 AssertExpression::from_expression(subject), 1458 Some(FALSE_LOWERCASE_DOCUMENT), 1459 subject.location(), 1460 ), 1461 ), 1462 ], 1463 ), 1464 }; 1465 1466 fields.push(("start", location.start.to_doc(arena))); 1467 fields.push(("end", subject.location().end.to_doc(arena))); 1468 fields.push(("expression_start", subject.location().start.to_doc(arena))); 1469 1470 docvec![ 1471 arena, 1472 self.source_map_tracker(arena, location.start), 1473 IF_SPACE_OPEN_PAREN_DOCUMENT, 1474 docvec![arena, EXCLAMATION_MARK_DOCUMENT, subject_document].nest(arena, INDENT), 1475 EMPTY_BREAK_DOCUMENT, 1476 CLOSE_PAREN_SPACE_OPEN_CURLY_DOCUMENT, 1477 docvec![ 1478 arena, 1479 LINE_DOCUMENT, 1480 self.throw_error(arena, "assert", message, location, fields), 1481 ] 1482 .nest(arena, INDENT), 1483 LINE_DOCUMENT, 1484 CLOSE_CURLY_DOCUMENT, 1485 ] 1486 .group(arena) 1487 } 1488 1489 fn negate_bool_expression( 1490 &mut self, 1491 arena: &'doc DocumentArena<'a, 'doc>, 1492 value: &'a TypedExpr, 1493 ) -> Document<'a, 'doc> { 1494 match value { 1495 TypedExpr::BinOp { 1496 operator, 1497 left, 1498 right, 1499 .. 1500 } => match operator { 1501 BinOp::And => self.print_bin_op(arena, left, right, "||"), 1502 BinOp::Or => self.print_bin_op(arena, left, right, "&&"), 1503 BinOp::Eq => self.equal(arena, left, right, false), 1504 BinOp::NotEq => self.equal(arena, left, right, true), 1505 BinOp::LtInt | BinOp::LtFloat => self.print_bin_op(arena, left, right, ">="), 1506 BinOp::LtEqInt | BinOp::LtEqFloat => self.print_bin_op(arena, left, right, ">"), 1507 BinOp::GtInt | BinOp::GtFloat => self.print_bin_op(arena, left, right, "<="), 1508 BinOp::GtEqInt | BinOp::GtEqFloat => self.print_bin_op(arena, left, right, "<"), 1509 BinOp::AddInt 1510 | BinOp::AddFloat 1511 | BinOp::SubInt 1512 | BinOp::SubFloat 1513 | BinOp::MultInt 1514 | BinOp::MultFloat 1515 | BinOp::DivInt 1516 | BinOp::DivFloat 1517 | BinOp::RemainderInt 1518 | BinOp::Concatenate => unreachable!("type checking should make this impossible"), 1519 }, 1520 TypedExpr::NegateBool { value, .. } => self.wrap_expression(arena, value), 1521 TypedExpr::Int { .. } 1522 | TypedExpr::Float { .. } 1523 | TypedExpr::String { .. } 1524 | TypedExpr::Block { .. } 1525 | TypedExpr::Pipeline { .. } 1526 | TypedExpr::Var { .. } 1527 | TypedExpr::Fn { .. } 1528 | TypedExpr::List { .. } 1529 | TypedExpr::Call { .. } 1530 | TypedExpr::Case { .. } 1531 | TypedExpr::RecordAccess { .. } 1532 | TypedExpr::PositionalAccess { .. } 1533 | TypedExpr::ModuleSelect { .. } 1534 | TypedExpr::Tuple { .. } 1535 | TypedExpr::TupleIndex { .. } 1536 | TypedExpr::Todo { .. } 1537 | TypedExpr::Panic { .. } 1538 | TypedExpr::Echo { .. } 1539 | TypedExpr::BitArray { .. } 1540 | TypedExpr::RecordUpdate { .. } 1541 | TypedExpr::NegateInt { .. } 1542 | TypedExpr::Invalid { .. } => docvec![ 1543 arena, 1544 EXCLAMATION_MARK_DOCUMENT, 1545 self.wrap_expression(arena, value) 1546 ], 1547 } 1548 } 1549 1550 /// In Gleam, the `&&` operator is short-circuiting, meaning that we can't 1551 /// pre-evaluate both sides of it, and use them in the exception that is 1552 /// thrown. 1553 /// Instead, we need to implement this short-circuiting logic ourself. 1554 /// 1555 /// If we short-circuit, we must leave the second expression unevaluated, 1556 /// and signal that using the `unevaluated` variant, as detailed in the 1557 /// exception format. For the first expression, we know it must be `false`, 1558 /// otherwise we would have continued by evaluating the second expression. 1559 /// 1560 /// Similarly, if we do evaluate the second expression and fail, we know 1561 /// that the first expression must have evaluated to `true`, and the second 1562 /// to `false`. This way, we avoid needing to evaluate either expression 1563 /// twice. 1564 /// 1565 /// The generated code then looks something like this: 1566 /// ```javascript 1567 /// if (expr1) { 1568 /// if (!expr2) { 1569 /// <throw exception> 1570 /// } 1571 /// } else { 1572 /// <throw exception> 1573 /// } 1574 /// ``` 1575 /// 1576 fn assert_and( 1577 &mut self, 1578 arena: &'doc DocumentArena<'a, 'doc>, 1579 left: &'a TypedExpr, 1580 right: &'a TypedExpr, 1581 message: &Document<'a, 'doc>, 1582 location: SrcSpan, 1583 ) -> Document<'a, 'doc> { 1584 let left_kind = AssertExpression::from_expression(left); 1585 let right_kind = AssertExpression::from_expression(right); 1586 1587 let fields_if_short_circuiting = vec![ 1588 ("kind", string(arena, "binary_operator")), 1589 ("operator", string(arena, "&&")), 1590 ( 1591 "left", 1592 self.asserted_expression( 1593 arena, 1594 left_kind, 1595 Some(FALSE_LOWERCASE_DOCUMENT), 1596 left.location(), 1597 ), 1598 ), 1599 ( 1600 "right", 1601 self.asserted_expression( 1602 arena, 1603 AssertExpression::Unevaluated, 1604 None, 1605 right.location(), 1606 ), 1607 ), 1608 ("start", location.start.to_doc(arena)), 1609 ("end", right.location().end.to_doc(arena)), 1610 ("expression_start", left.location().start.to_doc(arena)), 1611 ]; 1612 1613 let fields = vec![ 1614 ("kind", string(arena, "binary_operator")), 1615 ("operator", string(arena, "&&")), 1616 ( 1617 "left", 1618 self.asserted_expression( 1619 arena, 1620 left_kind, 1621 Some(TRUE_LOWERCASE_DOCUMENT), 1622 left.location(), 1623 ), 1624 ), 1625 ( 1626 "right", 1627 self.asserted_expression( 1628 arena, 1629 right_kind, 1630 Some(FALSE_LOWERCASE_DOCUMENT), 1631 right.location(), 1632 ), 1633 ), 1634 ("start", location.start.to_doc(arena)), 1635 ("end", right.location().end.to_doc(arena)), 1636 ("expression_start", left.location().start.to_doc(arena)), 1637 ]; 1638 1639 let left_value = self.not_in_tail_position(Some(Ordering::Loose), |this| { 1640 this.wrap_expression(arena, left) 1641 }); 1642 1643 let right_value = self.not_in_tail_position(Some(Ordering::Strict), |this| { 1644 this.negate_bool_expression(arena, right) 1645 }); 1646 1647 let right_check = docvec![ 1648 arena, 1649 LINE_DOCUMENT, 1650 IF_SPACE_OPEN_PAREN_DOCUMENT, 1651 right_value.nest(arena, INDENT), 1652 CLOSE_PAREN_SPACE_OPEN_CURLY_DOCUMENT, 1653 docvec![ 1654 arena, 1655 LINE_DOCUMENT, 1656 self.throw_error(arena, "assert", message, location, fields) 1657 ] 1658 .nest(arena, INDENT), 1659 LINE_DOCUMENT, 1660 CLOSE_CURLY_DOCUMENT, 1661 ]; 1662 1663 docvec![ 1664 arena, 1665 self.source_map_tracker(arena, location.start), 1666 IF_SPACE_OPEN_PAREN_DOCUMENT, 1667 left_value.nest(arena, INDENT), 1668 CLOSE_PAREN_SPACE_OPEN_CURLY_DOCUMENT, 1669 right_check.nest(arena, INDENT), 1670 LINE_DOCUMENT, 1671 CLOSE_CURLY_ELSE_OPEN_CURLY_DOCUMENT, 1672 docvec![ 1673 arena, 1674 LINE_DOCUMENT, 1675 self.throw_error( 1676 arena, 1677 "assert", 1678 message, 1679 location, 1680 fields_if_short_circuiting 1681 ) 1682 ] 1683 .nest(arena, INDENT), 1684 LINE_DOCUMENT, 1685 CLOSE_CURLY_DOCUMENT 1686 ] 1687 } 1688 1689 /// Similar to `&&`, `||` is also short-circuiting in Gleam. However, if `||` 1690 /// short-circuits, that's because the first expression evaluated to `true`, 1691 /// meaning the whole assertion succeeds. This allows us to directly use the 1692 /// `||` operator in JavaScript. 1693 /// 1694 /// The only difference is that due to the nature of `||`, if the assertion fails, 1695 /// we know that both sides must have evaluated to `false`, so we don't 1696 /// need to store the values of them in variables beforehand. 1697 fn assert_or( 1698 &mut self, 1699 arena: &'doc DocumentArena<'a, 'doc>, 1700 left: &'a TypedExpr, 1701 right: &'a TypedExpr, 1702 message: &Document<'a, 'doc>, 1703 location: SrcSpan, 1704 ) -> Document<'a, 'doc> { 1705 let fields = vec![ 1706 ("kind", string(arena, "binary_operator")), 1707 ("operator", string(arena, "||")), 1708 ( 1709 "left", 1710 self.asserted_expression( 1711 arena, 1712 AssertExpression::from_expression(left), 1713 Some(FALSE_LOWERCASE_DOCUMENT), 1714 left.location(), 1715 ), 1716 ), 1717 ( 1718 "right", 1719 self.asserted_expression( 1720 arena, 1721 AssertExpression::from_expression(right), 1722 Some(FALSE_LOWERCASE_DOCUMENT), 1723 right.location(), 1724 ), 1725 ), 1726 ("start", location.start.to_doc(arena)), 1727 ("end", right.location().end.to_doc(arena)), 1728 ("expression_start", left.location().start.to_doc(arena)), 1729 ]; 1730 1731 let left_value = self.not_in_tail_position(Some(Ordering::Loose), |this| { 1732 this.child_expression(arena, left) 1733 }); 1734 1735 let right_value = self.not_in_tail_position(Some(Ordering::Strict), |this| { 1736 this.child_expression(arena, right) 1737 }); 1738 1739 docvec![ 1740 arena, 1741 LINE_DOCUMENT, 1742 self.source_map_tracker(arena, location.start), 1743 IF_SPACE_OPEN_PAREN_DOCUMENT, 1744 docvec![ 1745 arena, 1746 EXCLAMATION_MARK_OPEN_PAREN_DOCUMENT, 1747 left_value, 1748 SPACE_DOUBLE_VERTICAL_BAR_SPACE_DOCUMENT, 1749 right_value, 1750 CLOSE_PAREN_DOCUMENT 1751 ] 1752 .nest(arena, INDENT), 1753 CLOSE_PAREN_SPACE_OPEN_CURLY_DOCUMENT, 1754 docvec![ 1755 arena, 1756 LINE_DOCUMENT, 1757 self.throw_error(arena, "assert", message, location, fields) 1758 ] 1759 .nest(arena, INDENT), 1760 LINE_DOCUMENT, 1761 CLOSE_CURLY_DOCUMENT, 1762 ] 1763 } 1764 1765 fn assign_to_variable( 1766 &mut self, 1767 arena: &'doc DocumentArena<'a, 'doc>, 1768 value: &'a TypedExpr, 1769 ) -> Document<'a, 'doc> { 1770 if let TypedExpr::Var { .. } = value { 1771 self.expression(arena, value) 1772 } else { 1773 let value = self.wrap_expression(arena, value); 1774 let variable = self.next_local_var(&ASSIGNMENT_VAR.into()); 1775 let assignment = docvec![ 1776 arena, 1777 LET_SPACE_DOCUMENT, 1778 variable.clone(), 1779 SPACE_EQUAL_SPACE_DOCUMENT, 1780 value, 1781 SEMICOLON_DOCUMENT 1782 ]; 1783 self.statement_level.push(assignment); 1784 variable.to_doc(arena) 1785 } 1786 } 1787 1788 fn asserted_expression( 1789 &mut self, 1790 arena: &'doc DocumentArena<'a, 'doc>, 1791 kind: AssertExpression, 1792 value: Option<Document<'a, 'doc>>, 1793 location: SrcSpan, 1794 ) -> Document<'a, 'doc> { 1795 let kind = match kind { 1796 AssertExpression::Literal => string(arena, "literal"), 1797 AssertExpression::Expression => string(arena, "expression"), 1798 AssertExpression::Unevaluated => string(arena, "unevaluated"), 1799 }; 1800 1801 let start = location.start.to_doc(arena); 1802 let end = location.end.to_doc(arena); 1803 let items = if let Some(value) = value { 1804 vec![ 1805 ("kind", kind), 1806 ("value", value), 1807 ("start", start), 1808 ("end", end), 1809 ] 1810 } else { 1811 vec![("kind", kind), ("start", start), ("end", end)] 1812 }; 1813 1814 wrap_object( 1815 arena, 1816 items 1817 .into_iter() 1818 .map(|(key, value)| (key.to_doc(arena), Some(value))), 1819 ) 1820 } 1821 1822 fn tuple( 1823 &mut self, 1824 arena: &'doc DocumentArena<'a, 'doc>, 1825 elements: &'a [TypedExpr], 1826 ) -> Document<'a, 'doc> { 1827 self.not_in_tail_position(Some(Ordering::Strict), |this| { 1828 array( 1829 arena, 1830 elements 1831 .iter() 1832 .map(|element| this.wrap_expression(arena, element)), 1833 ) 1834 }) 1835 } 1836 1837 fn call( 1838 &mut self, 1839 arena: &'doc DocumentArena<'a, 'doc>, 1840 fun: &'a TypedExpr, 1841 arguments: &'a [TypedCallArg], 1842 ) -> Document<'a, 'doc> { 1843 let arguments = arguments 1844 .iter() 1845 .map(|element| { 1846 self.not_in_tail_position(Some(Ordering::Strict), |this| { 1847 this.wrap_expression(arena, &element.value) 1848 }) 1849 }) 1850 .collect_vec(); 1851 1852 self.call_with_doc_arguments(arena, fun, arguments) 1853 } 1854 1855 fn call_with_doc_arguments( 1856 &mut self, 1857 arena: &'doc DocumentArena<'a, 'doc>, 1858 fun: &'a TypedExpr, 1859 arguments: Vec<Document<'a, 'doc>>, 1860 ) -> Document<'a, 'doc> { 1861 match fun { 1862 // Qualified record construction 1863 TypedExpr::ModuleSelect { 1864 constructor: ModuleValueConstructor::Record { name, .. }, 1865 module_alias, 1866 .. 1867 } => self.wrap_return( 1868 arena, 1869 construct_record(arena, Some(module_alias), name, arguments), 1870 ), 1871 1872 // Record construction 1873 TypedExpr::Var { 1874 constructor: 1875 ValueConstructor { 1876 variant: ValueConstructorVariant::Record { .. }, 1877 type_, 1878 .. 1879 }, 1880 name, 1881 .. 1882 } => { 1883 if type_.is_result_constructor() { 1884 if name == "Ok" { 1885 self.tracker.ok_used = true; 1886 } else if name == "Error" { 1887 self.tracker.error_used = true; 1888 } 1889 } 1890 self.wrap_return(arena, construct_record(arena, None, name, arguments)) 1891 } 1892 1893 // Tail call optimisation. If we are calling the current function 1894 // and we are in tail position we can avoid creating a new stack 1895 // frame, enabling recursion with constant memory usage. 1896 TypedExpr::Var { name, .. } 1897 if self.function_name == *name 1898 && self.current_function.can_recurse() 1899 && self.function_position.is_tail() 1900 && self.current_scope.counter(name) == Some(0) => 1901 { 1902 // Record that tail recursion is happening so that we know to 1903 // render the loop at the top level of the function. 1904 self.tail_recursion_used = true; 1905 arena.concat( 1906 arguments 1907 .into_iter() 1908 .zip(&self.function_arguments) 1909 .enumerate() 1910 .map(|(i, (element, argument))| { 1911 let mut doc = EMPTY_DOCUMENT; 1912 1913 if i != 0 { 1914 doc = doc.append(arena, LINE_DOCUMENT); 1915 } 1916 // Create an assignment for each variable created by the function arguments 1917 if let Some(name) = argument { 1918 doc = docvec![ 1919 arena, 1920 doc, 1921 LOOP_DOLLAR_DOCUMENT, 1922 name.to_doc(arena), 1923 SPACE_EQUAL_SPACE_DOCUMENT 1924 ] 1925 } 1926 // Render the value given to the function. Even if it is not 1927 // assigned we still render it because the expression may 1928 // have some side effects. 1929 docvec![arena, doc, element, SEMICOLON_DOCUMENT] 1930 }), 1931 ) 1932 } 1933 1934 TypedExpr::Int { .. } 1935 | TypedExpr::Float { .. } 1936 | TypedExpr::String { .. } 1937 | TypedExpr::Block { .. } 1938 | TypedExpr::Pipeline { .. } 1939 | TypedExpr::Var { .. } 1940 | TypedExpr::Fn { .. } 1941 | TypedExpr::List { .. } 1942 | TypedExpr::Call { .. } 1943 | TypedExpr::BinOp { .. } 1944 | TypedExpr::Case { .. } 1945 | TypedExpr::RecordAccess { .. } 1946 | TypedExpr::PositionalAccess { .. } 1947 | TypedExpr::ModuleSelect { .. } 1948 | TypedExpr::Tuple { .. } 1949 | TypedExpr::TupleIndex { .. } 1950 | TypedExpr::Todo { .. } 1951 | TypedExpr::Panic { .. } 1952 | TypedExpr::Echo { .. } 1953 | TypedExpr::BitArray { .. } 1954 | TypedExpr::RecordUpdate { .. } 1955 | TypedExpr::NegateBool { .. } 1956 | TypedExpr::NegateInt { .. } 1957 | TypedExpr::Invalid { .. } => { 1958 let fun = self.not_in_tail_position(None, |this| -> Document<'_, '_> { 1959 let is_fn_literal = matches!(fun, TypedExpr::Fn { .. }); 1960 let fun = this.wrap_expression(arena, fun); 1961 if is_fn_literal { 1962 docvec![arena, OPEN_PAREN_DOCUMENT, fun, CLOSE_PAREN_DOCUMENT] 1963 } else { 1964 fun 1965 } 1966 }); 1967 let arguments = call_arguments(arena, arguments); 1968 self.wrap_return(arena, docvec![arena, fun, arguments]) 1969 } 1970 } 1971 } 1972 1973 fn fn_( 1974 &mut self, 1975 arena: &'doc DocumentArena<'a, 'doc>, 1976 arguments: &'a [TypedArg], 1977 body: &'a [TypedStatement], 1978 kind: &FunctionLiteralKind, 1979 ) -> Document<'a, 'doc> { 1980 // New function, this is now the tail position 1981 let function_position = std::mem::replace(&mut self.function_position, Position::Tail); 1982 let scope_position = std::mem::replace(&mut self.scope_position, Position::Tail); 1983 1984 // And there's a new scope 1985 let scope = self.current_scope.clone(); 1986 for name in arguments.iter().flat_map(Arg::get_variable_name) { 1987 self.current_scope.set_counter(name, 0); 1988 } 1989 1990 // This is a new function so track that so that we don't 1991 // mistakenly trigger tail call optimisation 1992 let mut current_function = CurrentFunction::Anonymous; 1993 std::mem::swap(&mut self.current_function, &mut current_function); 1994 1995 // Generate the function body 1996 let result = self.statements(arena, body); 1997 1998 // Reset function name, scope, and tail position tracking 1999 self.function_position = function_position; 2000 self.scope_position = scope_position; 2001 self.current_scope = scope; 2002 std::mem::swap(&mut self.current_function, &mut current_function); 2003 2004 let mut docs = EMPTY_DOCUMENT; 2005 2006 // If the function is a use then we need to add a source map tracker 2007 // before the result to denote that the function is created by the use 2008 if let FunctionLiteralKind::Use { location } = kind { 2009 docs = docs.append(arena, self.source_map_tracker(arena, location.start)); 2010 } 2011 docs = docs.append(arena, fun_arguments(arena, arguments, false)); 2012 docs = docs.append(arena, SPACE_EQUAL_ARROW_SPACE_OPEN_CURLY_DOCUMENT); 2013 docs = docs.append(arena, BREAKABLE_SPACE_DOCUMENT); 2014 docs = docs.append(arena, result); 2015 2016 docvec![ 2017 arena, 2018 docs.nest(arena, INDENT) 2019 .append(arena, BREAKABLE_SPACE_DOCUMENT) 2020 .group(arena), 2021 CLOSE_CURLY_DOCUMENT, 2022 ] 2023 } 2024 2025 fn record_access( 2026 &mut self, 2027 arena: &'doc DocumentArena<'a, 'doc>, 2028 record: &'a TypedExpr, 2029 label: &'a str, 2030 ) -> Document<'a, 'doc> { 2031 self.not_in_tail_position(None, |this| { 2032 let record = this.wrap_expression(arena, record); 2033 docvec![arena, record, DOT_DOCUMENT, maybe_escape_property(label)] 2034 }) 2035 } 2036 2037 fn positional_access( 2038 &mut self, 2039 arena: &'doc DocumentArena<'a, 'doc>, 2040 record: &'a TypedExpr, 2041 index: u64, 2042 ) -> Document<'a, 'doc> { 2043 self.not_in_tail_position(None, |this| { 2044 let record = this.wrap_expression(arena, record); 2045 docvec![ 2046 arena, 2047 record, 2048 OPEN_SQUARE_DOCUMENT, 2049 index, 2050 CLOSE_SQUARE_DOCUMENT 2051 ] 2052 }) 2053 } 2054 2055 fn record_update( 2056 &mut self, 2057 arena: &'doc DocumentArena<'a, 'doc>, 2058 updated_record_assigned_name: &'a Option<EcoString>, 2059 updated_record: &'a TypedExpr, 2060 constructor: &'a TypedExpr, 2061 arguments: &'a [TypedCallArg], 2062 ) -> Document<'a, 'doc> { 2063 match updated_record_assigned_name.as_ref() { 2064 Some(name) => { 2065 docvec![ 2066 arena, 2067 self.not_in_tail_position(None, |this| this.simple_variable_assignment( 2068 arena, 2069 name, 2070 updated_record, 2071 updated_record.location(), 2072 )), 2073 LINE_DOCUMENT, 2074 self.call(arena, constructor, arguments), 2075 ] 2076 } 2077 None => self.call(arena, constructor, arguments), 2078 } 2079 } 2080 2081 fn tuple_index( 2082 &mut self, 2083 arena: &'doc DocumentArena<'a, 'doc>, 2084 tuple: &'a TypedExpr, 2085 index: u64, 2086 ) -> Document<'a, 'doc> { 2087 self.not_in_tail_position(None, |this| { 2088 let tuple = this.wrap_expression(arena, tuple); 2089 docvec![arena, tuple, eco_format!("[{index}]")] 2090 }) 2091 } 2092 2093 fn bin_op( 2094 &mut self, 2095 arena: &'doc DocumentArena<'a, 'doc>, 2096 name: &'a BinOp, 2097 left: &'a TypedExpr, 2098 right: &'a TypedExpr, 2099 ) -> Document<'a, 'doc> { 2100 match name { 2101 BinOp::And => self.print_bin_op(arena, left, right, "&&"), 2102 BinOp::Or => self.print_bin_op(arena, left, right, "||"), 2103 BinOp::LtInt | BinOp::LtFloat => self.print_bin_op(arena, left, right, "<"), 2104 BinOp::LtEqInt | BinOp::LtEqFloat => self.print_bin_op(arena, left, right, "<="), 2105 BinOp::Eq => self.equal(arena, left, right, true), 2106 BinOp::NotEq => self.equal(arena, left, right, false), 2107 BinOp::GtInt | BinOp::GtFloat => self.print_bin_op(arena, left, right, ">"), 2108 BinOp::GtEqInt | BinOp::GtEqFloat => self.print_bin_op(arena, left, right, ">="), 2109 BinOp::Concatenate | BinOp::AddInt | BinOp::AddFloat => { 2110 self.print_bin_op(arena, left, right, "+") 2111 } 2112 BinOp::SubInt | BinOp::SubFloat => self.print_bin_op(arena, left, right, "-"), 2113 BinOp::MultInt | BinOp::MultFloat => self.print_bin_op(arena, left, right, "*"), 2114 BinOp::RemainderInt => self.remainder_int(arena, left, right), 2115 BinOp::DivInt => self.div_int(arena, left, right), 2116 BinOp::DivFloat => self.div_float(arena, left, right), 2117 } 2118 } 2119 2120 fn div_int( 2121 &mut self, 2122 arena: &'doc DocumentArena<'a, 'doc>, 2123 left: &'a TypedExpr, 2124 right: &'a TypedExpr, 2125 ) -> Document<'a, 'doc> { 2126 let left_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| { 2127 this.child_expression(arena, left) 2128 }); 2129 let right_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| { 2130 this.child_expression(arena, right) 2131 }); 2132 2133 // If we have a constant value divided by zero then it's safe to replace 2134 // it directly with 0. 2135 if left.is_literal() && right.is_zero_compile_time_number() { 2136 "0".to_doc(arena) 2137 } else if right.is_non_zero_compile_time_number() { 2138 let division = if let TypedExpr::BinOp { .. } = left { 2139 docvec![ 2140 arena, 2141 left_doc.surround(arena, OPEN_PAREN_DOCUMENT, CLOSE_PAREN_DOCUMENT), 2142 SPACE_SLASH_SPACE_DOCUMENT, 2143 right_doc 2144 ] 2145 } else { 2146 docvec![arena, left_doc, SPACE_SLASH_SPACE_DOCUMENT, right_doc] 2147 }; 2148 docvec![ 2149 arena, 2150 GLOBAL_THIS_DOT_MATH_DOT_TRUNC_DOCUMENT, 2151 wrap_arguments(arena, [division]) 2152 ] 2153 } else { 2154 self.tracker.int_division_used = true; 2155 docvec![ 2156 arena, 2157 DIVIDE_INT_DOCUMENT, 2158 wrap_arguments(arena, [left_doc, right_doc]) 2159 ] 2160 } 2161 } 2162 2163 fn remainder_int( 2164 &mut self, 2165 arena: &'doc DocumentArena<'a, 'doc>, 2166 left: &'a TypedExpr, 2167 right: &'a TypedExpr, 2168 ) -> Document<'a, 'doc> { 2169 let left_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| { 2170 this.child_expression(arena, left) 2171 }); 2172 let right_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| { 2173 this.child_expression(arena, right) 2174 }); 2175 2176 // If we have a constant value divided by zero then it's safe to replace 2177 // it directly with 0. 2178 if left.is_literal() && right.is_zero_compile_time_number() { 2179 "0".to_doc(arena) 2180 } else if right.is_non_zero_compile_time_number() { 2181 if let TypedExpr::BinOp { .. } = left { 2182 docvec![ 2183 arena, 2184 left_doc.surround(arena, OPEN_PAREN_DOCUMENT, CLOSE_PAREN_DOCUMENT), 2185 SPACE_MODULO_SPACE_DOCUMENT, 2186 right_doc 2187 ] 2188 } else { 2189 docvec![arena, left_doc, SPACE_MODULO_SPACE_DOCUMENT, right_doc] 2190 } 2191 } else { 2192 self.tracker.int_remainder_used = true; 2193 docvec![ 2194 arena, 2195 "remainderInt", 2196 wrap_arguments(arena, [left_doc, right_doc]) 2197 ] 2198 } 2199 } 2200 2201 fn div_float( 2202 &mut self, 2203 arena: &'doc DocumentArena<'a, 'doc>, 2204 left: &'a TypedExpr, 2205 right: &'a TypedExpr, 2206 ) -> Document<'a, 'doc> { 2207 let left_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| { 2208 this.child_expression(arena, left) 2209 }); 2210 let right_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| { 2211 this.child_expression(arena, right) 2212 }); 2213 2214 // If we have a constant value divided by zero then it's safe to replace 2215 // it directly with 0. 2216 if left.is_literal() && right.is_zero_compile_time_number() { 2217 "0.0".to_doc(arena) 2218 } else if right.is_non_zero_compile_time_number() { 2219 if let TypedExpr::BinOp { .. } = left { 2220 docvec![ 2221 arena, 2222 left_doc.surround(arena, OPEN_PAREN_DOCUMENT, CLOSE_PAREN_DOCUMENT), 2223 SPACE_SLASH_SPACE_DOCUMENT, 2224 right_doc 2225 ] 2226 } else { 2227 docvec![arena, left_doc, SPACE_SLASH_SPACE_DOCUMENT, right_doc] 2228 } 2229 } else { 2230 self.tracker.float_division_used = true; 2231 docvec![ 2232 arena, 2233 "divideFloat", 2234 wrap_arguments(arena, [left_doc, right_doc]) 2235 ] 2236 } 2237 } 2238 2239 fn equal( 2240 &mut self, 2241 arena: &'doc DocumentArena<'a, 'doc>, 2242 left: &'a TypedExpr, 2243 right: &'a TypedExpr, 2244 should_be_equal: bool, 2245 ) -> Document<'a, 'doc> { 2246 // If it is a simple scalar type then we can use JS' reference identity 2247 if is_js_scalar(left.type_()) { 2248 let left_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| { 2249 this.child_expression(arena, left) 2250 }); 2251 let right_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| { 2252 this.child_expression(arena, right) 2253 }); 2254 let operator = if should_be_equal { 2255 SPACE_TRIPLE_EQUAL_SPACE_DOCUMENT 2256 } else { 2257 SPACE_EXCLAMATION_MARK_DOUBLE_EQUAL_SPACE_DOCUMENT 2258 }; 2259 return docvec![arena, left_doc, operator, right_doc]; 2260 } 2261 2262 // For comparison with singleton custom types, ie, one with no fields. 2263 // If you have some code like this 2264 // ```gleam 2265 // pub type Wibble { 2266 // Wibble 2267 // Wobble 2268 // } 2269 // 2270 // pub fn is_wibble(w: Wibble) -> Bool { 2271 // w == Wibble 2272 // } 2273 // ``` 2274 // Instead of `isEqual(w, new Wibble())`, generate `w instanceof Wibble` 2275 // because the first approach needs to construct a new Wibble, and then call the isEqual function, 2276 // which supports any shape of data, and so does a lot of extra logic which isn't necessary. 2277 2278 if let Some(doc) = self.singleton_variant_equality(arena, left, right, should_be_equal) { 2279 return doc; 2280 } 2281 2282 if let Some(doc) = self.singleton_variant_equality(arena, right, left, should_be_equal) { 2283 return doc; 2284 } 2285 2286 // Other types must be compared using structural equality 2287 let left = self.not_in_tail_position(Some(Ordering::Strict), |this| { 2288 this.wrap_expression(arena, left) 2289 }); 2290 let right = self.not_in_tail_position(Some(Ordering::Strict), |this| { 2291 this.wrap_expression(arena, right) 2292 }); 2293 2294 self.prelude_equal_call(arena, should_be_equal, left, right) 2295 } 2296 2297 fn singleton_variant_equality( 2298 &mut self, 2299 arena: &'doc DocumentArena<'a, 'doc>, 2300 left: &'a TypedExpr, 2301 right: &'a TypedExpr, 2302 should_be_equal: bool, 2303 ) -> Option<Document<'a, 'doc>> { 2304 match right { 2305 TypedExpr::Var { 2306 name, 2307 constructor: 2308 ValueConstructor { 2309 variant: 2310 ValueConstructorVariant::Record { 2311 arity: 0, 2312 name: variant_name, 2313 .. 2314 }, 2315 .. 2316 }, 2317 .. 2318 } => { 2319 let left_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| { 2320 this.wrap_expression(arena, left) 2321 }); 2322 Some(self.singleton_equal( 2323 arena, 2324 left_doc, 2325 None, 2326 name.clone(), 2327 should_be_equal, 2328 variant_name.clone(), 2329 right.type_(), 2330 )) 2331 } 2332 TypedExpr::ModuleSelect { 2333 module_alias, 2334 constructor: ModuleValueConstructor::Record { arity: 0, name, .. }, 2335 .. 2336 } => { 2337 let left_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| { 2338 this.wrap_expression(arena, left) 2339 }); 2340 Some(self.singleton_equal( 2341 arena, 2342 left_doc, 2343 Some(module_alias), 2344 name.clone(), 2345 should_be_equal, 2346 name.clone(), 2347 right.type_(), 2348 )) 2349 } 2350 // Empty lists are implemented as a variant with no fields, so we can 2351 // use `instanceof` for a faster check. 2352 TypedExpr::List { elements, .. } if elements.is_empty() => { 2353 let left_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| { 2354 this.wrap_expression(arena, left) 2355 }); 2356 self.tracker.list_empty_class_used = true; 2357 Some(self.singleton_equal( 2358 arena, 2359 left_doc, 2360 None, 2361 "$Empty".into(), 2362 should_be_equal, 2363 "Empty".into(), 2364 right.type_(), 2365 )) 2366 } 2367 TypedExpr::Int { .. } 2368 | TypedExpr::Float { .. } 2369 | TypedExpr::String { .. } 2370 | TypedExpr::Block { .. } 2371 | TypedExpr::Pipeline { .. } 2372 | TypedExpr::Var { .. } 2373 | TypedExpr::Fn { .. } 2374 | TypedExpr::List { .. } 2375 | TypedExpr::Call { .. } 2376 | TypedExpr::BinOp { .. } 2377 | TypedExpr::Case { .. } 2378 | TypedExpr::RecordAccess { .. } 2379 | TypedExpr::PositionalAccess { .. } 2380 | TypedExpr::ModuleSelect { .. } 2381 | TypedExpr::Tuple { .. } 2382 | TypedExpr::TupleIndex { .. } 2383 | TypedExpr::Todo { .. } 2384 | TypedExpr::Panic { .. } 2385 | TypedExpr::Echo { .. } 2386 | TypedExpr::BitArray { .. } 2387 | TypedExpr::RecordUpdate { .. } 2388 | TypedExpr::NegateBool { .. } 2389 | TypedExpr::NegateInt { .. } 2390 | TypedExpr::Invalid { .. } => None, 2391 } 2392 } 2393 2394 #[allow(clippy::too_many_arguments)] 2395 fn singleton_equal( 2396 &mut self, 2397 arena: &'doc DocumentArena<'a, 'doc>, 2398 value: Document<'a, 'doc>, 2399 module: Option<&'a str>, 2400 name: EcoString, 2401 should_be_equal: bool, 2402 variant_name: EcoString, 2403 type_: Arc<Type>, 2404 ) -> Document<'a, 'doc> { 2405 // If we're using this variant unqualified, register it as used so that 2406 // we know to import it. This `instanceof` check only happens if the 2407 // variant has no fields, so we don't need to check that here. 2408 if module.is_none() 2409 && let Some((package, module, type_name)) = type_.named_type_name_and_package() 2410 { 2411 _ = self 2412 .tracker 2413 .variants_used_in_instanceof 2414 .insert(TypeVariant { 2415 package, 2416 module, 2417 type_name, 2418 name: variant_name, 2419 }); 2420 } 2421 let record = if let Some(module) = module { 2422 docvec![arena, DOLLAR_DOCUMENT, module, DOT_DOCUMENT, name] 2423 } else { 2424 name.to_doc(arena) 2425 }; 2426 2427 if should_be_equal { 2428 docvec![arena, value, SPACE_INSTANCE_OF_SPACE_DOCUMENT, record] 2429 } else { 2430 docvec![ 2431 arena, 2432 EXCLAMATION_MARK_OPEN_PAREN_DOCUMENT, 2433 value, 2434 SPACE_INSTANCE_OF_SPACE_DOCUMENT, 2435 record, 2436 CLOSE_PAREN_DOCUMENT 2437 ] 2438 } 2439 } 2440 2441 fn equal_with_doc_operands( 2442 &mut self, 2443 arena: &'doc DocumentArena<'a, 'doc>, 2444 left: Document<'a, 'doc>, 2445 right: Document<'a, 'doc>, 2446 type_: Arc<Type>, 2447 should_be_equal: bool, 2448 ) -> Document<'a, 'doc> { 2449 // If it is a simple scalar type then we can use JS' reference identity 2450 if is_js_scalar(type_) { 2451 let operator = if should_be_equal { 2452 SPACE_TRIPLE_EQUAL_SPACE_DOCUMENT 2453 } else { 2454 SPACE_EXCLAMATION_MARK_DOUBLE_EQUAL_SPACE_DOCUMENT 2455 }; 2456 return docvec![arena, left, operator, right]; 2457 } 2458 2459 // Other types must be compared using structural equality 2460 self.prelude_equal_call(arena, should_be_equal, left, right) 2461 } 2462 2463 pub(super) fn prelude_equal_call( 2464 &mut self, 2465 arena: &'doc DocumentArena<'a, 'doc>, 2466 should_be_equal: bool, 2467 left: Document<'a, 'doc>, 2468 right: Document<'a, 'doc>, 2469 ) -> Document<'a, 'doc> { 2470 // Record that we need to import the prelude's isEqual function into the module 2471 self.tracker.object_equality_used = true; 2472 // Construct the call 2473 let arguments = wrap_arguments(arena, [left, right]); 2474 let operator = if should_be_equal { 2475 IS_EQUAL_DOCUMENT 2476 } else { 2477 EXCLAMATION_MARK_IS_EQUAL_DOCUMENT 2478 }; 2479 docvec![arena, operator, arguments] 2480 } 2481 2482 fn print_bin_op( 2483 &mut self, 2484 arena: &'doc DocumentArena<'a, 'doc>, 2485 left: &'a TypedExpr, 2486 right: &'a TypedExpr, 2487 op: &'a str, 2488 ) -> Document<'a, 'doc> { 2489 let left = self.not_in_tail_position(Some(Ordering::Strict), |this| { 2490 this.child_expression(arena, left) 2491 }); 2492 let right = self.not_in_tail_position(Some(Ordering::Strict), |this| { 2493 this.child_expression(arena, right) 2494 }); 2495 docvec![arena, left, SPACE_DOCUMENT, op, SPACE_DOCUMENT, right] 2496 } 2497 2498 pub(super) fn bin_op_with_doc_operands( 2499 &mut self, 2500 arena: &'doc DocumentArena<'a, 'doc>, 2501 name: BinOp, 2502 left: Document<'a, 'doc>, 2503 right: Document<'a, 'doc>, 2504 type_: &Arc<Type>, 2505 ) -> Document<'a, 'doc> { 2506 match name { 2507 BinOp::And => docvec![arena, left, SPACE_DOUBLE_AMPERSAND_SPACE_DOCUMENT, right], 2508 BinOp::Or => docvec![arena, left, SPACE_DOUBLE_VERTICAL_BAR_SPACE_DOCUMENT, right], 2509 BinOp::LtInt | BinOp::LtFloat => { 2510 docvec![arena, left, SPACE_LT_INT_SPACE_DOCUMENT, right] 2511 } 2512 BinOp::LtEqInt | BinOp::LtEqFloat => { 2513 docvec![arena, left, SPACE_LT_EQ_INT_SPACE_DOCUMENT, right] 2514 } 2515 BinOp::Eq => self.equal_with_doc_operands(arena, left, right, type_.clone(), true), 2516 BinOp::NotEq => self.equal_with_doc_operands(arena, left, right, type_.clone(), false), 2517 BinOp::GtInt | BinOp::GtFloat => { 2518 docvec![arena, left, SPACE_GT_INT_SPACE_DOCUMENT, right] 2519 } 2520 BinOp::GtEqInt | BinOp::GtEqFloat => { 2521 docvec![arena, left, SPACE_GT_EQ_INT_SPACE_DOCUMENT, right] 2522 } 2523 BinOp::Concatenate | BinOp::AddInt | BinOp::AddFloat => { 2524 docvec![arena, left, SPACE_PLUS_SPACE_DOCUMENT, right] 2525 } 2526 BinOp::SubInt | BinOp::SubFloat => { 2527 docvec![arena, left, SPACE_MINUS_SPACE_DOCUMENT, right] 2528 } 2529 BinOp::MultInt | BinOp::MultFloat => { 2530 docvec![arena, left, SPACE_TIMES_SPACE_DOCUMENT, right] 2531 } 2532 BinOp::RemainderInt => { 2533 self.tracker.int_remainder_used = true; 2534 docvec![ 2535 arena, 2536 CAMEL_CASE_REMAINDER_INT_DOCUMENT, 2537 wrap_arguments(arena, [left, right]) 2538 ] 2539 } 2540 BinOp::DivInt => { 2541 self.tracker.int_division_used = true; 2542 docvec![ 2543 arena, 2544 DIVIDE_INT_DOCUMENT, 2545 wrap_arguments(arena, [left, right]) 2546 ] 2547 } 2548 BinOp::DivFloat => { 2549 self.tracker.float_division_used = true; 2550 docvec![ 2551 arena, 2552 DIVIDE_FLOAT_DOCUMENT, 2553 wrap_arguments(arena, [left, right]) 2554 ] 2555 } 2556 } 2557 } 2558 2559 fn todo( 2560 &mut self, 2561 arena: &'doc DocumentArena<'a, 'doc>, 2562 message: Option<&'a TypedExpr>, 2563 location: &'a SrcSpan, 2564 ) -> Document<'a, 'doc> { 2565 let message = match message { 2566 Some(m) => self.not_in_tail_position(None, |this| this.wrap_expression(arena, m)), 2567 None => string( 2568 arena, 2569 "`todo` expression evaluated. This code has not yet been implemented.", 2570 ), 2571 }; 2572 self.throw_error(arena, "todo", &message, *location, vec![]) 2573 } 2574 2575 fn panic( 2576 &mut self, 2577 arena: &'doc DocumentArena<'a, 'doc>, 2578 location: &'a SrcSpan, 2579 message: Option<&'a TypedExpr>, 2580 ) -> Document<'a, 'doc> { 2581 let message = match message { 2582 Some(m) => self.not_in_tail_position(None, |this| this.wrap_expression(arena, m)), 2583 None => string(arena, "`panic` expression evaluated."), 2584 }; 2585 self.throw_error(arena, "panic", &message, *location, vec![]) 2586 } 2587 2588 pub(crate) fn throw_error<Fields>( 2589 &mut self, 2590 arena: &'doc DocumentArena<'a, 'doc>, 2591 error_name: &'a str, 2592 message: &Document<'a, 'doc>, 2593 location: SrcSpan, 2594 fields: Fields, 2595 ) -> Document<'a, 'doc> 2596 where 2597 Fields: IntoIterator<Item = (&'a str, Document<'a, 'doc>)>, 2598 { 2599 self.tracker.make_error_used = true; 2600 let module = self.module_name.clone().to_doc(arena).surround( 2601 arena, 2602 DOUBLE_QUOTE_DOCUMENT, 2603 DOUBLE_QUOTE_DOCUMENT, 2604 ); 2605 let function = self.function_name.clone().to_doc(arena).surround( 2606 arena, 2607 DOUBLE_QUOTE_DOCUMENT, 2608 DOUBLE_QUOTE_DOCUMENT, 2609 ); 2610 let line = self.line_numbers.line_number(location.start).to_doc(arena); 2611 let fields = wrap_object( 2612 arena, 2613 fields.into_iter().map(|(k, v)| (k.to_doc(arena), Some(v))), 2614 ); 2615 2616 docvec![ 2617 arena, 2618 self.source_map_tracker(arena, location.start), 2619 THROW_MAKE_ERROR_DOCUMENT, 2620 wrap_arguments( 2621 arena, 2622 [ 2623 string(arena, error_name), 2624 FILEPATH_UPPERCASE_DOCUMENT, 2625 module, 2626 line, 2627 function, 2628 *message, 2629 fields 2630 ] 2631 ), 2632 ] 2633 } 2634 2635 fn module_select( 2636 &mut self, 2637 arena: &'doc DocumentArena<'a, 'doc>, 2638 module: &'a str, 2639 label: &'a EcoString, 2640 constructor: &'a ModuleValueConstructor, 2641 ) -> Document<'a, 'doc> { 2642 match constructor { 2643 ModuleValueConstructor::Fn { .. } | ModuleValueConstructor::Constant { .. } => { 2644 docvec![ 2645 arena, 2646 DOLLAR_DOCUMENT, 2647 module, 2648 DOT_DOCUMENT, 2649 maybe_escape_identifier(label) 2650 ] 2651 } 2652 2653 ModuleValueConstructor::Record { 2654 name, arity, type_, .. 2655 } => self.record_constructor(arena, type_.clone(), Some(module), name, name, *arity), 2656 } 2657 } 2658 2659 fn echo( 2660 &mut self, 2661 arena: &'doc DocumentArena<'a, 'doc>, 2662 expression: Document<'a, 'doc>, 2663 message: Option<&'a TypedExpr>, 2664 location: &'a SrcSpan, 2665 ) -> Document<'a, 'doc> { 2666 self.tracker.echo_used = true; 2667 2668 let message = match message { 2669 Some(message) => self.not_in_tail_position(Some(Ordering::Strict), |this| { 2670 this.wrap_expression(arena, message) 2671 }), 2672 None => UNDEFINED_DOCUMENT, 2673 }; 2674 2675 let echo_arguments = call_arguments( 2676 arena, 2677 vec![ 2678 expression, 2679 message, 2680 self.src_path.clone().to_doc(arena), 2681 self.line_numbers.line_number(location.start).to_doc(arena), 2682 ], 2683 ); 2684 self.wrap_return(arena, docvec![arena, ECHO_DOCUMENT, echo_arguments]) 2685 } 2686 2687 pub(crate) fn constant_expression( 2688 &mut self, 2689 arena: &'doc DocumentArena<'a, 'doc>, 2690 context: Context, 2691 expression: &'a TypedConstant, 2692 ) -> Document<'a, 'doc> { 2693 match expression { 2694 Constant::Int { value, .. } => int(arena, value), 2695 Constant::Float { value, .. } => float(arena, value), 2696 Constant::String { value, .. } => string(arena, value), 2697 Constant::Tuple { elements, .. } => array( 2698 arena, 2699 elements 2700 .iter() 2701 .map(|element| self.constant_expression(arena, context, element)), 2702 ), 2703 2704 Constant::List { elements, tail, .. } => { 2705 if tail.is_none() && elements.is_empty() { 2706 return self.empty_list(); 2707 } 2708 2709 self.tracker.list_used = true; 2710 let list = match tail { 2711 // There's no tail in the list, we join all the elements and 2712 // call it a day. 2713 None => list( 2714 arena, 2715 elements 2716 .iter() 2717 .map(|element| self.constant_expression(arena, context, element)), 2718 ), 2719 2720 Some(tail) => match tail.list_elements() { 2721 // There's a tail in the list whose elements are all 2722 // known at compile time. In this case we replace the 2723 // tail with those elements and create a single flat 2724 // list. 2725 Some(tail_elements) => list( 2726 arena, 2727 elements 2728 .iter() 2729 .chain(tail_elements) 2730 .map(|element| self.constant_expression(arena, context, element)), 2731 ), 2732 // There's a tail in the list but we can't really tell 2733 // what its elements are at compile time. This means we 2734 // have to prepend to this list. 2735 None => { 2736 self.tracker.prepend_used = true; 2737 let tail = self.constant_expression(arena, context, tail); 2738 prepend( 2739 arena, 2740 elements.iter().map(|element| { 2741 self.constant_expression(arena, context, element) 2742 }), 2743 tail, 2744 ) 2745 } 2746 }, 2747 }; 2748 match context { 2749 Context::Constant => docvec![arena, PURE_JAVASCRIPT_COMMENT_DOCUMENT, list], 2750 Context::Guard => list, 2751 } 2752 } 2753 2754 Constant::Record { type_, name, .. } if type_.is_bool() && name == "True" => { 2755 TRUE_LOWERCASE_DOCUMENT 2756 } 2757 Constant::Record { type_, name, .. } if type_.is_bool() && name == "False" => { 2758 FALSE_LOWERCASE_DOCUMENT 2759 } 2760 Constant::Record { type_, .. } if type_.is_nil() => UNDEFINED_DOCUMENT, 2761 2762 Constant::Record { 2763 arguments, 2764 module, 2765 name, 2766 type_, 2767 .. 2768 } => { 2769 let tag = expression 2770 .constant_record_tag() 2771 .expect("record without inferred constructor made it to code generation"); 2772 2773 if module.is_none() && type_.is_result() { 2774 if tag == "Ok" { 2775 self.tracker.ok_used = true; 2776 } else { 2777 self.tracker.error_used = true; 2778 } 2779 } 2780 2781 // If there's no arguments, then this is either a constructor 2782 // which takes arguments being referenced rather than called, 2783 // or a variant with no fields at all. 2784 if arguments.is_none() { 2785 // If the constructor is not a function that takes arguments, 2786 // it effectively has zero arity. 2787 let arity = type_.fn_arity().unwrap_or(0) as u16; 2788 return self.record_constructor( 2789 arena, 2790 type_.clone(), 2791 module.as_ref().map(|(name, _)| name.as_str()), 2792 &tag, 2793 name, 2794 arity, 2795 ); 2796 } 2797 2798 // Otherwise we're always constructing a record! Record updates 2799 // are fully expanded during type checking, so we just need to 2800 // handle the arguments here. 2801 let field_values = arguments 2802 .iter() 2803 .flatten() 2804 .map(|argument| self.constant_expression(arena, context, &argument.value)) 2805 .collect_vec(); 2806 2807 let constructor = construct_record( 2808 arena, 2809 module.as_ref().map(|(module, _)| module.as_str()), 2810 name, 2811 field_values, 2812 ); 2813 match context { 2814 Context::Constant => { 2815 docvec![arena, PURE_JAVASCRIPT_COMMENT_DOCUMENT, constructor] 2816 } 2817 Context::Guard => constructor, 2818 } 2819 } 2820 Constant::BitArray { segments, .. } => { 2821 let bit_array = self.constant_bit_array(arena, segments, context); 2822 match context { 2823 Context::Constant => { 2824 docvec![arena, PURE_JAVASCRIPT_COMMENT_DOCUMENT, bit_array] 2825 } 2826 Context::Guard => bit_array, 2827 } 2828 } 2829 2830 Constant::Var { name, module, .. } => { 2831 match (module, context) { 2832 (None, Context::Guard) => self.local_var(name).to_doc(arena), 2833 (None, Context::Constant) => maybe_escape_identifier(name).to_doc(arena), 2834 (Some((module, _)), _) => { 2835 // JS keywords can be accessed here, but we must escape anyway 2836 // as we escape when exporting such names in the first place, 2837 // and the imported name has to match the exported name. 2838 docvec![ 2839 arena, 2840 DOLLAR_DOCUMENT, 2841 module, 2842 DOT_DOCUMENT, 2843 maybe_escape_identifier(name) 2844 ] 2845 } 2846 } 2847 } 2848 2849 Constant::StringConcatenation { left, right, .. } => { 2850 let left = self.constant_expression(arena, context, left); 2851 let right = self.constant_expression(arena, context, right); 2852 docvec![arena, left, SPACE_PLUS_SPACE_DOCUMENT, right] 2853 } 2854 2855 Constant::RecordUpdate { .. } => { 2856 panic!("record updates should not reach code generation") 2857 } 2858 Constant::Todo { .. } => { 2859 panic!("todo constants should not reach code generation") 2860 } 2861 Constant::Invalid { .. } => { 2862 panic!("invalid constants should not reach code generation") 2863 } 2864 } 2865 } 2866 2867 fn constant_bit_array( 2868 &mut self, 2869 arena: &'doc DocumentArena<'a, 'doc>, 2870 segments: &'a [TypedConstantBitArraySegment], 2871 context: Context, 2872 ) -> Document<'a, 'doc> { 2873 self.tracker.bit_array_literal_used = true; 2874 let segments_array = array( 2875 arena, 2876 segments.iter().map(|segment| { 2877 let value = match context { 2878 Context::Constant => self.constant_expression(arena, context, &segment.value), 2879 Context::Guard => self.guard_constant_expression(arena, &segment.value), 2880 }; 2881 2882 let details = self.constant_bit_array_segment_details(arena, segment, context); 2883 2884 match details.type_ { 2885 BitArraySegmentType::BitArray => { 2886 if segment.size().is_some() { 2887 self.tracker.bit_array_slice_used = true; 2888 docvec![ 2889 arena, 2890 BIT_ARRAY_SLICE_OPEN_PAREN_DOCUMENT, 2891 value, 2892 COMMA_ZERO_COMMA_SPACE, 2893 details.size, 2894 CLOSE_PAREN_DOCUMENT 2895 ] 2896 } else { 2897 value 2898 } 2899 } 2900 BitArraySegmentType::Int => { 2901 match (details.size_value, segment.value.as_ref()) { 2902 (Some(size_value), Constant::Int { int_value, .. }) 2903 if size_value <= SAFE_INT_SEGMENT_MAX_SIZE.into() 2904 && (&size_value % BigInt::from(8) == BigInt::ZERO) => 2905 { 2906 let bytes = bit_array_segment_int_value_to_bytes( 2907 int_value.clone(), 2908 size_value, 2909 segment.endianness(), 2910 ); 2911 2912 u8_slice(arena, &bytes) 2913 } 2914 2915 (Some(size_value), _) if size_value == 8.into() => value, 2916 2917 (Some(size_value), _) if size_value <= 0.into() => EMPTY_DOCUMENT, 2918 2919 _ => { 2920 self.tracker.sized_integer_segment_used = true; 2921 let size = details.size; 2922 let is_big = bool(segment.endianness().is_big()); 2923 docvec![ 2924 arena, 2925 SIZED_INT_OPEN_PAREN_DOCUMENT, 2926 value, 2927 COMMA_SPACE_DOCUMENT, 2928 size, 2929 COMMA_SPACE_DOCUMENT, 2930 is_big, 2931 CLOSE_PAREN_DOCUMENT 2932 ] 2933 } 2934 } 2935 } 2936 BitArraySegmentType::Float => { 2937 self.tracker.float_bit_array_segment_used = true; 2938 let size = details.size; 2939 let is_big = bool(details.endianness.is_big()); 2940 docvec![ 2941 arena, 2942 SIZED_FLOAT_OPEN_PAREN_DOCUMENT, 2943 value, 2944 COMMA_SPACE_DOCUMENT, 2945 size, 2946 COMMA_SPACE_DOCUMENT, 2947 is_big, 2948 CLOSE_PAREN_DOCUMENT 2949 ] 2950 } 2951 BitArraySegmentType::String(StringEncoding::Utf8) => { 2952 self.tracker.string_bit_array_segment_used = true; 2953 docvec![ 2954 arena, 2955 STRING_BITS_OPEN_PAREN_DOCUMENT, 2956 value, 2957 CLOSE_PAREN_DOCUMENT 2958 ] 2959 } 2960 BitArraySegmentType::String(StringEncoding::Utf16) => { 2961 self.tracker.string_utf16_bit_array_segment_used = true; 2962 let is_big = bool(details.endianness.is_big()); 2963 docvec![ 2964 arena, 2965 STRING_TO_UTF16_OPEN_PAREN_DOCUMENT, 2966 value, 2967 COMMA_SPACE_DOCUMENT, 2968 is_big, 2969 CLOSE_PAREN_DOCUMENT 2970 ] 2971 } 2972 BitArraySegmentType::String(StringEncoding::Utf32) => { 2973 self.tracker.string_utf32_bit_array_segment_used = true; 2974 let is_big = bool(details.endianness.is_big()); 2975 docvec![ 2976 arena, 2977 STRING_TO_UTF32_OPEN_PAREN_DOCUMENT, 2978 value, 2979 COMMA_SPACE_DOCUMENT, 2980 is_big, 2981 CLOSE_PAREN_DOCUMENT 2982 ] 2983 } 2984 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf8) => { 2985 self.tracker.codepoint_bit_array_segment_used = true; 2986 docvec![ 2987 arena, 2988 CODEPOINT_BITS_OPEN_PAREN_DOCUMENT, 2989 value, 2990 CLOSE_PAREN_DOCUMENT 2991 ] 2992 } 2993 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf16) => { 2994 self.tracker.codepoint_utf16_bit_array_segment_used = true; 2995 let is_big = bool(details.endianness.is_big()); 2996 docvec![ 2997 arena, 2998 CODEPOINT_TO_UTF16_OPEN_PAREN_DOCUMENT, 2999 value, 3000 COMMA_SPACE_DOCUMENT, 3001 is_big, 3002 CLOSE_PAREN_DOCUMENT 3003 ] 3004 } 3005 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf32) => { 3006 self.tracker.codepoint_utf32_bit_array_segment_used = true; 3007 let is_big = bool(details.endianness.is_big()); 3008 docvec![ 3009 arena, 3010 CODEPOINT_TO_UTF32_OPEN_PAREN_DOCUMENT, 3011 value, 3012 COMMA_SPACE_DOCUMENT, 3013 is_big, 3014 CLOSE_PAREN_DOCUMENT 3015 ] 3016 } 3017 } 3018 }), 3019 ); 3020 3021 docvec![ 3022 arena, 3023 TO_BIT_ARRAY_OPEN_PAREN_DOCUMENT, 3024 segments_array, 3025 CLOSE_PAREN_DOCUMENT 3026 ] 3027 } 3028 3029 fn constant_bit_array_segment_details( 3030 &mut self, 3031 arena: &'doc DocumentArena<'a, 'doc>, 3032 segment: &'a TypedConstantBitArraySegment, 3033 context: Context, 3034 ) -> BitArraySegmentDetails<'a, 'doc> { 3035 let size = segment.size(); 3036 let unit = segment.unit(); 3037 let (size_value, size) = match size { 3038 Some(Constant::Int { int_value, .. }) => { 3039 let size_value = int_value * unit; 3040 let size = eco_format!("{size_value}").to_doc(arena); 3041 (Some(size_value), size) 3042 } 3043 3044 Some(size) => { 3045 let mut size = match context { 3046 Context::Constant => self.constant_expression(arena, context, size), 3047 Context::Guard => self.guard_constant_expression(arena, size), 3048 }; 3049 if unit != 1 { 3050 size = size.group(arena).append( 3051 arena, 3052 SPACE_TIMES_SPACE_DOCUMENT.append(arena, unit.to_doc(arena)), 3053 ); 3054 } 3055 3056 (None, size) 3057 } 3058 3059 None => { 3060 let size_value: usize = if segment.type_.is_int() { 8 } else { 64 }; 3061 (Some(BigInt::from(size_value)), size_value.to_doc(arena)) 3062 } 3063 }; 3064 3065 let type_ = BitArraySegmentType::from_segment(segment); 3066 3067 BitArraySegmentDetails { 3068 type_, 3069 size, 3070 size_value, 3071 endianness: segment.endianness(), 3072 } 3073 } 3074 3075 pub(crate) fn guard( 3076 &mut self, 3077 arena: &'doc DocumentArena<'a, 'doc>, 3078 guard: &'a TypedClauseGuard, 3079 ) -> Document<'a, 'doc> { 3080 match guard { 3081 ClauseGuard::Invalid { .. } => unreachable!("invalid guard made it to code generation"), 3082 3083 ClauseGuard::Block { value, .. } => { 3084 self.guard(arena, value) 3085 .surround(arena, OPEN_PAREN_DOCUMENT, CLOSE_PAREN_DOCUMENT) 3086 } 3087 3088 ClauseGuard::BinaryOperator { 3089 left, 3090 right, 3091 operator, 3092 .. 3093 } => { 3094 let operator = match operator { 3095 BinOp::Eq if is_js_scalar(left.type_()) => TRIPLE_EQUAL_DOCUMENT, 3096 BinOp::NotEq if is_js_scalar(left.type_()) => { 3097 EXCLAMATION_MARK_DOUBLE_EQUAL_DOCUMENT 3098 } 3099 BinOp::Eq | BinOp::NotEq => { 3100 let should_be_equal = *operator == BinOp::Eq; 3101 3102 // Handle singleton equality optimization for guards 3103 if let Some(doc) = self.singleton_variant_guard_equality( 3104 arena, 3105 left, 3106 right, 3107 should_be_equal, 3108 ) { 3109 return doc; 3110 } 3111 3112 if let Some(doc) = self.singleton_variant_guard_equality( 3113 arena, 3114 right, 3115 left, 3116 should_be_equal, 3117 ) { 3118 return doc; 3119 } 3120 3121 let left_doc = self.guard(arena, left); 3122 let right_doc = self.guard(arena, right); 3123 return self.prelude_equal_call( 3124 arena, 3125 should_be_equal, 3126 left_doc, 3127 right_doc, 3128 ); 3129 } 3130 3131 BinOp::GtFloat | BinOp::GtInt => GT_INT_DOCUMENT, 3132 BinOp::GtEqFloat | BinOp::GtEqInt => GT_EQ_INT_DOCUMENT, 3133 BinOp::LtFloat | BinOp::LtInt => LT_INT_DOCUMENT, 3134 BinOp::LtEqFloat | BinOp::LtEqInt => LT_EQ_INT_DOCUMENT, 3135 3136 BinOp::AddFloat | BinOp::AddInt | BinOp::Concatenate => ADD_INT_DOCUMENT, 3137 BinOp::SubFloat | BinOp::SubInt => SUB_INT_DOCUMENT, 3138 BinOp::MultFloat | BinOp::MultInt => MULT_INT_DOCUMENT, 3139 3140 BinOp::DivFloat => { 3141 self.tracker.float_division_used = true; 3142 3143 return docvec![ 3144 arena, 3145 DIVIDE_FLOAT_DOCUMENT, 3146 wrap_arguments( 3147 arena, 3148 [self.guard(arena, left), self.guard(arena, right)] 3149 ) 3150 ]; 3151 } 3152 3153 BinOp::DivInt => { 3154 self.tracker.int_division_used = true; 3155 return docvec![ 3156 arena, 3157 DIVIDE_INT_DOCUMENT, 3158 wrap_arguments( 3159 arena, 3160 [self.guard(arena, left), self.guard(arena, right)] 3161 ) 3162 ]; 3163 } 3164 3165 BinOp::RemainderInt => { 3166 self.tracker.int_remainder_used = true; 3167 return docvec![ 3168 arena, 3169 CAMEL_CASE_REMAINDER_INT_DOCUMENT, 3170 wrap_arguments( 3171 arena, 3172 [self.guard(arena, left), self.guard(arena, right)] 3173 ) 3174 ]; 3175 } 3176 3177 BinOp::And => AND_DOCUMENT, 3178 BinOp::Or => OR_DOCUMENT, 3179 }; 3180 3181 let left_document = self.wrapped_guard(arena, left); 3182 let right_document = self.wrapped_guard(arena, right); 3183 3184 docvec![ 3185 arena, 3186 left_document, 3187 SPACE_DOCUMENT, 3188 operator, 3189 SPACE_DOCUMENT, 3190 right_document 3191 ] 3192 } 3193 3194 ClauseGuard::Var { name, .. } => self.local_var(name).to_doc(arena), 3195 3196 ClauseGuard::TupleIndex { tuple, index, .. } => { 3197 docvec![ 3198 arena, 3199 self.guard(arena, tuple,), 3200 OPEN_SQUARE_DOCUMENT, 3201 *index, 3202 CLOSE_SQUARE_DOCUMENT 3203 ] 3204 } 3205 3206 ClauseGuard::FieldAccess { 3207 label, container, .. 3208 } => docvec![ 3209 arena, 3210 self.guard(arena, container), 3211 DOT_DOCUMENT, 3212 maybe_escape_property(label) 3213 ], 3214 3215 ClauseGuard::ModuleSelect { 3216 module_alias, 3217 label, 3218 .. 3219 } => docvec![arena, DOLLAR_DOCUMENT, module_alias, DOT_DOCUMENT, label], 3220 3221 ClauseGuard::Not { expression, .. } => { 3222 docvec![ 3223 arena, 3224 EXCLAMATION_MARK_DOCUMENT, 3225 self.guard(arena, expression,) 3226 ] 3227 } 3228 3229 ClauseGuard::Constant(constant) => self.guard_constant_expression(arena, constant), 3230 } 3231 } 3232 3233 fn singleton_variant_guard_equality( 3234 &mut self, 3235 arena: &'doc DocumentArena<'a, 'doc>, 3236 left: &'a TypedClauseGuard, 3237 right: &'a TypedClauseGuard, 3238 should_be_equal: bool, 3239 ) -> Option<Document<'a, 'doc>> { 3240 match right { 3241 ClauseGuard::Constant(Constant::Record { 3242 record_constructor: Some(constructor), 3243 module, 3244 name, 3245 .. 3246 }) if let ValueConstructorVariant::Record { 3247 arity: 0, 3248 name: variant_name, 3249 .. 3250 } = &constructor.variant => 3251 { 3252 let left_doc = self.guard(arena, left); 3253 Some(self.singleton_equal( 3254 arena, 3255 left_doc, 3256 module.as_ref().map(|(module, _)| module.as_str()), 3257 name.clone(), 3258 should_be_equal, 3259 variant_name.clone(), 3260 right.type_(), 3261 )) 3262 } 3263 ClauseGuard::Constant(Constant::List { 3264 elements, 3265 tail: None, 3266 .. 3267 }) if elements.is_empty() => { 3268 let left_doc = self.guard(arena, left); 3269 self.tracker.list_empty_class_used = true; 3270 Some(self.singleton_equal( 3271 arena, 3272 left_doc, 3273 None, 3274 "$Empty".into(), 3275 should_be_equal, 3276 "Empty".into(), 3277 right.type_(), 3278 )) 3279 } 3280 ClauseGuard::Block { .. } 3281 | ClauseGuard::BinaryOperator { .. } 3282 | ClauseGuard::Not { .. } 3283 | ClauseGuard::Var { .. } 3284 | ClauseGuard::TupleIndex { .. } 3285 | ClauseGuard::FieldAccess { .. } 3286 | ClauseGuard::ModuleSelect { .. } 3287 | ClauseGuard::Constant(_) 3288 | ClauseGuard::Invalid { .. } => None, 3289 } 3290 } 3291 3292 fn wrapped_guard( 3293 &mut self, 3294 arena: &'doc DocumentArena<'a, 'doc>, 3295 guard: &'a TypedClauseGuard, 3296 ) -> Document<'a, 'doc> { 3297 match guard { 3298 ClauseGuard::Invalid { .. } => unreachable!("invalid guard made it to code generation"), 3299 ClauseGuard::Var { .. } 3300 | ClauseGuard::TupleIndex { .. } 3301 | ClauseGuard::Constant(_) 3302 | ClauseGuard::Not { .. } 3303 | ClauseGuard::FieldAccess { .. } 3304 | ClauseGuard::Block { .. } => self.guard(arena, guard), 3305 3306 ClauseGuard::BinaryOperator { .. } | ClauseGuard::ModuleSelect { .. } => { 3307 docvec![ 3308 arena, 3309 OPEN_PAREN_DOCUMENT, 3310 self.guard(arena, guard), 3311 CLOSE_PAREN_DOCUMENT 3312 ] 3313 } 3314 } 3315 } 3316 3317 fn guard_constant_expression( 3318 &mut self, 3319 arena: &'doc DocumentArena<'a, 'doc>, 3320 expression: &'a TypedConstant, 3321 ) -> Document<'a, 'doc> { 3322 match expression { 3323 Constant::Tuple { elements, .. } => array( 3324 arena, 3325 elements 3326 .iter() 3327 .map(|element| self.guard_constant_expression(arena, element)), 3328 ), 3329 3330 Constant::Record { type_, name, .. } if type_.is_bool() && name == "True" => { 3331 TRUE_LOWERCASE_DOCUMENT 3332 } 3333 Constant::Record { type_, name, .. } if type_.is_bool() && name == "False" => { 3334 FALSE_LOWERCASE_DOCUMENT 3335 } 3336 Constant::Record { type_, .. } if type_.is_nil() => UNDEFINED_DOCUMENT, 3337 3338 Constant::BitArray { segments, .. } => { 3339 self.constant_bit_array(arena, segments, Context::Guard) 3340 } 3341 3342 Constant::Var { name, .. } => self.local_var(name).to_doc(arena), 3343 3344 Constant::Record { .. } 3345 | Constant::Int { .. } 3346 | Constant::Float { .. } 3347 | Constant::String { .. } 3348 | Constant::List { .. } 3349 | Constant::RecordUpdate { .. } 3350 | Constant::StringConcatenation { .. } 3351 | Constant::Todo { .. } 3352 | Constant::Invalid { .. } => { 3353 self.constant_expression(arena, Context::Guard, expression) 3354 } 3355 } 3356 } 3357 3358 pub fn source_map_tracker( 3359 &mut self, 3360 arena: &'doc DocumentArena<'a, 'doc>, 3361 start_index: u32, 3362 ) -> Document<'a, 'doc> { 3363 create_cursor_position_observer( 3364 arena, 3365 &self.source_map_builder, 3366 self.line_numbers, 3367 start_index, 3368 ) 3369 } 3370 3371 pub(crate) fn record_constructor( 3372 &mut self, 3373 arena: &'doc DocumentArena<'a, 'doc>, 3374 type_: Arc<Type>, 3375 qualifier: Option<&'a str>, 3376 variant_name: &EcoString, 3377 name: &'a EcoString, 3378 arity: u16, 3379 ) -> Document<'a, 'doc> { 3380 if qualifier.is_none() && type_.is_result_constructor() { 3381 if name == "Ok" { 3382 self.tracker.ok_used = true; 3383 } else if name == "Error" { 3384 self.tracker.error_used = true; 3385 } 3386 } 3387 if type_.is_bool() && name == "True" { 3388 TRUE_LOWERCASE_DOCUMENT 3389 } else if type_.is_bool() { 3390 FALSE_LOWERCASE_DOCUMENT 3391 } else if type_.is_nil() { 3392 UNDEFINED_DOCUMENT 3393 } else if arity == 0 3394 && let Some((package, module, type_name)) = type_.named_type_name_and_package() 3395 { 3396 // If the variant has no fields, return the singleton constant so 3397 // that all values of the variant are the same underlying reference, 3398 // and are faster to compare. 3399 match qualifier { 3400 Some(module) => docvec![ 3401 arena, 3402 DOLLAR_DOCUMENT, 3403 module, 3404 DOT_DOCUMENT, 3405 type_name, 3406 DOLLAR_DOCUMENT, 3407 name, 3408 DOLLAR_CONST_DOCUMENT 3409 ], 3410 None => { 3411 if module != self.module_name { 3412 let alias = if name == variant_name { 3413 None 3414 } else { 3415 Some(eco_format!("{type_name}${name}$const")) 3416 }; 3417 // Since this constant is an implementation detail and not 3418 // present in Gleam code, we need to track it so that we 3419 // import it, as it doesn't appear directly in the `import`s 3420 // in the source code. 3421 _ = self.tracker.variant_constants_used.insert( 3422 TypeVariant { 3423 package, 3424 module, 3425 type_name: type_name.clone(), 3426 name: variant_name.clone(), 3427 }, 3428 alias, 3429 ); 3430 } 3431 docvec![ 3432 arena, 3433 type_name, 3434 DOLLAR_DOCUMENT, 3435 name, 3436 DOLLAR_CONST_DOCUMENT 3437 ] 3438 } 3439 } 3440 } else { 3441 let vars = (0..arity).map(|i| eco_format!("var{i}").to_doc(arena)); 3442 let body = docvec![ 3443 arena, 3444 RETURN_SPACE_DOCUMENT, 3445 construct_record(arena, qualifier, name, vars.clone()), 3446 SEMICOLON_DOCUMENT 3447 ]; 3448 docvec![ 3449 arena, 3450 docvec![ 3451 arena, 3452 wrap_arguments(arena, vars), 3453 SPACE_EQUAL_ARROW_SPACE_OPEN_CURLY_DOCUMENT, 3454 BREAKABLE_SPACE_DOCUMENT, 3455 body 3456 ] 3457 .nest(arena, INDENT) 3458 .append(arena, BREAKABLE_SPACE_DOCUMENT) 3459 .group(arena), 3460 CLOSE_CURLY_DOCUMENT, 3461 ] 3462 } 3463 } 3464} 3465 3466#[derive(Clone, Copy)] 3467enum AssertExpression { 3468 Literal, 3469 Expression, 3470 Unevaluated, 3471} 3472 3473impl AssertExpression { 3474 fn from_expression(expression: &TypedExpr) -> Self { 3475 if expression.is_literal() { 3476 Self::Literal 3477 } else { 3478 Self::Expression 3479 } 3480 } 3481} 3482 3483pub fn int<'a, 'doc>(arena: &'doc DocumentArena<'a, 'doc>, value: &'a str) -> Document<'a, 'doc> { 3484 eco_string_int(arena, value.into()) 3485} 3486 3487pub fn eco_string_int<'a, 'doc>( 3488 arena: &'doc DocumentArena<'a, 'doc>, 3489 value: EcoString, 3490) -> Document<'a, 'doc> { 3491 let mut out = EcoString::with_capacity(value.len()); 3492 3493 if value.starts_with('-') { 3494 out.push('-'); 3495 } else if value.starts_with('+') { 3496 out.push('+'); 3497 }; 3498 let value = value.trim_start_matches(['+', '-'].as_ref()); 3499 3500 let value = if value.starts_with("0x") { 3501 out.push_str("0x"); 3502 value.trim_start_matches("0x") 3503 } else if value.starts_with("0o") { 3504 out.push_str("0o"); 3505 value.trim_start_matches("0o") 3506 } else if value.starts_with("0b") { 3507 out.push_str("0b"); 3508 value.trim_start_matches("0b") 3509 } else { 3510 value 3511 }; 3512 3513 let value = value.trim_start_matches(['0', '_']); 3514 if value.is_empty() { 3515 out.push('0'); 3516 } 3517 3518 out.push_str(value); 3519 3520 out.to_doc(arena) 3521} 3522 3523pub fn float<'a, 'doc>(arena: &'doc DocumentArena<'a, 'doc>, value: &'a str) -> Document<'a, 'doc> { 3524 let mut out = EcoString::with_capacity(value.len()); 3525 3526 if value.starts_with('-') { 3527 out.push('-'); 3528 } else if value.starts_with('+') { 3529 out.push('+'); 3530 }; 3531 let value = value.trim_start_matches(['+', '-'].as_ref()); 3532 3533 let value = value.trim_start_matches(['0', '_']); 3534 if value.starts_with(['.', 'e', 'E']) { 3535 out.push('0'); 3536 } 3537 out.push_str(value); 3538 3539 out.to_doc(arena) 3540} 3541 3542pub fn float_from_value<'a, 'doc>( 3543 arena: &'doc DocumentArena<'a, 'doc>, 3544 value: f64, 3545) -> Document<'a, 'doc> { 3546 if value.is_infinite() { 3547 if value.is_sign_positive() { 3548 INFINITY_DOCUMENT 3549 } else { 3550 MINUS_INFINITY_DOCUMENT 3551 } 3552 } else if value.is_nan() { 3553 // NOTE: this case is probably unnecessary, as this function is only 3554 // invoked with `LiteralFloatValue` values, which cannot be nan. 3555 NAN_DOCUMENT 3556 } else { 3557 value.to_doc(arena) 3558 } 3559} 3560 3561/// The context where the constant expression is used, it might be inside a 3562/// function call, or in the definition of another constant. 3563/// 3564/// Based on the context we might want to annotate pure function calls as 3565/// "@__PURE__". 3566/// 3567#[derive(Debug, Clone, Copy)] 3568pub enum Context { 3569 Constant, 3570 Guard, 3571} 3572 3573#[derive(Debug)] 3574struct BitArraySegmentDetails<'a, 'doc> { 3575 type_: BitArraySegmentType, 3576 size: Document<'a, 'doc>, 3577 /// The size of the bit array segment stored as a BigInt. 3578 /// This has a value when the segment's size is known at compile time. 3579 size_value: Option<BigInt>, 3580 endianness: Endianness, 3581} 3582 3583#[derive(Debug, Clone, Copy)] 3584enum BitArraySegmentType { 3585 BitArray, 3586 Int, 3587 Float, 3588 String(StringEncoding), 3589 UtfCodepoint(StringEncoding), 3590} 3591 3592impl BitArraySegmentType { 3593 fn from_segment<Value>(segment: &BitArraySegment<Value, Arc<Type>>) -> Self { 3594 if segment.type_.is_int() { 3595 BitArraySegmentType::Int 3596 } else if segment.type_.is_float() { 3597 BitArraySegmentType::Float 3598 } else if segment.type_.is_bit_array() { 3599 BitArraySegmentType::BitArray 3600 } else if segment.type_.is_string() { 3601 let encoding = if segment.has_utf16_option() { 3602 StringEncoding::Utf16 3603 } else if segment.has_utf32_option() { 3604 StringEncoding::Utf32 3605 } else { 3606 StringEncoding::Utf8 3607 }; 3608 BitArraySegmentType::String(encoding) 3609 } else if segment.type_.is_utf_codepoint() { 3610 let encoding = if segment.has_utf16_codepoint_option() { 3611 StringEncoding::Utf16 3612 } else if segment.has_utf32_codepoint_option() { 3613 StringEncoding::Utf32 3614 } else { 3615 StringEncoding::Utf8 3616 }; 3617 BitArraySegmentType::UtfCodepoint(encoding) 3618 } else { 3619 panic!( 3620 "Invalid bit array segment type reached code generation: {:?}", 3621 segment.type_ 3622 ); 3623 } 3624 } 3625} 3626 3627pub fn string<'a, 'doc>( 3628 arena: &'doc DocumentArena<'a, 'doc>, 3629 value: &'a str, 3630) -> Document<'a, 'doc> { 3631 if value.contains('\n') { 3632 EcoString::from(value.replace('\n', r"\n")) 3633 .to_doc(arena) 3634 .surround(arena, DOUBLE_QUOTE_DOCUMENT, DOUBLE_QUOTE_DOCUMENT) 3635 } else { 3636 value 3637 .to_doc(arena) 3638 .surround(arena, DOUBLE_QUOTE_DOCUMENT, DOUBLE_QUOTE_DOCUMENT) 3639 } 3640} 3641 3642pub(crate) fn array<'a, 'doc, Elements: IntoIterator<Item = Document<'a, 'doc>>>( 3643 arena: &'doc DocumentArena<'a, 'doc>, 3644 elements: Elements, 3645) -> Document<'a, 'doc> { 3646 let elements = arena.join(elements, COMMA_BREAK_DOCUMENT); 3647 if elements.is_empty() { 3648 // Do not add a trailing comma since that adds an 'undefined' element 3649 OPEN_CLOSE_SQUARE_DOCUMENT 3650 } else { 3651 docvec![ 3652 arena, 3653 OPEN_SQUARE_DOCUMENT, 3654 docvec![arena, EMPTY_BREAK_DOCUMENT, elements].nest(arena, INDENT), 3655 TRAILING_COMMA_BREAK_DOCUMENT, 3656 CLOSE_SQUARE_DOCUMENT 3657 ] 3658 .group(arena) 3659 } 3660} 3661 3662pub(crate) fn list<'a, 'doc, I: IntoIterator<Item = Document<'a, 'doc>>>( 3663 arena: &'doc DocumentArena<'a, 'doc>, 3664 elements: I, 3665) -> Document<'a, 'doc> 3666where 3667 I::IntoIter: DoubleEndedIterator, 3668{ 3669 let array = array(arena, elements); 3670 docvec![ 3671 arena, 3672 TO_LIST_OPEN_PAREN_DOCUMENT, 3673 array, 3674 CLOSE_PAREN_DOCUMENT 3675 ] 3676} 3677 3678fn prepend<'a, 'doc, I: IntoIterator<Item = Document<'a, 'doc>>>( 3679 arena: &'doc DocumentArena<'a, 'doc>, 3680 elements: I, 3681 tail: Document<'a, 'doc>, 3682) -> Document<'a, 'doc> 3683where 3684 I::IntoIter: DoubleEndedIterator + ExactSizeIterator, 3685{ 3686 elements.into_iter().rev().fold(tail, |tail, element| { 3687 let arguments = call_arguments(arena, [element, tail]); 3688 docvec![arena, LIST_PREPEND_DOCUMENT, arguments] 3689 }) 3690} 3691 3692fn call_arguments<'a, 'doc, Elements: IntoIterator<Item = Document<'a, 'doc>>>( 3693 arena: &'doc DocumentArena<'a, 'doc>, 3694 elements: Elements, 3695) -> Document<'a, 'doc> { 3696 let elements = arena.join(elements, COMMA_BREAK_DOCUMENT); 3697 if elements.is_empty() { 3698 return OPEN_CLOSE_PAREN_DOCUMENT; 3699 } 3700 docvec![ 3701 arena, 3702 OPEN_PAREN_DOCUMENT, 3703 docvec![arena, EMPTY_BREAK_DOCUMENT, elements].nest(arena, INDENT), 3704 TRAILING_COMMA_BREAK_DOCUMENT, 3705 CLOSE_PAREN_DOCUMENT 3706 ] 3707 .group(arena) 3708} 3709 3710pub(crate) fn construct_record<'a, 'doc>( 3711 arena: &'doc DocumentArena<'a, 'doc>, 3712 module: Option<&'a str>, 3713 name: &'a str, 3714 arguments: impl IntoIterator<Item = Document<'a, 'doc>>, 3715) -> Document<'a, 'doc> { 3716 let mut any_arguments = false; 3717 let arguments = arena.join( 3718 arguments.into_iter().inspect(|_| { 3719 any_arguments = true; 3720 }), 3721 COMMA_BREAK_DOCUMENT, 3722 ); 3723 let arguments = docvec![arena, EMPTY_BREAK_DOCUMENT, arguments].nest(arena, INDENT); 3724 let name = if let Some(module) = module { 3725 docvec![arena, DOLLAR_DOCUMENT, module, DOT_DOCUMENT, name] 3726 } else { 3727 name.to_doc(arena) 3728 }; 3729 if any_arguments { 3730 docvec![ 3731 arena, 3732 NEW_SPACE_DOCUMENT, 3733 name, 3734 OPEN_PAREN_DOCUMENT, 3735 arguments, 3736 TRAILING_COMMA_BREAK_DOCUMENT, 3737 CLOSE_PAREN_DOCUMENT 3738 ] 3739 .group(arena) 3740 } else { 3741 docvec![arena, NEW_SPACE_DOCUMENT, name, OPEN_CLOSE_PAREN_DOCUMENT] 3742 } 3743} 3744 3745impl TypedExpr { 3746 fn handles_own_return(&self) -> bool { 3747 match self { 3748 TypedExpr::Todo { .. } 3749 | TypedExpr::Call { .. } 3750 | TypedExpr::Case { .. } 3751 | TypedExpr::Panic { .. } 3752 | TypedExpr::Block { .. } 3753 | TypedExpr::Echo { .. } 3754 | TypedExpr::Pipeline { .. } 3755 | TypedExpr::RecordUpdate { .. } => true, 3756 3757 TypedExpr::Int { .. } 3758 | TypedExpr::Float { .. } 3759 | TypedExpr::String { .. } 3760 | TypedExpr::Var { .. } 3761 | TypedExpr::Fn { .. } 3762 | TypedExpr::List { .. } 3763 | TypedExpr::BinOp { .. } 3764 | TypedExpr::RecordAccess { .. } 3765 | TypedExpr::PositionalAccess { .. } 3766 | TypedExpr::ModuleSelect { .. } 3767 | TypedExpr::Tuple { .. } 3768 | TypedExpr::TupleIndex { .. } 3769 | TypedExpr::BitArray { .. } 3770 | TypedExpr::NegateBool { .. } 3771 | TypedExpr::NegateInt { .. } 3772 | TypedExpr::Invalid { .. } => false, 3773 } 3774 } 3775} 3776 3777impl BinOp { 3778 fn is_operator_to_wrap(&self) -> bool { 3779 match self { 3780 BinOp::And 3781 | BinOp::Or 3782 | BinOp::Eq 3783 | BinOp::NotEq 3784 | BinOp::LtInt 3785 | BinOp::LtEqInt 3786 | BinOp::LtFloat 3787 | BinOp::LtEqFloat 3788 | BinOp::GtEqInt 3789 | BinOp::GtInt 3790 | BinOp::GtEqFloat 3791 | BinOp::GtFloat 3792 | BinOp::AddInt 3793 | BinOp::AddFloat 3794 | BinOp::SubInt 3795 | BinOp::SubFloat 3796 | BinOp::MultFloat 3797 | BinOp::DivInt 3798 | BinOp::DivFloat 3799 | BinOp::RemainderInt 3800 | BinOp::Concatenate => true, 3801 BinOp::MultInt => false, 3802 } 3803 } 3804} 3805 3806pub fn is_js_scalar(t: Arc<Type>) -> bool { 3807 t.is_int() || t.is_float() || t.is_bool() || t.is_nil() || t.is_string() 3808} 3809 3810fn expression_requires_semicolon(expression: &TypedExpr) -> bool { 3811 match expression { 3812 TypedExpr::Int { .. } 3813 | TypedExpr::Fn { .. } 3814 | TypedExpr::Var { .. } 3815 | TypedExpr::List { .. } 3816 | TypedExpr::Call { .. } 3817 | TypedExpr::Echo { .. } 3818 | TypedExpr::Float { .. } 3819 | TypedExpr::String { .. } 3820 | TypedExpr::BinOp { .. } 3821 | TypedExpr::Tuple { .. } 3822 | TypedExpr::NegateInt { .. } 3823 | TypedExpr::BitArray { .. } 3824 | TypedExpr::TupleIndex { .. } 3825 | TypedExpr::NegateBool { .. } 3826 | TypedExpr::RecordAccess { .. } 3827 | TypedExpr::PositionalAccess { .. } 3828 | TypedExpr::ModuleSelect { .. } => true, 3829 3830 TypedExpr::Todo { .. } 3831 | TypedExpr::Case { .. } 3832 | TypedExpr::Panic { .. } 3833 | TypedExpr::Pipeline { .. } 3834 | TypedExpr::RecordUpdate { .. } 3835 | TypedExpr::Invalid { .. } 3836 | TypedExpr::Block { .. } => false, 3837 } 3838} 3839 3840/// Wrap a document in an immediately invoked function expression 3841fn immediately_invoked_function_expression_document<'a, 'doc>( 3842 arena: &'doc DocumentArena<'a, 'doc>, 3843 document: Document<'a, 'doc>, 3844) -> Document<'a, 'doc> { 3845 docvec![ 3846 arena, 3847 docvec![ 3848 arena, 3849 OPEN_PAREN_OPEN_CLOSE_PAREN_EQUAL_ARROW_OPEN_CURLY_DOCUMENT, 3850 BREAKABLE_SPACE_DOCUMENT, 3851 document 3852 ] 3853 .nest(arena, INDENT), 3854 BREAKABLE_SPACE_DOCUMENT, 3855 CLOSE_CURLY_CLOSE_PAREN_OPEN_CLOSE_PAREN_DOCUMENT, 3856 ] 3857 .group(arena) 3858} 3859 3860fn u8_slice<'a, 'doc>(arena: &'doc DocumentArena<'a, 'doc>, bytes: &[u8]) -> Document<'a, 'doc> { 3861 arena.join( 3862 bytes.iter().map(|byte| byte.to_doc(arena)), 3863 COMMA_SPACE_DOCUMENT, 3864 ) 3865}