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