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