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