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