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