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