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