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