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