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