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