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