this repo has no description
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 { value, .. } => float(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 // Other types must be compared using structural equality
1540 let left =
1541 self.not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(left));
1542 let right =
1543 self.not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(right));
1544 self.prelude_equal_call(should_be_equal, left, right)
1545 }
1546
1547 fn equal_with_doc_operands(
1548 &mut self,
1549 left: Document<'a>,
1550 right: Document<'a>,
1551 type_: Arc<Type>,
1552 should_be_equal: bool,
1553 ) -> Document<'a> {
1554 // If it is a simple scalar type then we can use JS' reference identity
1555 if is_js_scalar(type_) {
1556 let operator = if should_be_equal { " === " } else { " !== " };
1557 return docvec![left, operator, right];
1558 }
1559
1560 // Other types must be compared using structural equality
1561 self.prelude_equal_call(should_be_equal, left, right)
1562 }
1563
1564 pub(super) fn prelude_equal_call(
1565 &mut self,
1566 should_be_equal: bool,
1567 left: Document<'a>,
1568 right: Document<'a>,
1569 ) -> Document<'a> {
1570 // Record that we need to import the prelude's isEqual function into the module
1571 self.tracker.object_equality_used = true;
1572 // Construct the call
1573 let arguments = wrap_arguments([left, right]);
1574 let operator = if should_be_equal {
1575 "isEqual"
1576 } else {
1577 "!isEqual"
1578 };
1579 docvec![operator, arguments]
1580 }
1581
1582 fn print_bin_op(
1583 &mut self,
1584 left: &'a TypedExpr,
1585 right: &'a TypedExpr,
1586 op: &'a str,
1587 ) -> Document<'a> {
1588 let left =
1589 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left));
1590 let right =
1591 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right));
1592 docvec![left, " ", op, " ", right]
1593 }
1594
1595 pub(super) fn bin_op_with_doc_operands(
1596 &mut self,
1597 name: BinOp,
1598 left: Document<'a>,
1599 right: Document<'a>,
1600 type_: &Arc<Type>,
1601 ) -> Document<'a> {
1602 match name {
1603 BinOp::And => docvec![left, " && ", right],
1604 BinOp::Or => docvec![left, " || ", right],
1605 BinOp::LtInt | BinOp::LtFloat => docvec![left, " < ", right],
1606 BinOp::LtEqInt | BinOp::LtEqFloat => docvec![left, " <= ", right],
1607 BinOp::Eq => self.equal_with_doc_operands(left, right, type_.clone(), true),
1608 BinOp::NotEq => self.equal_with_doc_operands(left, right, type_.clone(), false),
1609 BinOp::GtInt | BinOp::GtFloat => docvec![left, " > ", right],
1610 BinOp::GtEqInt | BinOp::GtEqFloat => docvec![left, " >= ", right],
1611 BinOp::Concatenate | BinOp::AddInt | BinOp::AddFloat => {
1612 docvec![left, " + ", right]
1613 }
1614 BinOp::SubInt | BinOp::SubFloat => docvec![left, " - ", right],
1615 BinOp::MultInt | BinOp::MultFloat => docvec![left, " * ", right],
1616 BinOp::RemainderInt => {
1617 self.tracker.int_remainder_used = true;
1618 docvec!["remainderInt", wrap_arguments([left, right])]
1619 }
1620 BinOp::DivInt => {
1621 self.tracker.int_division_used = true;
1622 docvec!["divideInt", wrap_arguments([left, right])]
1623 }
1624 BinOp::DivFloat => {
1625 self.tracker.float_division_used = true;
1626 docvec!["divideFloat", wrap_arguments([left, right])]
1627 }
1628 }
1629 }
1630
1631 fn todo(&mut self, message: Option<&'a TypedExpr>, location: &'a SrcSpan) -> Document<'a> {
1632 let message = match message {
1633 Some(m) => self.not_in_tail_position(None, |this| this.wrap_expression(m)),
1634 None => string("`todo` expression evaluated. This code has not yet been implemented."),
1635 };
1636 self.throw_error("todo", &message, *location, vec![])
1637 }
1638
1639 fn panic(&mut self, location: &'a SrcSpan, message: Option<&'a TypedExpr>) -> Document<'a> {
1640 let message = match message {
1641 Some(m) => self.not_in_tail_position(None, |this| this.wrap_expression(m)),
1642 None => string("`panic` expression evaluated."),
1643 };
1644 self.throw_error("panic", &message, *location, vec![])
1645 }
1646
1647 pub(crate) fn throw_error<Fields>(
1648 &mut self,
1649 error_name: &'a str,
1650 message: &Document<'a>,
1651 location: SrcSpan,
1652 fields: Fields,
1653 ) -> Document<'a>
1654 where
1655 Fields: IntoIterator<Item = (&'a str, Document<'a>)>,
1656 {
1657 self.tracker.make_error_used = true;
1658 let module = self.module_name.clone().to_doc().surround('"', '"');
1659 let function = self.function_name.clone().to_doc().surround("\"", "\"");
1660 let line = self.line_numbers.line_number(location.start).to_doc();
1661 let fields = wrap_object(fields.into_iter().map(|(k, v)| (k.to_doc(), Some(v))));
1662
1663 docvec![
1664 "throw makeError",
1665 wrap_arguments([
1666 string(error_name),
1667 "FILEPATH".to_doc(),
1668 module,
1669 line,
1670 function,
1671 message.clone(),
1672 fields
1673 ]),
1674 ]
1675 }
1676
1677 fn module_select(
1678 &mut self,
1679 module: &'a str,
1680 label: &'a EcoString,
1681 constructor: &'a ModuleValueConstructor,
1682 ) -> Document<'a> {
1683 match constructor {
1684 ModuleValueConstructor::Fn { .. } | ModuleValueConstructor::Constant { .. } => {
1685 docvec!["$", module, ".", maybe_escape_identifier(label)]
1686 }
1687
1688 ModuleValueConstructor::Record {
1689 name, arity, type_, ..
1690 } => record_constructor(type_.clone(), Some(module), name, *arity, self.tracker),
1691 }
1692 }
1693
1694 fn echo(
1695 &mut self,
1696 expression: Document<'a>,
1697 message: Option<&'a TypedExpr>,
1698 location: &'a SrcSpan,
1699 ) -> Document<'a> {
1700 self.tracker.echo_used = true;
1701
1702 let message = match message {
1703 Some(message) => self
1704 .not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(message)),
1705 None => "undefined".to_doc(),
1706 };
1707
1708 let echo_arguments = call_arguments(vec![
1709 expression,
1710 message,
1711 self.src_path.clone().to_doc(),
1712 self.line_numbers.line_number(location.start).to_doc(),
1713 ]);
1714 self.wrap_return(docvec!["echo", echo_arguments])
1715 }
1716
1717 pub(crate) fn constant_expression(
1718 &mut self,
1719 context: Context,
1720 expression: &'a TypedConstant,
1721 ) -> Document<'a> {
1722 match expression {
1723 Constant::Int { value, .. } => int(value),
1724 Constant::Float { value, .. } => float(value),
1725 Constant::String { value, .. } => string(value),
1726 Constant::Tuple { elements, .. } => array(
1727 elements
1728 .iter()
1729 .map(|element| self.constant_expression(context, element)),
1730 ),
1731
1732 Constant::List { elements, .. } => {
1733 self.tracker.list_used = true;
1734 let list = list(
1735 elements
1736 .iter()
1737 .map(|element| self.constant_expression(context, element)),
1738 );
1739
1740 match context {
1741 Context::Constant => docvec!["/* @__PURE__ */ ", list],
1742 Context::Function => list,
1743 }
1744 }
1745
1746 Constant::Record { type_, name, .. } if type_.is_bool() && name == "True" => {
1747 "true".to_doc()
1748 }
1749 Constant::Record { type_, name, .. } if type_.is_bool() && name == "False" => {
1750 "false".to_doc()
1751 }
1752 Constant::Record { type_, .. } if type_.is_nil() => "undefined".to_doc(),
1753
1754 Constant::Record {
1755 arguments,
1756 module,
1757 name,
1758 tag,
1759 type_,
1760 ..
1761 } => {
1762 if module.is_none() && type_.is_result() {
1763 if tag == "Ok" {
1764 self.tracker.ok_used = true;
1765 } else {
1766 self.tracker.error_used = true;
1767 }
1768 }
1769
1770 // If there's no arguments and the type is a function that takes
1771 // arguments then this is the constructor being referenced, not the
1772 // function being called.
1773 if let Some(arity) = type_.fn_arity()
1774 && arguments.is_empty()
1775 && arity != 0
1776 {
1777 let arity = arity as u16;
1778 return record_constructor(type_.clone(), None, name, arity, self.tracker);
1779 }
1780
1781 let field_values = arguments
1782 .iter()
1783 .map(|argument| self.constant_expression(context, &argument.value))
1784 .collect_vec();
1785
1786 let constructor = construct_record(
1787 module.as_ref().map(|(module, _)| module.as_str()),
1788 name,
1789 field_values,
1790 );
1791 match context {
1792 Context::Constant => docvec!["/* @__PURE__ */ ", constructor],
1793 Context::Function => constructor,
1794 }
1795 }
1796
1797 Constant::BitArray { segments, .. } => {
1798 let bit_array = self.constant_bit_array(segments, context);
1799 match context {
1800 Context::Constant => docvec!["/* @__PURE__ */ ", bit_array],
1801 Context::Function => bit_array,
1802 }
1803 }
1804
1805 Constant::Var { name, module, .. } => {
1806 match module {
1807 None => maybe_escape_identifier(name).to_doc(),
1808 Some((module, _)) => {
1809 // JS keywords can be accessed here, but we must escape anyway
1810 // as we escape when exporting such names in the first place,
1811 // and the imported name has to match the exported name.
1812 docvec!["$", module, ".", maybe_escape_identifier(name)]
1813 }
1814 }
1815 }
1816
1817 Constant::StringConcatenation { left, right, .. } => {
1818 let left = self.constant_expression(context, left);
1819 let right = self.constant_expression(context, right);
1820 docvec![left, " + ", right]
1821 }
1822
1823 Constant::Invalid { .. } => {
1824 panic!("invalid constants should not reach code generation")
1825 }
1826 }
1827 }
1828
1829 fn constant_bit_array(
1830 &mut self,
1831 segments: &'a [TypedConstantBitArraySegment],
1832 context: Context,
1833 ) -> Document<'a> {
1834 self.tracker.bit_array_literal_used = true;
1835 let segments_array = array(segments.iter().map(|segment| {
1836 let value = self.constant_expression(Context::Constant, &segment.value);
1837
1838 let details = self.constant_bit_array_segment_details(segment, context);
1839
1840 match details.type_ {
1841 BitArraySegmentType::BitArray => {
1842 if segment.size().is_some() {
1843 self.tracker.bit_array_slice_used = true;
1844 docvec!["bitArraySlice(", value, ", 0, ", details.size, ")"]
1845 } else {
1846 value
1847 }
1848 }
1849 BitArraySegmentType::Int => match (details.size_value, segment.value.as_ref()) {
1850 (Some(size_value), Constant::Int { int_value, .. })
1851 if size_value <= SAFE_INT_SEGMENT_MAX_SIZE.into()
1852 && (&size_value % BigInt::from(8) == BigInt::ZERO) =>
1853 {
1854 let bytes = bit_array_segment_int_value_to_bytes(
1855 int_value.clone(),
1856 size_value,
1857 segment.endianness(),
1858 );
1859
1860 u8_slice(&bytes)
1861 }
1862
1863 (Some(size_value), _) if size_value == 8.into() => value,
1864
1865 (Some(size_value), _) if size_value <= 0.into() => nil(),
1866
1867 _ => {
1868 self.tracker.sized_integer_segment_used = true;
1869 let size = details.size;
1870 let is_big = bool(segment.endianness().is_big());
1871 docvec!["sizedInt(", value, ", ", size, ", ", is_big, ")"]
1872 }
1873 },
1874 BitArraySegmentType::Float => {
1875 self.tracker.float_bit_array_segment_used = true;
1876 let size = details.size;
1877 let is_big = bool(details.endianness.is_big());
1878 docvec!["sizedFloat(", value, ", ", size, ", ", is_big, ")"]
1879 }
1880 BitArraySegmentType::String(StringEncoding::Utf8) => {
1881 self.tracker.string_bit_array_segment_used = true;
1882 docvec!["stringBits(", value, ")"]
1883 }
1884 BitArraySegmentType::String(StringEncoding::Utf16) => {
1885 self.tracker.string_utf16_bit_array_segment_used = true;
1886 let is_big = bool(details.endianness.is_big());
1887 docvec!["stringToUtf16(", value, ", ", is_big, ")"]
1888 }
1889 BitArraySegmentType::String(StringEncoding::Utf32) => {
1890 self.tracker.string_utf32_bit_array_segment_used = true;
1891 let is_big = bool(details.endianness.is_big());
1892 docvec!["stringToUtf32(", value, ", ", is_big, ")"]
1893 }
1894 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf8) => {
1895 self.tracker.codepoint_bit_array_segment_used = true;
1896 docvec!["codepointBits(", value, ")"]
1897 }
1898 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf16) => {
1899 self.tracker.codepoint_utf16_bit_array_segment_used = true;
1900 let is_big = bool(details.endianness.is_big());
1901 docvec!["codepointToUtf16(", value, ", ", is_big, ")"]
1902 }
1903 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf32) => {
1904 self.tracker.codepoint_utf32_bit_array_segment_used = true;
1905 let is_big = bool(details.endianness.is_big());
1906 docvec!["codepointToUtf32(", value, ", ", is_big, ")"]
1907 }
1908 }
1909 }));
1910
1911 docvec!["toBitArray(", segments_array, ")"]
1912 }
1913
1914 fn constant_bit_array_segment_details(
1915 &mut self,
1916 segment: &'a TypedConstantBitArraySegment,
1917 context: Context,
1918 ) -> BitArraySegmentDetails<'a> {
1919 let size = segment.size();
1920 let unit = segment.unit();
1921 let (size_value, size) = match size {
1922 Some(Constant::Int { int_value, .. }) => {
1923 let size_value = int_value * unit;
1924 let size = eco_format!("{}", size_value).to_doc();
1925 (Some(size_value), size)
1926 }
1927
1928 Some(size) => {
1929 let mut size = self.constant_expression(context, size);
1930 if unit != 1 {
1931 size = size.group().append(" * ".to_doc().append(unit.to_doc()));
1932 }
1933
1934 (None, size)
1935 }
1936
1937 None => {
1938 let size_value: usize = if segment.type_.is_int() { 8 } else { 64 };
1939 (Some(BigInt::from(size_value)), docvec![size_value])
1940 }
1941 };
1942
1943 let type_ = BitArraySegmentType::from_segment(segment);
1944
1945 BitArraySegmentDetails {
1946 type_,
1947 size,
1948 size_value,
1949 endianness: segment.endianness(),
1950 }
1951 }
1952
1953 pub(crate) fn guard(&mut self, guard: &'a TypedClauseGuard) -> Document<'a> {
1954 match guard {
1955 ClauseGuard::Block { value, .. } => self.guard(value).surround("(", ")"),
1956
1957 ClauseGuard::Equals { left, right, .. } if is_js_scalar(left.type_()) => {
1958 let left = self.wrapped_guard(left);
1959 let right = self.wrapped_guard(right);
1960 docvec![left, " === ", right]
1961 }
1962
1963 ClauseGuard::NotEquals { left, right, .. } if is_js_scalar(left.type_()) => {
1964 let left = self.wrapped_guard(left);
1965 let right = self.wrapped_guard(right);
1966 docvec![left, " !== ", right]
1967 }
1968
1969 ClauseGuard::Equals { left, right, .. } => {
1970 let left = self.guard(left);
1971 let right = self.guard(right);
1972 self.prelude_equal_call(true, left, right)
1973 }
1974
1975 ClauseGuard::NotEquals { left, right, .. } => {
1976 let left = self.guard(left);
1977 let right = self.guard(right);
1978 self.prelude_equal_call(false, left, right)
1979 }
1980
1981 ClauseGuard::GtFloat { left, right, .. } | ClauseGuard::GtInt { left, right, .. } => {
1982 let left = self.wrapped_guard(left);
1983 let right = self.wrapped_guard(right);
1984 docvec![left, " > ", right]
1985 }
1986
1987 ClauseGuard::GtEqFloat { left, right, .. }
1988 | ClauseGuard::GtEqInt { left, right, .. } => {
1989 let left = self.wrapped_guard(left);
1990 let right = self.wrapped_guard(right);
1991 docvec![left, " >= ", right]
1992 }
1993
1994 ClauseGuard::LtFloat { left, right, .. } | ClauseGuard::LtInt { left, right, .. } => {
1995 let left = self.wrapped_guard(left);
1996 let right = self.wrapped_guard(right);
1997 docvec![left, " < ", right]
1998 }
1999
2000 ClauseGuard::LtEqFloat { left, right, .. }
2001 | ClauseGuard::LtEqInt { left, right, .. } => {
2002 let left = self.wrapped_guard(left);
2003 let right = self.wrapped_guard(right);
2004 docvec![left, " <= ", right]
2005 }
2006
2007 ClauseGuard::AddFloat { left, right, .. } | ClauseGuard::AddInt { left, right, .. } => {
2008 let left = self.wrapped_guard(left);
2009 let right = self.wrapped_guard(right);
2010 docvec![left, " + ", right]
2011 }
2012
2013 ClauseGuard::SubFloat { left, right, .. } | ClauseGuard::SubInt { left, right, .. } => {
2014 let left = self.wrapped_guard(left);
2015 let right = self.wrapped_guard(right);
2016 docvec![left, " - ", right]
2017 }
2018
2019 ClauseGuard::MultFloat { left, right, .. }
2020 | ClauseGuard::MultInt { left, right, .. } => {
2021 let left = self.wrapped_guard(left);
2022 let right = self.wrapped_guard(right);
2023 docvec![left, " * ", right]
2024 }
2025
2026 ClauseGuard::DivFloat { left, right, .. } => {
2027 let left = self.wrapped_guard(left);
2028 let right = self.wrapped_guard(right);
2029 self.tracker.float_division_used = true;
2030 docvec!["divideFloat", wrap_arguments([left, right])]
2031 }
2032
2033 ClauseGuard::DivInt { left, right, .. } => {
2034 let left = self.wrapped_guard(left);
2035 let right = self.wrapped_guard(right);
2036 self.tracker.int_division_used = true;
2037 docvec!["divideInt", wrap_arguments([left, right])]
2038 }
2039
2040 ClauseGuard::RemainderInt { left, right, .. } => {
2041 let left = self.wrapped_guard(left);
2042 let right = self.wrapped_guard(right);
2043 self.tracker.int_remainder_used = true;
2044 docvec!["remainderInt", wrap_arguments([left, right])]
2045 }
2046
2047 ClauseGuard::Or { left, right, .. } => {
2048 let left = self.wrapped_guard(left);
2049 let right = self.wrapped_guard(right);
2050 docvec![left, " || ", right]
2051 }
2052
2053 ClauseGuard::And { left, right, .. } => {
2054 let left = self.wrapped_guard(left);
2055 let right = self.wrapped_guard(right);
2056 docvec![left, " && ", right]
2057 }
2058
2059 ClauseGuard::Var { name, .. } => self.local_var(name).to_doc(),
2060
2061 ClauseGuard::TupleIndex { tuple, index, .. } => {
2062 docvec![self.guard(tuple,), "[", index, "]"]
2063 }
2064
2065 ClauseGuard::FieldAccess {
2066 label, container, ..
2067 } => docvec![self.guard(container), ".", maybe_escape_property(label)],
2068
2069 ClauseGuard::ModuleSelect {
2070 module_alias,
2071 label,
2072 ..
2073 } => docvec!["$", module_alias, ".", label],
2074
2075 ClauseGuard::Not { expression, .. } => docvec!["!", self.guard(expression,)],
2076
2077 ClauseGuard::Constant(constant) => self.guard_constant_expression(constant),
2078 }
2079 }
2080
2081 fn wrapped_guard(&mut self, guard: &'a TypedClauseGuard) -> Document<'a> {
2082 match guard {
2083 ClauseGuard::Var { .. }
2084 | ClauseGuard::TupleIndex { .. }
2085 | ClauseGuard::Constant(_)
2086 | ClauseGuard::Not { .. }
2087 | ClauseGuard::FieldAccess { .. }
2088 | ClauseGuard::Block { .. } => self.guard(guard),
2089
2090 ClauseGuard::Equals { .. }
2091 | ClauseGuard::NotEquals { .. }
2092 | ClauseGuard::GtInt { .. }
2093 | ClauseGuard::GtEqInt { .. }
2094 | ClauseGuard::LtInt { .. }
2095 | ClauseGuard::LtEqInt { .. }
2096 | ClauseGuard::GtFloat { .. }
2097 | ClauseGuard::GtEqFloat { .. }
2098 | ClauseGuard::LtFloat { .. }
2099 | ClauseGuard::LtEqFloat { .. }
2100 | ClauseGuard::AddInt { .. }
2101 | ClauseGuard::AddFloat { .. }
2102 | ClauseGuard::SubInt { .. }
2103 | ClauseGuard::SubFloat { .. }
2104 | ClauseGuard::MultInt { .. }
2105 | ClauseGuard::MultFloat { .. }
2106 | ClauseGuard::DivInt { .. }
2107 | ClauseGuard::DivFloat { .. }
2108 | ClauseGuard::RemainderInt { .. }
2109 | ClauseGuard::Or { .. }
2110 | ClauseGuard::And { .. }
2111 | ClauseGuard::ModuleSelect { .. } => docvec!["(", self.guard(guard), ")"],
2112 }
2113 }
2114
2115 fn guard_constant_expression(&mut self, expression: &'a TypedConstant) -> Document<'a> {
2116 match expression {
2117 Constant::Tuple { elements, .. } => array(
2118 elements
2119 .iter()
2120 .map(|element| self.guard_constant_expression(element)),
2121 ),
2122
2123 Constant::List { elements, .. } => {
2124 self.tracker.list_used = true;
2125 list(
2126 elements
2127 .iter()
2128 .map(|element| self.guard_constant_expression(element)),
2129 )
2130 }
2131 Constant::Record { type_, name, .. } if type_.is_bool() && name == "True" => {
2132 "true".to_doc()
2133 }
2134 Constant::Record { type_, name, .. } if type_.is_bool() && name == "False" => {
2135 "false".to_doc()
2136 }
2137 Constant::Record { type_, .. } if type_.is_nil() => "undefined".to_doc(),
2138
2139 Constant::Record {
2140 arguments,
2141 module,
2142 name,
2143 tag,
2144 type_,
2145 ..
2146 } => {
2147 if module.is_none() && type_.is_result() {
2148 if tag == "Ok" {
2149 self.tracker.ok_used = true;
2150 } else {
2151 self.tracker.error_used = true;
2152 }
2153 }
2154
2155 // If there's no arguments and the type is a function that takes
2156 // arguments then this is the constructor being referenced, not the
2157 // function being called.
2158 if let Some(arity) = type_.fn_arity()
2159 && arguments.is_empty()
2160 && arity != 0
2161 {
2162 let arity = arity as u16;
2163 return record_constructor(type_.clone(), None, name, arity, self.tracker);
2164 }
2165
2166 let field_values = arguments
2167 .iter()
2168 .map(|argument| self.guard_constant_expression(&argument.value))
2169 .collect_vec();
2170 construct_record(
2171 module.as_ref().map(|(module, _)| module.as_str()),
2172 name,
2173 field_values,
2174 )
2175 }
2176
2177 Constant::BitArray { segments, .. } => {
2178 self.constant_bit_array(segments, Context::Function)
2179 }
2180
2181 Constant::Var { name, .. } => self.local_var(name).to_doc(),
2182
2183 expression => self.constant_expression(Context::Function, expression),
2184 }
2185 }
2186}
2187
2188#[derive(Clone, Copy)]
2189enum AssertExpression {
2190 Literal,
2191 Expression,
2192 Unevaluated,
2193}
2194
2195impl AssertExpression {
2196 fn from_expression(expression: &TypedExpr) -> Self {
2197 if expression.is_literal() {
2198 Self::Literal
2199 } else {
2200 Self::Expression
2201 }
2202 }
2203}
2204
2205pub fn int(value: &str) -> Document<'_> {
2206 eco_string_int(value.into())
2207}
2208
2209pub fn eco_string_int<'a>(value: EcoString) -> Document<'a> {
2210 let mut out = EcoString::with_capacity(value.len());
2211
2212 if value.starts_with('-') {
2213 out.push('-');
2214 } else if value.starts_with('+') {
2215 out.push('+');
2216 };
2217 let value = value.trim_start_matches(['+', '-'].as_ref());
2218
2219 let value = if value.starts_with("0x") {
2220 out.push_str("0x");
2221 value.trim_start_matches("0x")
2222 } else if value.starts_with("0o") {
2223 out.push_str("0o");
2224 value.trim_start_matches("0o")
2225 } else if value.starts_with("0b") {
2226 out.push_str("0b");
2227 value.trim_start_matches("0b")
2228 } else {
2229 value
2230 };
2231
2232 let value = value.trim_start_matches('0');
2233 if value.is_empty() {
2234 out.push('0');
2235 }
2236
2237 // If the number starts with a `0` then an underscore, the `0` will be stripped,
2238 // leaving the number to look something like `_1_2_3`, which is not valid syntax.
2239 // Therefore, we strip the `_` to avoid this case.
2240 let value = value.trim_start_matches('_');
2241
2242 out.push_str(value);
2243
2244 out.to_doc()
2245}
2246
2247pub fn float(value: &str) -> Document<'_> {
2248 let mut out = EcoString::with_capacity(value.len());
2249
2250 if value.starts_with('-') {
2251 out.push('-');
2252 } else if value.starts_with('+') {
2253 out.push('+');
2254 };
2255 let value = value.trim_start_matches(['+', '-'].as_ref());
2256
2257 let value = value.trim_start_matches('0');
2258 if value.starts_with(['.', 'e', 'E']) {
2259 out.push('0');
2260 }
2261 out.push_str(value);
2262
2263 out.to_doc()
2264}
2265
2266/// The context where the constant expression is used, it might be inside a
2267/// function call, or in the definition of another constant.
2268///
2269/// Based on the context we might want to annotate pure function calls as
2270/// "@__PURE__".
2271///
2272#[derive(Debug, Clone, Copy)]
2273pub enum Context {
2274 Constant,
2275 Function,
2276}
2277
2278#[derive(Debug)]
2279struct BitArraySegmentDetails<'a> {
2280 type_: BitArraySegmentType,
2281 size: Document<'a>,
2282 /// The size of the bit array segment stored as a BigInt.
2283 /// This has a value when the segment's size is known at compile time.
2284 size_value: Option<BigInt>,
2285 endianness: Endianness,
2286}
2287
2288#[derive(Debug, Clone, Copy)]
2289enum BitArraySegmentType {
2290 BitArray,
2291 Int,
2292 Float,
2293 String(StringEncoding),
2294 UtfCodepoint(StringEncoding),
2295}
2296
2297impl BitArraySegmentType {
2298 fn from_segment<Value>(segment: &BitArraySegment<Value, Arc<Type>>) -> Self {
2299 if segment.type_.is_int() {
2300 BitArraySegmentType::Int
2301 } else if segment.type_.is_float() {
2302 BitArraySegmentType::Float
2303 } else if segment.type_.is_bit_array() {
2304 BitArraySegmentType::BitArray
2305 } else if segment.type_.is_string() {
2306 let encoding = if segment.has_utf16_option() {
2307 StringEncoding::Utf16
2308 } else if segment.has_utf32_option() {
2309 StringEncoding::Utf32
2310 } else {
2311 StringEncoding::Utf8
2312 };
2313 BitArraySegmentType::String(encoding)
2314 } else if segment.type_.is_utf_codepoint() {
2315 let encoding = if segment.has_utf16_codepoint_option() {
2316 StringEncoding::Utf16
2317 } else if segment.has_utf32_codepoint_option() {
2318 StringEncoding::Utf32
2319 } else {
2320 StringEncoding::Utf8
2321 };
2322 BitArraySegmentType::UtfCodepoint(encoding)
2323 } else {
2324 panic!(
2325 "Invalid bit array segment type reached code generation: {:?}",
2326 segment.type_
2327 );
2328 }
2329 }
2330}
2331
2332pub fn string(value: &str) -> Document<'_> {
2333 if value.contains('\n') {
2334 EcoString::from(value.replace('\n', r"\n"))
2335 .to_doc()
2336 .surround("\"", "\"")
2337 } else {
2338 value.to_doc().surround("\"", "\"")
2339 }
2340}
2341
2342pub(crate) fn array<'a, Elements: IntoIterator<Item = Document<'a>>>(
2343 elements: Elements,
2344) -> Document<'a> {
2345 let elements = Itertools::intersperse(elements.into_iter(), break_(",", ", ")).collect_vec();
2346 if elements.is_empty() {
2347 // Do not add a trailing comma since that adds an 'undefined' element
2348 "[]".to_doc()
2349 } else {
2350 docvec![
2351 "[",
2352 docvec![break_("", ""), elements].nest(INDENT),
2353 break_(",", ""),
2354 "]"
2355 ]
2356 .group()
2357 }
2358}
2359
2360pub(crate) fn list<'a, I: IntoIterator<Item = Document<'a>>>(elements: I) -> Document<'a>
2361where
2362 I::IntoIter: DoubleEndedIterator + ExactSizeIterator,
2363{
2364 let array = array(elements);
2365 docvec!["toList(", array, ")"]
2366}
2367
2368fn prepend<'a, I: IntoIterator<Item = Document<'a>>>(
2369 elements: I,
2370 tail: Document<'a>,
2371) -> Document<'a>
2372where
2373 I::IntoIter: DoubleEndedIterator + ExactSizeIterator,
2374{
2375 elements.into_iter().rev().fold(tail, |tail, element| {
2376 let arguments = call_arguments([element, tail]);
2377 docvec!["listPrepend", arguments]
2378 })
2379}
2380
2381fn call_arguments<'a, Elements: IntoIterator<Item = Document<'a>>>(
2382 elements: Elements,
2383) -> Document<'a> {
2384 let elements = Itertools::intersperse(elements.into_iter(), break_(",", ", "))
2385 .collect_vec()
2386 .to_doc();
2387 if elements.is_empty() {
2388 return "()".to_doc();
2389 }
2390 docvec![
2391 "(",
2392 docvec![break_("", ""), elements].nest(INDENT),
2393 break_(",", ""),
2394 ")"
2395 ]
2396 .group()
2397}
2398
2399pub(crate) fn construct_record<'a>(
2400 module: Option<&'a str>,
2401 name: &'a str,
2402 arguments: impl IntoIterator<Item = Document<'a>>,
2403) -> Document<'a> {
2404 let mut any_arguments = false;
2405 let arguments = join(
2406 arguments.into_iter().inspect(|_| {
2407 any_arguments = true;
2408 }),
2409 break_(",", ", "),
2410 );
2411 let arguments = docvec![break_("", ""), arguments].nest(INDENT);
2412 let name = if let Some(module) = module {
2413 docvec!["$", module, ".", name]
2414 } else {
2415 name.to_doc()
2416 };
2417 if any_arguments {
2418 docvec!["new ", name, "(", arguments, break_(",", ""), ")"].group()
2419 } else {
2420 docvec!["new ", name, "()"]
2421 }
2422}
2423
2424impl TypedExpr {
2425 fn handles_own_return(&self) -> bool {
2426 match self {
2427 TypedExpr::Todo { .. }
2428 | TypedExpr::Call { .. }
2429 | TypedExpr::Case { .. }
2430 | TypedExpr::Panic { .. }
2431 | TypedExpr::Block { .. }
2432 | TypedExpr::Echo { .. }
2433 | TypedExpr::Pipeline { .. }
2434 | TypedExpr::RecordUpdate { .. } => true,
2435
2436 TypedExpr::Int { .. }
2437 | TypedExpr::Float { .. }
2438 | TypedExpr::String { .. }
2439 | TypedExpr::Var { .. }
2440 | TypedExpr::Fn { .. }
2441 | TypedExpr::List { .. }
2442 | TypedExpr::BinOp { .. }
2443 | TypedExpr::RecordAccess { .. }
2444 | TypedExpr::ModuleSelect { .. }
2445 | TypedExpr::Tuple { .. }
2446 | TypedExpr::TupleIndex { .. }
2447 | TypedExpr::BitArray { .. }
2448 | TypedExpr::NegateBool { .. }
2449 | TypedExpr::NegateInt { .. }
2450 | TypedExpr::Invalid { .. } => false,
2451 }
2452 }
2453}
2454
2455impl BinOp {
2456 fn is_operator_to_wrap(&self) -> bool {
2457 match self {
2458 BinOp::And
2459 | BinOp::Or
2460 | BinOp::Eq
2461 | BinOp::NotEq
2462 | BinOp::LtInt
2463 | BinOp::LtEqInt
2464 | BinOp::LtFloat
2465 | BinOp::LtEqFloat
2466 | BinOp::GtEqInt
2467 | BinOp::GtInt
2468 | BinOp::GtEqFloat
2469 | BinOp::GtFloat
2470 | BinOp::AddInt
2471 | BinOp::AddFloat
2472 | BinOp::SubInt
2473 | BinOp::SubFloat
2474 | BinOp::MultFloat
2475 | BinOp::DivInt
2476 | BinOp::DivFloat
2477 | BinOp::RemainderInt
2478 | BinOp::Concatenate => true,
2479 BinOp::MultInt => false,
2480 }
2481 }
2482}
2483
2484pub fn is_js_scalar(t: Arc<Type>) -> bool {
2485 t.is_int() || t.is_float() || t.is_bool() || t.is_nil() || t.is_string()
2486}
2487
2488fn requires_semicolon(statement: &TypedStatement) -> bool {
2489 match statement {
2490 Statement::Expression(
2491 TypedExpr::Int { .. }
2492 | TypedExpr::Fn { .. }
2493 | TypedExpr::Var { .. }
2494 | TypedExpr::List { .. }
2495 | TypedExpr::Call { .. }
2496 | TypedExpr::Echo { .. }
2497 | TypedExpr::Float { .. }
2498 | TypedExpr::String { .. }
2499 | TypedExpr::BinOp { .. }
2500 | TypedExpr::Tuple { .. }
2501 | TypedExpr::NegateInt { .. }
2502 | TypedExpr::BitArray { .. }
2503 | TypedExpr::TupleIndex { .. }
2504 | TypedExpr::NegateBool { .. }
2505 | TypedExpr::RecordAccess { .. }
2506 | TypedExpr::ModuleSelect { .. }
2507 | TypedExpr::Block { .. },
2508 ) => true,
2509
2510 Statement::Expression(
2511 TypedExpr::Todo { .. }
2512 | TypedExpr::Case { .. }
2513 | TypedExpr::Panic { .. }
2514 | TypedExpr::Pipeline { .. }
2515 | TypedExpr::RecordUpdate { .. }
2516 | TypedExpr::Invalid { .. },
2517 ) => false,
2518
2519 Statement::Assignment(_) => false,
2520 Statement::Use(_) => false,
2521 Statement::Assert(_) => false,
2522 }
2523}
2524
2525/// Wrap a document in an immediately invoked function expression
2526fn immediately_invoked_function_expression_document(document: Document<'_>) -> Document<'_> {
2527 docvec![
2528 docvec!["(() => {", break_("", " "), document].nest(INDENT),
2529 break_("", " "),
2530 "})()",
2531 ]
2532 .group()
2533}
2534
2535pub(crate) fn record_constructor<'a>(
2536 type_: Arc<Type>,
2537 qualifier: Option<&'a str>,
2538 name: &'a str,
2539 arity: u16,
2540 tracker: &mut UsageTracker,
2541) -> Document<'a> {
2542 if qualifier.is_none() && type_.is_result_constructor() {
2543 if name == "Ok" {
2544 tracker.ok_used = true;
2545 } else if name == "Error" {
2546 tracker.error_used = true;
2547 }
2548 }
2549 if type_.is_bool() && name == "True" {
2550 "true".to_doc()
2551 } else if type_.is_bool() {
2552 "false".to_doc()
2553 } else if type_.is_nil() {
2554 "undefined".to_doc()
2555 } else if arity == 0 {
2556 match qualifier {
2557 Some(module) => docvec!["new $", module, ".", name, "()"],
2558 None => docvec!["new ", name, "()"],
2559 }
2560 } else {
2561 let vars = (0..arity).map(|i| eco_format!("var{i}").to_doc());
2562 let body = docvec![
2563 "return ",
2564 construct_record(qualifier, name, vars.clone()),
2565 ";"
2566 ];
2567 docvec![
2568 docvec![wrap_arguments(vars), " => {", break_("", " "), body]
2569 .nest(INDENT)
2570 .append(break_("", " "))
2571 .group(),
2572 "}",
2573 ]
2574 }
2575}
2576
2577fn u8_slice<'a>(bytes: &[u8]) -> Document<'a> {
2578 let s: EcoString = bytes
2579 .iter()
2580 .map(u8::to_string)
2581 .collect::<Vec<_>>()
2582 .join(", ")
2583 .into();
2584
2585 docvec![s]
2586}