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