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