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