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
92 kB 2533 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 = 1441 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left)); 1442 let right = 1443 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right)); 1444 self.tracker.int_division_used = true; 1445 docvec!["divideInt", wrap_arguments([left, right])] 1446 } 1447 1448 fn remainder_int(&mut self, left: &'a TypedExpr, right: &'a TypedExpr) -> Document<'a> { 1449 let left = 1450 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left)); 1451 let right = 1452 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right)); 1453 self.tracker.int_remainder_used = true; 1454 docvec!["remainderInt", wrap_arguments([left, right])] 1455 } 1456 1457 fn div_float(&mut self, left: &'a TypedExpr, right: &'a TypedExpr) -> Document<'a> { 1458 let left_doc = 1459 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left)); 1460 let right_doc = 1461 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right)); 1462 1463 if right.non_zero_compile_time_number() { 1464 if let TypedExpr::BinOp { .. } = left { 1465 docvec![left_doc.surround("(", ")"), " / ", right_doc] 1466 } else { 1467 docvec![left_doc, " / ", right_doc] 1468 } 1469 } else { 1470 self.tracker.float_division_used = true; 1471 docvec!["divideFloat", wrap_arguments([left_doc, right_doc])] 1472 } 1473 } 1474 1475 fn equal( 1476 &mut self, 1477 left: &'a TypedExpr, 1478 right: &'a TypedExpr, 1479 should_be_equal: bool, 1480 ) -> Document<'a> { 1481 // If it is a simple scalar type then we can use JS' reference identity 1482 if is_js_scalar(left.type_()) { 1483 let left_doc = self 1484 .not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left)); 1485 let right_doc = self 1486 .not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right)); 1487 let operator = if should_be_equal { " === " } else { " !== " }; 1488 return docvec![left_doc, operator, right_doc]; 1489 } 1490 1491 // Other types must be compared using structural equality 1492 let left = 1493 self.not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(left)); 1494 let right = 1495 self.not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(right)); 1496 self.prelude_equal_call(should_be_equal, left, right) 1497 } 1498 1499 fn equal_with_doc_operands( 1500 &mut self, 1501 left: Document<'a>, 1502 right: Document<'a>, 1503 type_: Arc<Type>, 1504 should_be_equal: bool, 1505 ) -> Document<'a> { 1506 // If it is a simple scalar type then we can use JS' reference identity 1507 if is_js_scalar(type_) { 1508 let operator = if should_be_equal { " === " } else { " !== " }; 1509 return docvec![left, operator, right]; 1510 } 1511 1512 // Other types must be compared using structural equality 1513 self.prelude_equal_call(should_be_equal, left, right) 1514 } 1515 1516 pub(super) fn prelude_equal_call( 1517 &mut self, 1518 should_be_equal: bool, 1519 left: Document<'a>, 1520 right: Document<'a>, 1521 ) -> Document<'a> { 1522 // Record that we need to import the prelude's isEqual function into the module 1523 self.tracker.object_equality_used = true; 1524 // Construct the call 1525 let arguments = wrap_arguments([left, right]); 1526 let operator = if should_be_equal { 1527 "isEqual" 1528 } else { 1529 "!isEqual" 1530 }; 1531 docvec![operator, arguments] 1532 } 1533 1534 fn print_bin_op( 1535 &mut self, 1536 left: &'a TypedExpr, 1537 right: &'a TypedExpr, 1538 op: &'a str, 1539 ) -> Document<'a> { 1540 let left = 1541 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left)); 1542 let right = 1543 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right)); 1544 docvec![left, " ", op, " ", right] 1545 } 1546 1547 pub(super) fn bin_op_with_doc_operands( 1548 &mut self, 1549 name: BinOp, 1550 left: Document<'a>, 1551 right: Document<'a>, 1552 type_: &Arc<Type>, 1553 ) -> Document<'a> { 1554 match name { 1555 BinOp::And => docvec![left, " && ", right], 1556 BinOp::Or => docvec![left, " || ", right], 1557 BinOp::LtInt | BinOp::LtFloat => docvec![left, " < ", right], 1558 BinOp::LtEqInt | BinOp::LtEqFloat => docvec![left, " <= ", right], 1559 BinOp::Eq => self.equal_with_doc_operands(left, right, type_.clone(), true), 1560 BinOp::NotEq => self.equal_with_doc_operands(left, right, type_.clone(), false), 1561 BinOp::GtInt | BinOp::GtFloat => docvec![left, " > ", right], 1562 BinOp::GtEqInt | BinOp::GtEqFloat => docvec![left, " >= ", right], 1563 BinOp::Concatenate | BinOp::AddInt | BinOp::AddFloat => { 1564 docvec![left, " + ", right] 1565 } 1566 BinOp::SubInt | BinOp::SubFloat => docvec![left, " - ", right], 1567 BinOp::MultInt | BinOp::MultFloat => docvec![left, " * ", right], 1568 BinOp::RemainderInt => { 1569 self.tracker.int_remainder_used = true; 1570 docvec!["remainderInt", wrap_arguments([left, right])] 1571 } 1572 BinOp::DivInt => { 1573 self.tracker.int_division_used = true; 1574 docvec!["divideInt", wrap_arguments([left, right])] 1575 } 1576 BinOp::DivFloat => { 1577 self.tracker.float_division_used = true; 1578 docvec!["divideFloat", wrap_arguments([left, right])] 1579 } 1580 } 1581 } 1582 1583 fn todo(&mut self, message: Option<&'a TypedExpr>, location: &'a SrcSpan) -> Document<'a> { 1584 let message = match message { 1585 Some(m) => self.not_in_tail_position(None, |this| this.wrap_expression(m)), 1586 None => string("`todo` expression evaluated. This code has not yet been implemented."), 1587 }; 1588 self.throw_error("todo", &message, *location, vec![]) 1589 } 1590 1591 fn panic(&mut self, location: &'a SrcSpan, message: Option<&'a TypedExpr>) -> Document<'a> { 1592 let message = match message { 1593 Some(m) => self.not_in_tail_position(None, |this| this.wrap_expression(m)), 1594 None => string("`panic` expression evaluated."), 1595 }; 1596 self.throw_error("panic", &message, *location, vec![]) 1597 } 1598 1599 pub(crate) fn throw_error<Fields>( 1600 &mut self, 1601 error_name: &'a str, 1602 message: &Document<'a>, 1603 location: SrcSpan, 1604 fields: Fields, 1605 ) -> Document<'a> 1606 where 1607 Fields: IntoIterator<Item = (&'a str, Document<'a>)>, 1608 { 1609 self.tracker.make_error_used = true; 1610 let module = self.module_name.clone().to_doc().surround('"', '"'); 1611 let function = self.function_name.clone().to_doc().surround("\"", "\""); 1612 let line = self.line_numbers.line_number(location.start).to_doc(); 1613 let fields = wrap_object(fields.into_iter().map(|(k, v)| (k.to_doc(), Some(v)))); 1614 1615 docvec![ 1616 "throw makeError", 1617 wrap_arguments([ 1618 string(error_name), 1619 "FILEPATH".to_doc(), 1620 module, 1621 line, 1622 function, 1623 message.clone(), 1624 fields 1625 ]), 1626 ] 1627 } 1628 1629 fn module_select( 1630 &mut self, 1631 module: &'a str, 1632 label: &'a EcoString, 1633 constructor: &'a ModuleValueConstructor, 1634 ) -> Document<'a> { 1635 match constructor { 1636 ModuleValueConstructor::Fn { .. } | ModuleValueConstructor::Constant { .. } => { 1637 docvec!["$", module, ".", maybe_escape_identifier(label)] 1638 } 1639 1640 ModuleValueConstructor::Record { 1641 name, arity, type_, .. 1642 } => record_constructor(type_.clone(), Some(module), name, *arity, self.tracker), 1643 } 1644 } 1645 1646 fn echo( 1647 &mut self, 1648 expression: Document<'a>, 1649 message: Option<&'a TypedExpr>, 1650 location: &'a SrcSpan, 1651 ) -> Document<'a> { 1652 self.tracker.echo_used = true; 1653 1654 let message = match message { 1655 Some(message) => self 1656 .not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(message)), 1657 None => "undefined".to_doc(), 1658 }; 1659 1660 let echo_arguments = call_arguments(vec![ 1661 expression, 1662 message, 1663 self.src_path.clone().to_doc(), 1664 self.line_numbers.line_number(location.start).to_doc(), 1665 ]); 1666 self.wrap_return(docvec!["echo", echo_arguments]) 1667 } 1668 1669 pub(crate) fn constant_expression( 1670 &mut self, 1671 context: Context, 1672 expression: &'a TypedConstant, 1673 ) -> Document<'a> { 1674 match expression { 1675 Constant::Int { value, .. } => int(value), 1676 Constant::Float { value, .. } => float(value), 1677 Constant::String { value, .. } => string(value), 1678 Constant::Tuple { elements, .. } => array( 1679 elements 1680 .iter() 1681 .map(|element| self.constant_expression(context, element)), 1682 ), 1683 1684 Constant::List { elements, .. } => { 1685 self.tracker.list_used = true; 1686 let list = list( 1687 elements 1688 .iter() 1689 .map(|element| self.constant_expression(context, element)), 1690 ); 1691 1692 match context { 1693 Context::Constant => docvec!["/* @__PURE__ */ ", list], 1694 Context::Function => list, 1695 } 1696 } 1697 1698 Constant::Record { type_, name, .. } if type_.is_bool() && name == "True" => { 1699 "true".to_doc() 1700 } 1701 Constant::Record { type_, name, .. } if type_.is_bool() && name == "False" => { 1702 "false".to_doc() 1703 } 1704 Constant::Record { type_, .. } if type_.is_nil() => "undefined".to_doc(), 1705 1706 Constant::Record { 1707 arguments, 1708 module, 1709 name, 1710 tag, 1711 type_, 1712 .. 1713 } => { 1714 if module.is_none() && type_.is_result() { 1715 if tag == "Ok" { 1716 self.tracker.ok_used = true; 1717 } else { 1718 self.tracker.error_used = true; 1719 } 1720 } 1721 1722 // If there's no arguments and the type is a function that takes 1723 // arguments then this is the constructor being referenced, not the 1724 // function being called. 1725 if let Some(arity) = type_.fn_arity() { 1726 if arguments.is_empty() && arity != 0 { 1727 let arity = arity as u16; 1728 return record_constructor(type_.clone(), None, name, arity, self.tracker); 1729 } 1730 } 1731 1732 let field_values = arguments 1733 .iter() 1734 .map(|argument| self.constant_expression(context, &argument.value)) 1735 .collect_vec(); 1736 1737 let constructor = construct_record( 1738 module.as_ref().map(|(module, _)| module.as_str()), 1739 name, 1740 field_values, 1741 ); 1742 match context { 1743 Context::Constant => docvec!["/* @__PURE__ */ ", constructor], 1744 Context::Function => constructor, 1745 } 1746 } 1747 1748 Constant::BitArray { segments, .. } => { 1749 let bit_array = self.constant_bit_array(segments, context); 1750 match context { 1751 Context::Constant => docvec!["/* @__PURE__ */ ", bit_array], 1752 Context::Function => bit_array, 1753 } 1754 } 1755 1756 Constant::Var { name, module, .. } => { 1757 match module { 1758 None => maybe_escape_identifier(name).to_doc(), 1759 Some((module, _)) => { 1760 // JS keywords can be accessed here, but we must escape anyway 1761 // as we escape when exporting such names in the first place, 1762 // and the imported name has to match the exported name. 1763 docvec!["$", module, ".", maybe_escape_identifier(name)] 1764 } 1765 } 1766 } 1767 1768 Constant::StringConcatenation { left, right, .. } => { 1769 let left = self.constant_expression(context, left); 1770 let right = self.constant_expression(context, right); 1771 docvec![left, " + ", right] 1772 } 1773 1774 Constant::Invalid { .. } => { 1775 panic!("invalid constants should not reach code generation") 1776 } 1777 } 1778 } 1779 1780 fn constant_bit_array( 1781 &mut self, 1782 segments: &'a [TypedConstantBitArraySegment], 1783 context: Context, 1784 ) -> Document<'a> { 1785 self.tracker.bit_array_literal_used = true; 1786 let segments_array = array(segments.iter().map(|segment| { 1787 let value = self.constant_expression(Context::Constant, &segment.value); 1788 1789 let details = self.constant_bit_array_segment_details(segment, context); 1790 1791 match details.type_ { 1792 BitArraySegmentType::BitArray => { 1793 if segment.size().is_some() { 1794 self.tracker.bit_array_slice_used = true; 1795 docvec!["bitArraySlice(", value, ", 0, ", details.size, ")"] 1796 } else { 1797 value 1798 } 1799 } 1800 BitArraySegmentType::Int => match (details.size_value, segment.value.as_ref()) { 1801 (Some(size_value), Constant::Int { int_value, .. }) 1802 if size_value <= SAFE_INT_SEGMENT_MAX_SIZE.into() 1803 && (&size_value % BigInt::from(8) == BigInt::ZERO) => 1804 { 1805 let bytes = bit_array_segment_int_value_to_bytes( 1806 int_value.clone(), 1807 size_value, 1808 segment.endianness(), 1809 ); 1810 1811 u8_slice(&bytes) 1812 } 1813 1814 (Some(size_value), _) if size_value == 8.into() => value, 1815 1816 (Some(size_value), _) if size_value <= 0.into() => nil(), 1817 1818 _ => { 1819 self.tracker.sized_integer_segment_used = true; 1820 let size = details.size; 1821 let is_big = bool(segment.endianness().is_big()); 1822 docvec!["sizedInt(", value, ", ", size, ", ", is_big, ")"] 1823 } 1824 }, 1825 BitArraySegmentType::Float => { 1826 self.tracker.float_bit_array_segment_used = true; 1827 let size = details.size; 1828 let is_big = bool(details.endianness.is_big()); 1829 docvec!["sizedFloat(", value, ", ", size, ", ", is_big, ")"] 1830 } 1831 BitArraySegmentType::String(StringEncoding::Utf8) => { 1832 self.tracker.string_bit_array_segment_used = true; 1833 docvec!["stringBits(", value, ")"] 1834 } 1835 BitArraySegmentType::String(StringEncoding::Utf16) => { 1836 self.tracker.string_utf16_bit_array_segment_used = true; 1837 let is_big = bool(details.endianness.is_big()); 1838 docvec!["stringToUtf16(", value, ", ", is_big, ")"] 1839 } 1840 BitArraySegmentType::String(StringEncoding::Utf32) => { 1841 self.tracker.string_utf32_bit_array_segment_used = true; 1842 let is_big = bool(details.endianness.is_big()); 1843 docvec!["stringToUtf32(", value, ", ", is_big, ")"] 1844 } 1845 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf8) => { 1846 self.tracker.codepoint_bit_array_segment_used = true; 1847 docvec!["codepointBits(", value, ")"] 1848 } 1849 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf16) => { 1850 self.tracker.codepoint_utf16_bit_array_segment_used = true; 1851 let is_big = bool(details.endianness.is_big()); 1852 docvec!["codepointToUtf16(", value, ", ", is_big, ")"] 1853 } 1854 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf32) => { 1855 self.tracker.codepoint_utf32_bit_array_segment_used = true; 1856 let is_big = bool(details.endianness.is_big()); 1857 docvec!["codepointToUtf32(", value, ", ", is_big, ")"] 1858 } 1859 } 1860 })); 1861 1862 docvec!["toBitArray(", segments_array, ")"] 1863 } 1864 1865 fn constant_bit_array_segment_details( 1866 &mut self, 1867 segment: &'a TypedConstantBitArraySegment, 1868 context: Context, 1869 ) -> BitArraySegmentDetails<'a> { 1870 let size = segment.size(); 1871 let unit = segment.unit(); 1872 let (size_value, size) = match size { 1873 Some(Constant::Int { int_value, .. }) => { 1874 let size_value = int_value * unit; 1875 let size = eco_format!("{}", size_value).to_doc(); 1876 (Some(size_value), size) 1877 } 1878 1879 Some(size) => { 1880 let mut size = self.constant_expression(context, size); 1881 if unit != 1 { 1882 size = size.group().append(" * ".to_doc().append(unit.to_doc())); 1883 } 1884 1885 (None, size) 1886 } 1887 1888 None => { 1889 let size_value: usize = if segment.type_.is_int() { 8 } else { 64 }; 1890 (Some(BigInt::from(size_value)), docvec![size_value]) 1891 } 1892 }; 1893 1894 let type_ = BitArraySegmentType::from_segment(segment); 1895 1896 BitArraySegmentDetails { 1897 type_, 1898 size, 1899 size_value, 1900 endianness: segment.endianness(), 1901 } 1902 } 1903 1904 pub(crate) fn guard(&mut self, guard: &'a TypedClauseGuard) -> Document<'a> { 1905 match guard { 1906 ClauseGuard::Equals { left, right, .. } if is_js_scalar(left.type_()) => { 1907 let left = self.wrapped_guard(left); 1908 let right = self.wrapped_guard(right); 1909 docvec![left, " === ", right] 1910 } 1911 1912 ClauseGuard::NotEquals { left, right, .. } if is_js_scalar(left.type_()) => { 1913 let left = self.wrapped_guard(left); 1914 let right = self.wrapped_guard(right); 1915 docvec![left, " !== ", right] 1916 } 1917 1918 ClauseGuard::Equals { left, right, .. } => { 1919 let left = self.guard(left); 1920 let right = self.guard(right); 1921 self.prelude_equal_call(true, left, right) 1922 } 1923 1924 ClauseGuard::NotEquals { left, right, .. } => { 1925 let left = self.guard(left); 1926 let right = self.guard(right); 1927 self.prelude_equal_call(false, left, right) 1928 } 1929 1930 ClauseGuard::GtFloat { left, right, .. } | ClauseGuard::GtInt { left, right, .. } => { 1931 let left = self.wrapped_guard(left); 1932 let right = self.wrapped_guard(right); 1933 docvec![left, " > ", right] 1934 } 1935 1936 ClauseGuard::GtEqFloat { left, right, .. } 1937 | ClauseGuard::GtEqInt { left, right, .. } => { 1938 let left = self.wrapped_guard(left); 1939 let right = self.wrapped_guard(right); 1940 docvec![left, " >= ", right] 1941 } 1942 1943 ClauseGuard::LtFloat { left, right, .. } | ClauseGuard::LtInt { left, right, .. } => { 1944 let left = self.wrapped_guard(left); 1945 let right = self.wrapped_guard(right); 1946 docvec![left, " < ", right] 1947 } 1948 1949 ClauseGuard::LtEqFloat { left, right, .. } 1950 | ClauseGuard::LtEqInt { left, right, .. } => { 1951 let left = self.wrapped_guard(left); 1952 let right = self.wrapped_guard(right); 1953 docvec![left, " <= ", right] 1954 } 1955 1956 ClauseGuard::AddFloat { left, right, .. } | ClauseGuard::AddInt { left, right, .. } => { 1957 let left = self.wrapped_guard(left); 1958 let right = self.wrapped_guard(right); 1959 docvec![left, " + ", right] 1960 } 1961 1962 ClauseGuard::SubFloat { left, right, .. } | ClauseGuard::SubInt { left, right, .. } => { 1963 let left = self.wrapped_guard(left); 1964 let right = self.wrapped_guard(right); 1965 docvec![left, " - ", right] 1966 } 1967 1968 ClauseGuard::MultFloat { left, right, .. } 1969 | ClauseGuard::MultInt { left, right, .. } => { 1970 let left = self.wrapped_guard(left); 1971 let right = self.wrapped_guard(right); 1972 docvec![left, " * ", right] 1973 } 1974 1975 ClauseGuard::DivFloat { left, right, .. } => { 1976 let left = self.wrapped_guard(left); 1977 let right = self.wrapped_guard(right); 1978 self.tracker.float_division_used = true; 1979 docvec!["divideFloat", wrap_arguments([left, right])] 1980 } 1981 1982 ClauseGuard::DivInt { left, right, .. } => { 1983 let left = self.wrapped_guard(left); 1984 let right = self.wrapped_guard(right); 1985 self.tracker.int_division_used = true; 1986 docvec!["divideInt", wrap_arguments([left, right])] 1987 } 1988 1989 ClauseGuard::RemainderInt { left, right, .. } => { 1990 let left = self.wrapped_guard(left); 1991 let right = self.wrapped_guard(right); 1992 self.tracker.int_remainder_used = true; 1993 docvec!["remainderInt", wrap_arguments([left, right])] 1994 } 1995 1996 ClauseGuard::Or { left, right, .. } => { 1997 let left = self.wrapped_guard(left); 1998 let right = self.wrapped_guard(right); 1999 docvec![left, " || ", right] 2000 } 2001 2002 ClauseGuard::And { left, right, .. } => { 2003 let left = self.wrapped_guard(left); 2004 let right = self.wrapped_guard(right); 2005 docvec![left, " && ", right] 2006 } 2007 2008 ClauseGuard::Var { name, .. } => self.local_var(name).to_doc(), 2009 2010 ClauseGuard::TupleIndex { tuple, index, .. } => { 2011 docvec![self.guard(tuple,), "[", index, "]"] 2012 } 2013 2014 ClauseGuard::FieldAccess { 2015 label, container, .. 2016 } => docvec![self.guard(container), ".", maybe_escape_property(label)], 2017 2018 ClauseGuard::ModuleSelect { 2019 module_alias, 2020 label, 2021 .. 2022 } => docvec!["$", module_alias, ".", label], 2023 2024 ClauseGuard::Not { expression, .. } => docvec!["!", self.guard(expression,)], 2025 2026 ClauseGuard::Constant(constant) => self.guard_constant_expression(constant), 2027 } 2028 } 2029 2030 fn wrapped_guard(&mut self, guard: &'a TypedClauseGuard) -> Document<'a> { 2031 match guard { 2032 ClauseGuard::Var { .. } 2033 | ClauseGuard::TupleIndex { .. } 2034 | ClauseGuard::Constant(_) 2035 | ClauseGuard::Not { .. } 2036 | ClauseGuard::FieldAccess { .. } => self.guard(guard), 2037 2038 ClauseGuard::Equals { .. } 2039 | ClauseGuard::NotEquals { .. } 2040 | ClauseGuard::GtInt { .. } 2041 | ClauseGuard::GtEqInt { .. } 2042 | ClauseGuard::LtInt { .. } 2043 | ClauseGuard::LtEqInt { .. } 2044 | ClauseGuard::GtFloat { .. } 2045 | ClauseGuard::GtEqFloat { .. } 2046 | ClauseGuard::LtFloat { .. } 2047 | ClauseGuard::LtEqFloat { .. } 2048 | ClauseGuard::AddInt { .. } 2049 | ClauseGuard::AddFloat { .. } 2050 | ClauseGuard::SubInt { .. } 2051 | ClauseGuard::SubFloat { .. } 2052 | ClauseGuard::MultInt { .. } 2053 | ClauseGuard::MultFloat { .. } 2054 | ClauseGuard::DivInt { .. } 2055 | ClauseGuard::DivFloat { .. } 2056 | ClauseGuard::RemainderInt { .. } 2057 | ClauseGuard::Or { .. } 2058 | ClauseGuard::And { .. } 2059 | ClauseGuard::ModuleSelect { .. } => docvec!["(", self.guard(guard,), ")"], 2060 } 2061 } 2062 2063 fn guard_constant_expression(&mut self, expression: &'a TypedConstant) -> Document<'a> { 2064 match expression { 2065 Constant::Tuple { elements, .. } => array( 2066 elements 2067 .iter() 2068 .map(|element| self.guard_constant_expression(element)), 2069 ), 2070 2071 Constant::List { elements, .. } => { 2072 self.tracker.list_used = true; 2073 list( 2074 elements 2075 .iter() 2076 .map(|element| self.guard_constant_expression(element)), 2077 ) 2078 } 2079 Constant::Record { type_, name, .. } if type_.is_bool() && name == "True" => { 2080 "true".to_doc() 2081 } 2082 Constant::Record { type_, name, .. } if type_.is_bool() && name == "False" => { 2083 "false".to_doc() 2084 } 2085 Constant::Record { type_, .. } if type_.is_nil() => "undefined".to_doc(), 2086 2087 Constant::Record { 2088 arguments, 2089 module, 2090 name, 2091 tag, 2092 type_, 2093 .. 2094 } => { 2095 if module.is_none() && type_.is_result() { 2096 if tag == "Ok" { 2097 self.tracker.ok_used = true; 2098 } else { 2099 self.tracker.error_used = true; 2100 } 2101 } 2102 2103 // If there's no arguments and the type is a function that takes 2104 // arguments then this is the constructor being referenced, not the 2105 // function being called. 2106 if let Some(arity) = type_.fn_arity() { 2107 if arguments.is_empty() && arity != 0 { 2108 let arity = arity as u16; 2109 return record_constructor(type_.clone(), None, name, arity, self.tracker); 2110 } 2111 } 2112 2113 let field_values = arguments 2114 .iter() 2115 .map(|argument| self.guard_constant_expression(&argument.value)) 2116 .collect_vec(); 2117 construct_record( 2118 module.as_ref().map(|(module, _)| module.as_str()), 2119 name, 2120 field_values, 2121 ) 2122 } 2123 2124 Constant::BitArray { segments, .. } => { 2125 self.constant_bit_array(segments, Context::Function) 2126 } 2127 2128 Constant::Var { name, .. } => self.local_var(name).to_doc(), 2129 2130 expression => self.constant_expression(Context::Function, expression), 2131 } 2132 } 2133} 2134 2135#[derive(Clone, Copy)] 2136enum AssertExpression { 2137 Literal, 2138 Expression, 2139 Unevaluated, 2140} 2141 2142impl AssertExpression { 2143 fn from_expression(expression: &TypedExpr) -> Self { 2144 if expression.is_literal() { 2145 Self::Literal 2146 } else { 2147 Self::Expression 2148 } 2149 } 2150} 2151 2152pub fn int(value: &str) -> Document<'_> { 2153 eco_string_int(value.into()) 2154} 2155 2156pub fn eco_string_int<'a>(value: EcoString) -> Document<'a> { 2157 let mut out = EcoString::with_capacity(value.len()); 2158 2159 if value.starts_with('-') { 2160 out.push('-'); 2161 } else if value.starts_with('+') { 2162 out.push('+'); 2163 }; 2164 let value = value.trim_start_matches(['+', '-'].as_ref()); 2165 2166 let value = if value.starts_with("0x") { 2167 out.push_str("0x"); 2168 value.trim_start_matches("0x") 2169 } else if value.starts_with("0o") { 2170 out.push_str("0o"); 2171 value.trim_start_matches("0o") 2172 } else if value.starts_with("0b") { 2173 out.push_str("0b"); 2174 value.trim_start_matches("0b") 2175 } else { 2176 value 2177 }; 2178 2179 let value = value.trim_start_matches('0'); 2180 if value.is_empty() { 2181 out.push('0'); 2182 } 2183 2184 // If the number starts with a `0` then an underscore, the `0` will be stripped, 2185 // leaving the number to look something like `_1_2_3`, which is not valid syntax. 2186 // Therefore, we strip the `_` to avoid this case. 2187 let value = value.trim_start_matches('_'); 2188 2189 out.push_str(value); 2190 2191 out.to_doc() 2192} 2193 2194pub fn float(value: &str) -> Document<'_> { 2195 let mut out = EcoString::with_capacity(value.len()); 2196 2197 if value.starts_with('-') { 2198 out.push('-'); 2199 } else if value.starts_with('+') { 2200 out.push('+'); 2201 }; 2202 let value = value.trim_start_matches(['+', '-'].as_ref()); 2203 2204 let value = value.trim_start_matches('0'); 2205 if value.starts_with(['.', 'e', 'E']) { 2206 out.push('0'); 2207 } 2208 out.push_str(value); 2209 2210 out.to_doc() 2211} 2212 2213/// The context where the constant expression is used, it might be inside a 2214/// function call, or in the definition of another constant. 2215/// 2216/// Based on the context we might want to annotate pure function calls as 2217/// "@__PURE__". 2218/// 2219#[derive(Debug, Clone, Copy)] 2220pub enum Context { 2221 Constant, 2222 Function, 2223} 2224 2225#[derive(Debug)] 2226struct BitArraySegmentDetails<'a> { 2227 type_: BitArraySegmentType, 2228 size: Document<'a>, 2229 /// The size of the bit array segment stored as a BigInt. 2230 /// This has a value when the segment's size is known at compile time. 2231 size_value: Option<BigInt>, 2232 endianness: Endianness, 2233} 2234 2235#[derive(Debug, Clone, Copy)] 2236enum BitArraySegmentType { 2237 BitArray, 2238 Int, 2239 Float, 2240 String(StringEncoding), 2241 UtfCodepoint(StringEncoding), 2242} 2243 2244impl BitArraySegmentType { 2245 fn from_segment<Value>(segment: &BitArraySegment<Value, Arc<Type>>) -> Self { 2246 if segment.type_.is_int() { 2247 BitArraySegmentType::Int 2248 } else if segment.type_.is_float() { 2249 BitArraySegmentType::Float 2250 } else if segment.type_.is_bit_array() { 2251 BitArraySegmentType::BitArray 2252 } else if segment.type_.is_string() { 2253 let encoding = if segment.has_utf16_option() { 2254 StringEncoding::Utf16 2255 } else if segment.has_utf32_option() { 2256 StringEncoding::Utf32 2257 } else { 2258 StringEncoding::Utf8 2259 }; 2260 BitArraySegmentType::String(encoding) 2261 } else if segment.type_.is_utf_codepoint() { 2262 let encoding = if segment.has_utf16_codepoint_option() { 2263 StringEncoding::Utf16 2264 } else if segment.has_utf32_codepoint_option() { 2265 StringEncoding::Utf32 2266 } else { 2267 StringEncoding::Utf8 2268 }; 2269 BitArraySegmentType::UtfCodepoint(encoding) 2270 } else { 2271 panic!( 2272 "Invalid bit array segment type reached code generation: {:?}", 2273 segment.type_ 2274 ); 2275 } 2276 } 2277} 2278 2279pub fn string(value: &str) -> Document<'_> { 2280 if value.contains('\n') { 2281 EcoString::from(value.replace('\n', r"\n")) 2282 .to_doc() 2283 .surround("\"", "\"") 2284 } else { 2285 value.to_doc().surround("\"", "\"") 2286 } 2287} 2288 2289pub(crate) fn array<'a, Elements: IntoIterator<Item = Document<'a>>>( 2290 elements: Elements, 2291) -> Document<'a> { 2292 let elements = Itertools::intersperse(elements.into_iter(), break_(",", ", ")).collect_vec(); 2293 if elements.is_empty() { 2294 // Do not add a trailing comma since that adds an 'undefined' element 2295 "[]".to_doc() 2296 } else { 2297 docvec![ 2298 "[", 2299 docvec![break_("", ""), elements].nest(INDENT), 2300 break_(",", ""), 2301 "]" 2302 ] 2303 .group() 2304 } 2305} 2306 2307pub(crate) fn list<'a, I: IntoIterator<Item = Document<'a>>>(elements: I) -> Document<'a> 2308where 2309 I::IntoIter: DoubleEndedIterator + ExactSizeIterator, 2310{ 2311 let array = array(elements); 2312 docvec!["toList(", array, ")"] 2313} 2314 2315fn prepend<'a, I: IntoIterator<Item = Document<'a>>>( 2316 elements: I, 2317 tail: Document<'a>, 2318) -> Document<'a> 2319where 2320 I::IntoIter: DoubleEndedIterator + ExactSizeIterator, 2321{ 2322 elements.into_iter().rev().fold(tail, |tail, element| { 2323 let arguments = call_arguments([element, tail]); 2324 docvec!["listPrepend", arguments] 2325 }) 2326} 2327 2328fn call_arguments<'a, Elements: IntoIterator<Item = Document<'a>>>( 2329 elements: Elements, 2330) -> Document<'a> { 2331 let elements = Itertools::intersperse(elements.into_iter(), break_(",", ", ")) 2332 .collect_vec() 2333 .to_doc(); 2334 if elements.is_empty() { 2335 return "()".to_doc(); 2336 } 2337 docvec![ 2338 "(", 2339 docvec![break_("", ""), elements].nest(INDENT), 2340 break_(",", ""), 2341 ")" 2342 ] 2343 .group() 2344} 2345 2346pub(crate) fn construct_record<'a>( 2347 module: Option<&'a str>, 2348 name: &'a str, 2349 arguments: impl IntoIterator<Item = Document<'a>>, 2350) -> Document<'a> { 2351 let mut any_arguments = false; 2352 let arguments = join( 2353 arguments.into_iter().inspect(|_| { 2354 any_arguments = true; 2355 }), 2356 break_(",", ", "), 2357 ); 2358 let arguments = docvec![break_("", ""), arguments].nest(INDENT); 2359 let name = if let Some(module) = module { 2360 docvec!["$", module, ".", name] 2361 } else { 2362 name.to_doc() 2363 }; 2364 if any_arguments { 2365 docvec!["new ", name, "(", arguments, break_(",", ""), ")"].group() 2366 } else { 2367 docvec!["new ", name, "()"] 2368 } 2369} 2370 2371impl TypedExpr { 2372 fn handles_own_return(&self) -> bool { 2373 match self { 2374 TypedExpr::Todo { .. } 2375 | TypedExpr::Call { .. } 2376 | TypedExpr::Case { .. } 2377 | TypedExpr::Panic { .. } 2378 | TypedExpr::Block { .. } 2379 | TypedExpr::Echo { .. } 2380 | TypedExpr::Pipeline { .. } 2381 | TypedExpr::RecordUpdate { .. } => true, 2382 2383 TypedExpr::Int { .. } 2384 | TypedExpr::Float { .. } 2385 | TypedExpr::String { .. } 2386 | TypedExpr::Var { .. } 2387 | TypedExpr::Fn { .. } 2388 | TypedExpr::List { .. } 2389 | TypedExpr::BinOp { .. } 2390 | TypedExpr::RecordAccess { .. } 2391 | TypedExpr::ModuleSelect { .. } 2392 | TypedExpr::Tuple { .. } 2393 | TypedExpr::TupleIndex { .. } 2394 | TypedExpr::BitArray { .. } 2395 | TypedExpr::NegateBool { .. } 2396 | TypedExpr::NegateInt { .. } 2397 | TypedExpr::Invalid { .. } => false, 2398 } 2399 } 2400} 2401 2402impl BinOp { 2403 fn is_operator_to_wrap(&self) -> bool { 2404 match self { 2405 BinOp::And 2406 | BinOp::Or 2407 | BinOp::Eq 2408 | BinOp::NotEq 2409 | BinOp::LtInt 2410 | BinOp::LtEqInt 2411 | BinOp::LtFloat 2412 | BinOp::LtEqFloat 2413 | BinOp::GtEqInt 2414 | BinOp::GtInt 2415 | BinOp::GtEqFloat 2416 | BinOp::GtFloat 2417 | BinOp::AddInt 2418 | BinOp::AddFloat 2419 | BinOp::SubInt 2420 | BinOp::SubFloat 2421 | BinOp::MultFloat 2422 | BinOp::DivInt 2423 | BinOp::DivFloat 2424 | BinOp::RemainderInt 2425 | BinOp::Concatenate => true, 2426 BinOp::MultInt => false, 2427 } 2428 } 2429} 2430 2431pub fn is_js_scalar(t: Arc<Type>) -> bool { 2432 t.is_int() || t.is_float() || t.is_bool() || t.is_nil() || t.is_string() 2433} 2434 2435fn requires_semicolon(statement: &TypedStatement) -> bool { 2436 match statement { 2437 Statement::Expression( 2438 TypedExpr::Int { .. } 2439 | TypedExpr::Fn { .. } 2440 | TypedExpr::Var { .. } 2441 | TypedExpr::List { .. } 2442 | TypedExpr::Call { .. } 2443 | TypedExpr::Echo { .. } 2444 | TypedExpr::Float { .. } 2445 | TypedExpr::String { .. } 2446 | TypedExpr::BinOp { .. } 2447 | TypedExpr::Tuple { .. } 2448 | TypedExpr::NegateInt { .. } 2449 | TypedExpr::BitArray { .. } 2450 | TypedExpr::TupleIndex { .. } 2451 | TypedExpr::NegateBool { .. } 2452 | TypedExpr::RecordAccess { .. } 2453 | TypedExpr::ModuleSelect { .. } 2454 | TypedExpr::Block { .. }, 2455 ) => true, 2456 2457 Statement::Expression( 2458 TypedExpr::Todo { .. } 2459 | TypedExpr::Case { .. } 2460 | TypedExpr::Panic { .. } 2461 | TypedExpr::Pipeline { .. } 2462 | TypedExpr::RecordUpdate { .. } 2463 | TypedExpr::Invalid { .. }, 2464 ) => false, 2465 2466 Statement::Assignment(_) => false, 2467 Statement::Use(_) => false, 2468 Statement::Assert(_) => false, 2469 } 2470} 2471 2472/// Wrap a document in an immediately invoked function expression 2473fn immediately_invoked_function_expression_document(document: Document<'_>) -> Document<'_> { 2474 docvec![ 2475 docvec!["(() => {", break_("", " "), document].nest(INDENT), 2476 break_("", " "), 2477 "})()", 2478 ] 2479 .group() 2480} 2481 2482pub(crate) fn record_constructor<'a>( 2483 type_: Arc<Type>, 2484 qualifier: Option<&'a str>, 2485 name: &'a str, 2486 arity: u16, 2487 tracker: &mut UsageTracker, 2488) -> Document<'a> { 2489 if qualifier.is_none() && type_.is_result_constructor() { 2490 if name == "Ok" { 2491 tracker.ok_used = true; 2492 } else if name == "Error" { 2493 tracker.error_used = true; 2494 } 2495 } 2496 if type_.is_bool() && name == "True" { 2497 "true".to_doc() 2498 } else if type_.is_bool() { 2499 "false".to_doc() 2500 } else if type_.is_nil() { 2501 "undefined".to_doc() 2502 } else if arity == 0 { 2503 match qualifier { 2504 Some(module) => docvec!["new $", module, ".", name, "()"], 2505 None => docvec!["new ", name, "()"], 2506 } 2507 } else { 2508 let vars = (0..arity).map(|i| eco_format!("var{i}").to_doc()); 2509 let body = docvec![ 2510 "return ", 2511 construct_record(qualifier, name, vars.clone()), 2512 ";" 2513 ]; 2514 docvec![ 2515 docvec![wrap_arguments(vars), " => {", break_("", " "), body] 2516 .nest(INDENT) 2517 .append(break_("", " ")) 2518 .group(), 2519 "}", 2520 ] 2521 } 2522} 2523 2524fn u8_slice<'a>(bytes: &[u8]) -> Document<'a> { 2525 let s: EcoString = bytes 2526 .iter() 2527 .map(u8::to_string) 2528 .collect::<Vec<_>>() 2529 .join(", ") 2530 .into(); 2531 2532 docvec![s] 2533}