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