Fork of daniellemaywood.uk/gleam — Wasm codegen work
141 kB
3865 lines
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2021 The Gleam contributors
3
4use num_bigint::BigInt;
5use vec1::Vec1;
6
7use super::{decision::ASSIGNMENT_VAR, *};
8use crate::{
9 ast::*,
10 exhaustiveness::StringEncoding,
11 line_numbers::LineNumbers,
12 type_::{
13 ModuleValueConstructor, Type, TypedCallArg, ValueConstructor, ValueConstructorVariant,
14 },
15};
16use pretty_arena::*;
17use std::sync::Arc;
18
19#[derive(Debug, Clone)]
20pub enum Position {
21 /// We are compiling the last expression in a function, meaning that it should
22 /// use `return` to return the value it produces from the function.
23 Tail,
24 /// We are inside a function, but the value of this expression isn't being
25 /// used, so we don't need to do anything with the returned value.
26 Statement,
27 /// The value of this expression needs to be used inside another expression,
28 /// so we need to use the value that is returned by this expression.
29 Expression(Ordering),
30 /// We are compiling an expression inside a block, meaning we must assign
31 /// to the `_block` variable at the end of the scope, because blocks are not
32 /// expressions in JS.
33 /// Since JS doesn't have variable shadowing, we must store the name of the
34 /// variable being used, which will include the incrementing counter.
35 /// For example, `block$2`
36 Assign(EcoString),
37}
38
39impl Position {
40 /// Returns `true` if the position is [`Tail`].
41 ///
42 /// [`Tail`]: Position::Tail
43 #[must_use]
44 pub fn is_tail(&self) -> bool {
45 matches!(self, Self::Tail)
46 }
47
48 #[must_use]
49 pub fn ordering(&self) -> Ordering {
50 match self {
51 Self::Expression(ordering) => *ordering,
52 Self::Tail | Self::Assign(_) | Self::Statement => Ordering::Loose,
53 }
54 }
55}
56
57#[derive(Debug, Clone, Copy)]
58/// Determines whether we can lift blocks into statement level instead of using
59/// immediately invoked function expressions. Consider the following piece of code:
60///
61/// ```gleam
62/// some_function(function_with_side_effect(), {
63/// let a = 10
64/// other_function_with_side_effects(a)
65/// })
66/// ```
67/// Here, if we lift the block that is the second argument of the function, we
68/// would end up running `other_function_with_side_effects` before
69/// `function_with_side_effects`. This would be invalid, as code in Gleam should be
70/// evaluated left-to-right, top-to-bottom. In this case, the ordering would be
71/// `Strict`, indicating that we cannot lift the block.
72///
73/// However, in this example:
74///
75/// ```gleam
76/// let value = !{
77/// let value = False
78/// some_function_with_side_effect()
79/// value
80/// }
81/// ```
82/// The only expression is the block, meaning it can be safely lifted without
83/// changing the evaluation order of the program. So the ordering is `Loose`.
84///
85pub enum Ordering {
86 Strict,
87 Loose,
88}
89
90/// Tracking where the current function is a module function or an anonymous function.
91#[derive(Debug)]
92enum CurrentFunction {
93 /// The current function is a module function
94 ///
95 /// ```gleam
96 /// pub fn main() -> Nil {
97 /// // we are here
98 /// }
99 /// ```
100 Module,
101
102 /// The current function is a module function, but one of its arguments shadows
103 /// the reference to itself so it cannot recurse.
104 ///
105 /// ```gleam
106 /// pub fn main(main: fn() -> Nil) -> Nil {
107 /// // we are here
108 /// }
109 /// ```
110 ModuleWithShadowingArgument,
111
112 /// The current function is an anonymous function
113 ///
114 /// ```gleam
115 /// pub fn main() -> Nil {
116 /// fn() {
117 /// // we are here
118 /// }
119 /// }
120 /// ```
121 Anonymous,
122}
123
124impl CurrentFunction {
125 #[inline]
126 fn can_recurse(&self) -> bool {
127 match self {
128 CurrentFunction::Module => true,
129 CurrentFunction::ModuleWithShadowingArgument => false,
130 CurrentFunction::Anonymous => false,
131 }
132 }
133}
134
135/// The variables in scope while generating the code for a function.
136///
137/// User variables are tracked in a map keyed by name, so a shadowed name can be
138/// given a unique JavaScript identifier. The compiler synthesised variables
139/// (the `$` case subject, `_pipe`, `_block`, ...) are held in their own fields
140/// instead. They can't be referenced by user code, and a branch that generates
141/// directly into its enclosing scope needs to restore the user variables while
142/// leaving the synthesised counters advanced, so that later code doesn't
143/// redeclare one of them.
144#[derive(Debug, Clone, Default)]
145pub(crate) struct Scope {
146 user_variables: im::HashMap<EcoString, usize>,
147 /// The highest suffix handed out for each user variable still declared in
148 /// the current JS scope, kept across a directly matching `case` branch so a
149 /// variable that leaked out of it is not redeclared by a later `let`.
150 high_water: im::HashMap<EcoString, usize>,
151 assignment: Option<usize>,
152 pipe: Option<usize>,
153 block: Option<usize>,
154 use_assignment: Option<usize>,
155 record_update: Option<usize>,
156 capture: Option<usize>,
157 assert_subject: Option<usize>,
158 assert_fail: Option<usize>,
159}
160
161impl Scope {
162 fn new(user_variables: im::HashMap<EcoString, usize>) -> Self {
163 Self {
164 user_variables,
165 ..Self::default()
166 }
167 }
168
169 /// The counter for a variable name, whether user or synthesised.
170 fn counter(&self, name: &str) -> Option<usize> {
171 match name {
172 ASSIGNMENT_VAR => self.assignment,
173 PIPE_VARIABLE => self.pipe,
174 BLOCK_VARIABLE => self.block,
175 USE_ASSIGNMENT_VARIABLE => self.use_assignment,
176 RECORD_UPDATE_VARIABLE => self.record_update,
177 CAPTURE_VARIABLE => self.capture,
178 ASSERT_SUBJECT_VARIABLE => self.assert_subject,
179 ASSERT_FAIL_VARIABLE => self.assert_fail,
180 _ => self.user_variables.get(name).copied(),
181 }
182 }
183
184 /// Set the counter for a variable name, whether user or synthesised.
185 pub(crate) fn set_counter(&mut self, name: &EcoString, value: usize) {
186 match name.as_str() {
187 ASSIGNMENT_VAR => self.assignment = Some(value),
188 PIPE_VARIABLE => self.pipe = Some(value),
189 BLOCK_VARIABLE => self.block = Some(value),
190 USE_ASSIGNMENT_VARIABLE => self.use_assignment = Some(value),
191 RECORD_UPDATE_VARIABLE => self.record_update = Some(value),
192 CAPTURE_VARIABLE => self.capture = Some(value),
193 ASSERT_SUBJECT_VARIABLE => self.assert_subject = Some(value),
194 ASSERT_FAIL_VARIABLE => self.assert_fail = Some(value),
195 _ => {
196 let _ = self.user_variables.insert(name.clone(), value);
197 }
198 }
199 }
200
201 /// Advance the counter for a name to its next suffix, skipping any suffix
202 /// already handed out for it in the current scope so a name that leaked out
203 /// of a directly matching branch can't be redeclared.
204 fn advance_counter(&mut self, name: &EcoString) {
205 let in_scope = self.counter(name).map_or(0, |i| i + 1);
206 let high_water = self.high_water.get(name).map_or(0, |i| i + 1);
207 let next = in_scope.max(high_water);
208 self.set_counter(name, next);
209 // Only user variables leak out of a directly matching branch; the
210 // synthesised counters survive the restore on their own.
211 if self.user_variables.contains_key(name) {
212 let _ = self.high_water.insert(name.clone(), next);
213 }
214 }
215
216 /// The user variables currently in scope.
217 pub(crate) fn user_variables(&self) -> &im::HashMap<EcoString, usize> {
218 &self.user_variables
219 }
220
221 /// Restore previously saved user variables, reverting any counters advanced
222 /// during the branch to their earlier values. Variables introduced in the
223 /// branch with no earlier binding are kept, and the synthesised counters are
224 /// left untouched.
225 pub(crate) fn restore_user_variables(&mut self, previous: &im::HashMap<EcoString, usize>) {
226 self.user_variables.extend(previous.clone());
227 }
228}
229
230#[derive(Debug)]
231pub(crate) struct Generator<'module, 'ast, 'doc> {
232 module_name: EcoString,
233 src_path: EcoString,
234 line_numbers: &'module LineNumbers,
235 function_name: EcoString,
236 function_arguments: Vec<Option<&'module EcoString>>,
237 current_function: CurrentFunction,
238 pub current_scope: Scope,
239 pub function_position: Position,
240 pub scope_position: Position,
241 // We register whether these features are used within an expression so that
242 // the module generator can output a suitable function if it is needed.
243 pub tracker: &'module mut UsageTracker,
244 // We track whether tail call recursion is used so that we can render a loop
245 // at the top level of the function to use in place of pushing new stack
246 // frames.
247 pub tail_recursion_used: bool,
248 /// Statements to be compiled when lifting blocks into statement scope.
249 /// For example, when compiling the following code:
250 /// ```gleam
251 /// let a = {
252 /// let b = 1
253 /// b + 1
254 /// }
255 /// ```
256 /// There will be 2 items in `statement_level`: The first will be `let _block;`
257 /// The second will be the generated code for the block being assigned to `a`.
258 /// This lets use return `_block` as the value that the block evaluated to,
259 /// while still including the necessary code in the output at the right place.
260 ///
261 /// Once the `let` statement has compiled its value, it will add anything accumulated
262 /// in `statement_level` to the generated code, so it will result in:
263 ///
264 /// ```javascript
265 /// let _block;
266 /// {...}
267 /// let a = _block;
268 /// ```
269 ///
270 statement_level: Vec<Document<'ast, 'doc>>,
271
272 /// This will be true if we've generated a `let assert` statement that we know
273 /// is guaranteed to throw.
274 /// This means we can stop code generation for all the following statements
275 /// in the same block!
276 pub let_assert_always_panics: bool,
277
278 pub source_map_builder: Option<Rc<RefCell<DebugIgnore<sourcemap::SourceMapBuilder>>>>,
279}
280
281impl<'module, 'a, 'doc> Generator<'module, 'a, 'doc> {
282 #[allow(clippy::too_many_arguments)] // TODO: FIXME
283 pub fn new(
284 module_name: EcoString,
285 src_path: EcoString,
286 line_numbers: &'module LineNumbers,
287 function_name: EcoString,
288 function_arguments: Vec<Option<&'module EcoString>>,
289 tracker: &'module mut UsageTracker,
290 initial_scope_vars: im::HashMap<EcoString, usize>,
291 source_map_builder: Option<Rc<RefCell<DebugIgnore<sourcemap::SourceMapBuilder>>>>,
292 ) -> Self {
293 let mut current_scope = Scope::new(initial_scope_vars);
294 let mut current_function = CurrentFunction::Module;
295 for &name in function_arguments.iter().flatten() {
296 // Initialise the function arguments
297 current_scope.set_counter(name, 0);
298
299 // If any of the function arguments shadow the current function then
300 // recursion is no longer possible.
301 if function_name.as_ref() == name {
302 current_function = CurrentFunction::ModuleWithShadowingArgument;
303 }
304 }
305 Self {
306 tracker,
307 module_name,
308 src_path,
309 line_numbers,
310 function_name,
311 function_arguments,
312 tail_recursion_used: false,
313 current_scope,
314 current_function,
315 function_position: Position::Tail,
316 scope_position: Position::Tail,
317 statement_level: Vec::new(),
318 let_assert_always_panics: false,
319 source_map_builder,
320 }
321 }
322
323 pub fn local_var(&mut self, name: &EcoString) -> EcoString {
324 match self.current_scope.counter(name) {
325 None => {
326 self.current_scope.set_counter(name, 0);
327 maybe_escape_identifier(name)
328 }
329 Some(0) => maybe_escape_identifier(name),
330 Some(n) if name == "$" => eco_format!("${n}"),
331 Some(n) => eco_format!("{name}${n}"),
332 }
333 }
334
335 pub fn next_local_var(&mut self, name: &EcoString) -> EcoString {
336 self.current_scope.advance_counter(name);
337 self.local_var(name)
338 }
339
340 pub fn function_body(
341 &mut self,
342 arena: &'doc DocumentArena<'a, 'doc>,
343 body: &'a [TypedStatement],
344 arguments: &'a [TypedArg],
345 ) -> Document<'a, 'doc> {
346 let body = self.statements(arena, body);
347 if self.tail_recursion_used {
348 self.tail_call_loop(arena, body, arguments)
349 } else {
350 body
351 }
352 }
353
354 fn tail_call_loop(
355 &mut self,
356 arena: &'doc DocumentArena<'a, 'doc>,
357 body: Document<'a, 'doc>,
358 arguments: &'a [TypedArg],
359 ) -> Document<'a, 'doc> {
360 let loop_assignments = arena.concat(arguments.iter().flat_map(|arg| {
361 arg.get_variable_name().map(|name| {
362 let var = maybe_escape_identifier(name);
363 docvec![
364 arena,
365 self.source_map_tracker(arena, arg.location.start),
366 LET_SPACE_DOCUMENT,
367 var,
368 SPACE_EQUAL_LOOP_DOLLAR_DOCUMENT,
369 name,
370 SEMICOLON_DOCUMENT,
371 LINE_DOCUMENT
372 ]
373 })
374 }));
375 docvec![
376 arena,
377 WHILE_TRUE_OPEN_PAREN_DOCUMENT,
378 docvec![arena, LINE_DOCUMENT, loop_assignments, body].nest(arena, INDENT),
379 LINE_DOCUMENT,
380 CLOSE_CURLY_DOCUMENT
381 ]
382 }
383
384 fn statement(
385 &mut self,
386 arena: &'doc DocumentArena<'a, 'doc>,
387 statement: &'a TypedStatement,
388 ) -> Document<'a, 'doc> {
389 let expression_doc = match statement {
390 Statement::Expression(expression) => self.expression(arena, expression),
391 Statement::Assignment(assignment) => self.assignment(arena, assignment),
392 Statement::Use(use_) => self.expression(arena, &use_.call),
393 Statement::Assert(assert) => self.assert(arena, assert),
394 };
395 self.add_statement_level(arena, expression_doc)
396 }
397
398 fn add_statement_level(
399 &mut self,
400 arena: &'doc DocumentArena<'a, 'doc>,
401 expression: Document<'a, 'doc>,
402 ) -> Document<'a, 'doc> {
403 if self.statement_level.is_empty() {
404 expression
405 } else {
406 let mut statements = std::mem::take(&mut self.statement_level);
407 statements.push(expression);
408 arena.join(statements, LINE_DOCUMENT)
409 }
410 }
411
412 pub fn expression(
413 &mut self,
414 arena: &'doc DocumentArena<'a, 'doc>,
415 expression: &'a TypedExpr,
416 ) -> Document<'a, 'doc> {
417 let mut document = match expression {
418 TypedExpr::String { value, .. } => string(arena, value),
419
420 TypedExpr::Int { value, .. } => int(arena, value),
421 TypedExpr::Float { float_value, .. } => float_from_value(arena, float_value.value()),
422
423 TypedExpr::List { elements, tail, .. } => {
424 self.not_in_tail_position(Some(Ordering::Strict), |this| match tail {
425 Some(tail) => {
426 this.tracker.prepend_used = true;
427 let tail = this.wrap_expression(arena, tail);
428 prepend(
429 arena,
430 elements
431 .iter()
432 .map(|element| this.wrap_expression(arena, element)),
433 tail,
434 )
435 }
436 None if elements.is_empty() => this.empty_list(),
437 None => {
438 this.tracker.list_used = true;
439 list(
440 arena,
441 elements
442 .iter()
443 .map(|element| this.wrap_expression(arena, element)),
444 )
445 }
446 })
447 }
448
449 TypedExpr::Tuple { elements, .. } => self.tuple(arena, elements),
450 TypedExpr::TupleIndex { tuple, index, .. } => self.tuple_index(arena, tuple, *index),
451
452 TypedExpr::Case {
453 subjects,
454 clauses,
455 compiled_case,
456 ..
457 } => decision::case(arena, compiled_case, clauses, subjects, self),
458
459 TypedExpr::Call { fun, arguments, .. } => self.call(arena, fun, arguments),
460 TypedExpr::Fn {
461 arguments,
462 body,
463 kind,
464 ..
465 } => self.fn_(arena, arguments, body, kind),
466
467 TypedExpr::RecordAccess { record, label, .. } => {
468 self.record_access(arena, record, label)
469 }
470
471 TypedExpr::PositionalAccess { record, index, .. } => {
472 self.positional_access(arena, record, *index)
473 }
474
475 TypedExpr::RecordUpdate {
476 updated_record_assigned_name,
477 updated_record,
478 constructor,
479 arguments,
480 ..
481 } => self.record_update(
482 arena,
483 updated_record_assigned_name,
484 updated_record,
485 constructor,
486 arguments,
487 ),
488
489 TypedExpr::Var {
490 name, constructor, ..
491 } => self.variable(arena, name, constructor),
492
493 TypedExpr::Pipeline {
494 first_value,
495 assignments,
496 finally,
497 ..
498 } => self.pipeline(arena, first_value, assignments.as_slice(), finally),
499
500 TypedExpr::Block { statements, .. } => self.block(arena, statements),
501
502 TypedExpr::BinOp {
503 operator,
504 left,
505 right,
506 ..
507 } => self.bin_op(arena, operator, left, right),
508
509 TypedExpr::Todo {
510 message, location, ..
511 } => self.todo(arena, message.as_ref().map(|m| &**m), location),
512
513 TypedExpr::Panic {
514 location, message, ..
515 } => self.panic(arena, location, message.as_ref().map(|m| &**m)),
516
517 TypedExpr::BitArray { segments, .. } => self.bit_array(arena, segments),
518
519 TypedExpr::ModuleSelect {
520 module_alias,
521 label,
522 constructor,
523 ..
524 } => self.module_select(arena, module_alias, label, constructor),
525
526 TypedExpr::NegateBool { value, .. } => {
527 self.negate_with(arena, EXCLAMATION_MARK_DOCUMENT, value)
528 }
529
530 TypedExpr::NegateInt { value, .. } => {
531 self.negate_with(arena, MINUS_SPACE_DOCUMENT, value)
532 }
533
534 TypedExpr::Echo {
535 expression,
536 message,
537 location,
538 ..
539 } => {
540 let expression = expression
541 .as_ref()
542 .expect("echo with no expression outside of pipe");
543 let expresion_doc =
544 self.not_in_tail_position(None, |this| this.wrap_expression(arena, expression));
545 self.echo(arena, expresion_doc, message.as_deref(), location)
546 }
547
548 TypedExpr::Invalid { .. } => {
549 panic!("invalid expressions should not reach code generation")
550 }
551 };
552 if let Position::Statement = self.scope_position
553 && expression_requires_semicolon(expression)
554 {
555 document = document.append(arena, SEMICOLON_DOCUMENT);
556 }
557 if expression.handles_own_return() {
558 docvec![
559 arena,
560 self.source_map_tracker(arena, expression.location().start),
561 document
562 ]
563 } else {
564 docvec![
565 arena,
566 self.source_map_tracker(arena, expression.location().start),
567 self.wrap_return(arena, document)
568 ]
569 }
570 }
571
572 /// Return the singleton empty list; all empty lists are the same underlying
573 /// reference, which makes comparison faster.
574 fn empty_list(&mut self) -> Document<'a, 'doc> {
575 self.tracker.list_empty_const_used = true;
576 DOLLAR_LIST_DOLLAR_EMPTY_DOLLAR_CONST_DOCUMENT
577 }
578
579 fn negate_with(
580 &mut self,
581 arena: &'doc DocumentArena<'a, 'doc>,
582 with: Document<'a, 'doc>,
583 value: &'a TypedExpr,
584 ) -> Document<'a, 'doc> {
585 self.not_in_tail_position(None, |this| {
586 docvec![arena, with, this.wrap_expression(arena, value)]
587 })
588 }
589
590 fn bit_array(
591 &mut self,
592 arena: &'doc DocumentArena<'a, 'doc>,
593 segments: &'a [TypedExprBitArraySegment],
594 ) -> Document<'a, 'doc> {
595 self.tracker.bit_array_literal_used = true;
596
597 // Collect all the values used in segments.
598 let segments_array = array(
599 arena,
600 segments.iter().map(|segment| {
601 let value = self.not_in_tail_position(Some(Ordering::Strict), |this| {
602 this.wrap_expression(arena, &segment.value)
603 });
604
605 let details = self.bit_array_segment_details(arena, segment);
606
607 match details.type_ {
608 BitArraySegmentType::BitArray => {
609 if segment.size().is_some() {
610 self.tracker.bit_array_slice_used = true;
611 docvec![
612 arena,
613 BIT_ARRAY_SLICE_OPEN_PAREN_DOCUMENT,
614 value,
615 COMMA_ZERO_COMMA_SPACE,
616 details.size,
617 CLOSE_PAREN_DOCUMENT
618 ]
619 } else {
620 value
621 }
622 }
623 BitArraySegmentType::Int => {
624 match (details.size_value, segment.value.as_ref()) {
625 (Some(size_value), TypedExpr::Int { int_value, .. })
626 if size_value <= SAFE_INT_SEGMENT_MAX_SIZE.into()
627 && (&size_value % BigInt::from(8) == BigInt::ZERO) =>
628 {
629 let bytes = bit_array_segment_int_value_to_bytes(
630 int_value.clone(),
631 size_value,
632 segment.endianness(),
633 );
634
635 u8_slice(arena, &bytes)
636 }
637
638 (Some(size_value), _) if size_value == 8.into() => value,
639
640 (Some(size_value), _) if size_value <= 0.into() => EMPTY_DOCUMENT,
641
642 _ => {
643 self.tracker.sized_integer_segment_used = true;
644 let size = details.size;
645 let is_big = bool(segment.endianness().is_big());
646 docvec![
647 arena,
648 SIZED_INT_OPEN_PAREN_DOCUMENT,
649 value,
650 COMMA_SPACE_DOCUMENT,
651 size,
652 COMMA_SPACE_DOCUMENT,
653 is_big,
654 CLOSE_PAREN_DOCUMENT
655 ]
656 }
657 }
658 }
659 BitArraySegmentType::Float => {
660 self.tracker.float_bit_array_segment_used = true;
661 let size = details.size;
662 let is_big = bool(details.endianness.is_big());
663 docvec![
664 arena,
665 SIZED_FLOAT_OPEN_PAREN_DOCUMENT,
666 value,
667 COMMA_SPACE_DOCUMENT,
668 size,
669 COMMA_SPACE_DOCUMENT,
670 is_big,
671 CLOSE_PAREN_DOCUMENT
672 ]
673 }
674 BitArraySegmentType::String(StringEncoding::Utf8) => {
675 self.tracker.string_bit_array_segment_used = true;
676 docvec![
677 arena,
678 STRING_BITS_OPEN_PAREN_DOCUMENT,
679 value,
680 CLOSE_PAREN_DOCUMENT
681 ]
682 }
683 BitArraySegmentType::String(StringEncoding::Utf16) => {
684 self.tracker.string_utf16_bit_array_segment_used = true;
685 let is_big = bool(details.endianness.is_big());
686 docvec![
687 arena,
688 STRING_TO_UTF16_OPEN_PAREN_DOCUMENT,
689 value,
690 COMMA_SPACE_DOCUMENT,
691 is_big,
692 CLOSE_PAREN_DOCUMENT
693 ]
694 }
695 BitArraySegmentType::String(StringEncoding::Utf32) => {
696 self.tracker.string_utf32_bit_array_segment_used = true;
697 let is_big = bool(details.endianness.is_big());
698 docvec![
699 arena,
700 STRING_TO_UTF32_OPEN_PAREN_DOCUMENT,
701 value,
702 COMMA_SPACE_DOCUMENT,
703 is_big,
704 CLOSE_PAREN_DOCUMENT
705 ]
706 }
707 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf8) => {
708 self.tracker.codepoint_bit_array_segment_used = true;
709 docvec![
710 arena,
711 CODEPOINT_BITS_OPEN_PAREN_DOCUMENT,
712 value,
713 CLOSE_PAREN_DOCUMENT
714 ]
715 }
716 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf16) => {
717 self.tracker.codepoint_utf16_bit_array_segment_used = true;
718 let is_big = bool(details.endianness.is_big());
719 docvec![
720 arena,
721 CODEPOINT_TO_UTF16_OPEN_PAREN_DOCUMENT,
722 value,
723 COMMA_SPACE_DOCUMENT,
724 is_big,
725 CLOSE_PAREN_DOCUMENT
726 ]
727 }
728 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf32) => {
729 self.tracker.codepoint_utf32_bit_array_segment_used = true;
730 let is_big = bool(details.endianness.is_big());
731 docvec![
732 arena,
733 CODEPOINT_TO_UTF32_OPEN_PAREN_DOCUMENT,
734 value,
735 COMMA_SPACE_DOCUMENT,
736 is_big,
737 CLOSE_PAREN_DOCUMENT
738 ]
739 }
740 }
741 }),
742 );
743
744 docvec![
745 arena,
746 TO_BIT_ARRAY_OPEN_PAREN_DOCUMENT,
747 segments_array,
748 CLOSE_PAREN_DOCUMENT
749 ]
750 }
751
752 fn bit_array_segment_details(
753 &mut self,
754 arena: &'doc DocumentArena<'a, 'doc>,
755 segment: &'a TypedExprBitArraySegment,
756 ) -> BitArraySegmentDetails<'a, 'doc> {
757 let size = segment.size();
758 let unit = segment.unit();
759 let (size_value, size) = match size {
760 Some(TypedExpr::Int { int_value, .. }) => {
761 let size_value = int_value * unit;
762 let size = eco_format!("{size_value}").to_doc(arena);
763 (Some(size_value), size)
764 }
765 Some(size) => {
766 let mut size = self.not_in_tail_position(Some(Ordering::Strict), |this| {
767 this.wrap_expression(arena, size)
768 });
769
770 if unit != 1 {
771 size = size.group(arena).append(
772 arena,
773 SPACE_TIMES_SPACE_DOCUMENT.append(arena, unit.to_doc(arena)),
774 );
775 }
776
777 (None, size)
778 }
779
780 None => {
781 let size_value: usize = if segment.type_.is_int() { 8 } else { 64 };
782 (Some(BigInt::from(size_value)), size_value.to_doc(arena))
783 }
784 };
785
786 let type_ = BitArraySegmentType::from_segment(segment);
787
788 BitArraySegmentDetails {
789 type_,
790 size,
791 size_value,
792 endianness: segment.endianness(),
793 }
794 }
795
796 pub fn wrap_return(
797 &mut self,
798 arena: &'doc DocumentArena<'a, 'doc>,
799 document: Document<'a, 'doc>,
800 ) -> Document<'a, 'doc> {
801 match &self.scope_position {
802 Position::Tail => docvec![arena, RETURN_SPACE_DOCUMENT, document, SEMICOLON_DOCUMENT],
803 Position::Expression(_) | Position::Statement => document,
804 Position::Assign(name) => docvec![
805 arena,
806 name.clone(),
807 SPACE_EQUAL_SPACE_DOCUMENT,
808 document,
809 SEMICOLON_DOCUMENT
810 ],
811 }
812 }
813
814 pub fn not_in_tail_position<CompileFn, Output>(
815 &mut self,
816 // If ordering is None, it is inherited from the parent scope.
817 // It will be None in cases like `!x`, where `x` can be lifted
818 // only if the ordering is already loose.
819 ordering: Option<Ordering>,
820 compile: CompileFn,
821 ) -> Output
822 where
823 CompileFn: Fn(&mut Self) -> Output,
824 {
825 let new_ordering = ordering.unwrap_or(self.scope_position.ordering());
826
827 let function_position = std::mem::replace(
828 &mut self.function_position,
829 Position::Expression(new_ordering),
830 );
831 let scope_position =
832 std::mem::replace(&mut self.scope_position, Position::Expression(new_ordering));
833
834 let result = compile(self);
835
836 self.function_position = function_position;
837 self.scope_position = scope_position;
838 result
839 }
840
841 /// Use the `_block` variable if the expression is JS statement.
842 pub fn wrap_expression(
843 &mut self,
844 arena: &'doc DocumentArena<'a, 'doc>,
845 expression: &'a TypedExpr,
846 ) -> Document<'a, 'doc> {
847 match (expression, &self.scope_position) {
848 (_, Position::Tail | Position::Assign(_)) => self.expression(arena, expression),
849 (
850 TypedExpr::Panic { .. }
851 | TypedExpr::Todo { .. }
852 | TypedExpr::Case { .. }
853 | TypedExpr::Pipeline { .. }
854 | TypedExpr::RecordUpdate {
855 // Record updates that assign a variable generate multiple statements
856 updated_record_assigned_name: Some(_),
857 ..
858 },
859 Position::Expression(Ordering::Loose),
860 ) => self.wrap_block(arena, |this| this.expression(arena, expression)),
861 (
862 TypedExpr::Panic { .. }
863 | TypedExpr::Todo { .. }
864 | TypedExpr::Case { .. }
865 | TypedExpr::Pipeline { .. }
866 | TypedExpr::RecordUpdate {
867 // Record updates that assign a variable generate multiple statements
868 updated_record_assigned_name: Some(_),
869 ..
870 },
871 Position::Expression(Ordering::Strict),
872 ) => self.immediately_invoked_function_expression(
873 arena,
874 expression,
875 |this, expression| this.expression(arena, expression),
876 ),
877 _ => self.expression(arena, expression),
878 }
879 }
880
881 /// Wrap an expression using the `_block` variable if required due to being
882 /// a JS statement, or in parens if required due to being an operator or
883 /// a function literal.
884 pub fn child_expression(
885 &mut self,
886 arena: &'doc DocumentArena<'a, 'doc>,
887 expression: &'a TypedExpr,
888 ) -> Document<'a, 'doc> {
889 match expression {
890 TypedExpr::BinOp { operator, .. } if operator.is_operator_to_wrap() => {}
891 TypedExpr::Fn { .. } => {}
892
893 TypedExpr::Int { .. }
894 | TypedExpr::Float { .. }
895 | TypedExpr::String { .. }
896 | TypedExpr::Block { .. }
897 | TypedExpr::Pipeline { .. }
898 | TypedExpr::Var { .. }
899 | TypedExpr::List { .. }
900 | TypedExpr::Call { .. }
901 | TypedExpr::BinOp { .. }
902 | TypedExpr::Case { .. }
903 | TypedExpr::RecordAccess { .. }
904 | TypedExpr::PositionalAccess { .. }
905 | TypedExpr::ModuleSelect { .. }
906 | TypedExpr::Tuple { .. }
907 | TypedExpr::TupleIndex { .. }
908 | TypedExpr::Todo { .. }
909 | TypedExpr::Panic { .. }
910 | TypedExpr::Echo { .. }
911 | TypedExpr::BitArray { .. }
912 | TypedExpr::RecordUpdate { .. }
913 | TypedExpr::NegateBool { .. }
914 | TypedExpr::NegateInt { .. }
915 | TypedExpr::Invalid { .. } => return self.wrap_expression(arena, expression),
916 }
917
918 let document = self.expression(arena, expression);
919 match &self.scope_position {
920 // Here the document is a return statement: `return <expr>;`
921 // or an assignment: `_block = <expr>;`
922 Position::Tail | Position::Assign(_) | Position::Statement => document,
923 Position::Expression(_) => {
924 docvec![arena, OPEN_PAREN_DOCUMENT, document, CLOSE_PAREN_DOCUMENT]
925 }
926 }
927 }
928
929 /// Wrap an expression in an immediately invoked function expression
930 fn immediately_invoked_function_expression<T, ToDoc>(
931 &mut self,
932 arena: &'doc DocumentArena<'a, 'doc>,
933 statements: &'a T,
934 to_doc: ToDoc,
935 ) -> Document<'a, 'doc>
936 where
937 ToDoc: FnOnce(&mut Self, &'a T) -> Document<'a, 'doc>,
938 {
939 // Save initial state
940 let scope_position = std::mem::replace(&mut self.scope_position, Position::Tail);
941 let statement_level = std::mem::take(&mut self.statement_level);
942
943 // Set state for in this iife
944 let current_scope = self.current_scope.clone();
945
946 // Generate the expression
947 let result = to_doc(self, statements);
948 let doc = self.add_statement_level(arena, result);
949 let doc = immediately_invoked_function_expression_document(arena, doc);
950
951 // Reset
952 self.current_scope = current_scope;
953 self.scope_position = scope_position;
954 self.statement_level = statement_level;
955
956 self.wrap_return(arena, doc)
957 }
958
959 fn wrap_block<CompileFn>(
960 &mut self,
961 arena: &'doc DocumentArena<'a, 'doc>,
962 compile: CompileFn,
963 ) -> Document<'a, 'doc>
964 where
965 CompileFn: Fn(&mut Self) -> Document<'a, 'doc>,
966 {
967 let block_variable = self.next_local_var(&BLOCK_VARIABLE.into());
968
969 // Save initial state
970 let scope_position = std::mem::replace(
971 &mut self.scope_position,
972 Position::Assign(block_variable.clone()),
973 );
974 let function_position = std::mem::replace(
975 &mut self.function_position,
976 Position::Expression(Ordering::Strict),
977 );
978
979 // Generate the expression
980 let statement_doc = compile(self);
981
982 // Reset
983 self.scope_position = scope_position;
984 self.function_position = function_position;
985
986 self.statement_level.push(docvec![
987 arena,
988 LET_SPACE_DOCUMENT,
989 block_variable.clone(),
990 SEMICOLON_DOCUMENT
991 ]);
992 self.statement_level.push(statement_doc);
993
994 self.wrap_return(arena, block_variable.to_doc(arena))
995 }
996
997 fn variable(
998 &mut self,
999 arena: &'doc DocumentArena<'a, 'doc>,
1000 name: &'a EcoString,
1001 constructor: &'a ValueConstructor,
1002 ) -> Document<'a, 'doc> {
1003 match &constructor.variant {
1004 ValueConstructorVariant::Record {
1005 arity,
1006 name: variant_name,
1007 ..
1008 } => {
1009 let type_ = constructor.type_.clone();
1010 self.record_constructor(arena, type_, None, variant_name, name, *arity)
1011 }
1012 ValueConstructorVariant::ModuleFn { .. }
1013 | ValueConstructorVariant::ModuleConstant { .. }
1014 | ValueConstructorVariant::LocalVariable { .. } => self.local_var(name).to_doc(arena),
1015 }
1016 }
1017
1018 fn pipeline(
1019 &mut self,
1020 arena: &'doc DocumentArena<'a, 'doc>,
1021 first_value: &'a TypedPipelineAssignment,
1022 assignments: &'a [(TypedPipelineAssignment, PipelineAssignmentKind)],
1023 finally: &'a TypedExpr,
1024 ) -> Document<'a, 'doc> {
1025 let count = assignments.len();
1026 let mut documents = Vec::with_capacity((count + 2) * 2);
1027
1028 let all_assignments = std::iter::once(first_value)
1029 .chain(assignments.iter().map(|(assignment, _kind)| assignment));
1030
1031 let mut latest_local_var: Option<EcoString> = None;
1032 for assignment in all_assignments {
1033 // An echo in a pipeline won't result in an assignment, instead it
1034 // just prints the previous variable assigned in the pipeline.
1035 if let TypedExpr::Echo {
1036 expression: None,
1037 message,
1038 location,
1039 ..
1040 } = assignment.value.as_ref()
1041 {
1042 documents.push(self.not_in_tail_position(Some(Ordering::Strict), |this| {
1043 let var = latest_local_var
1044 .as_ref()
1045 .expect("echo with no previous step in a pipe");
1046 this.echo(arena, var.to_doc(arena), message.as_deref(), location)
1047 }));
1048 documents.push(SEMICOLON_DOCUMENT);
1049 } else {
1050 // Otherwise we assign the intermediate pipe value to a variable.
1051 let assignment_document =
1052 self.not_in_tail_position(Some(Ordering::Strict), |this| {
1053 this.simple_variable_assignment(
1054 arena,
1055 &assignment.name,
1056 &assignment.value,
1057 assignment.location,
1058 )
1059 });
1060 documents.push(self.add_statement_level(arena, assignment_document));
1061 latest_local_var = Some(self.local_var(&assignment.name));
1062 }
1063
1064 documents.push(LINE_DOCUMENT);
1065 }
1066
1067 if let TypedExpr::Echo {
1068 expression: None,
1069 message,
1070 location,
1071 ..
1072 } = finally
1073 {
1074 let var = latest_local_var.expect("echo with no previous step in a pipe");
1075 documents.push(self.echo(arena, var.to_doc(arena), message.as_deref(), location));
1076 match &self.scope_position {
1077 Position::Statement => documents.push(SEMICOLON_DOCUMENT),
1078 Position::Expression(_) | Position::Tail | Position::Assign(_) => {}
1079 }
1080 } else {
1081 let finally_doc = self.expression(arena, finally);
1082 documents.push(self.add_statement_level(arena, finally_doc));
1083 }
1084
1085 arena.concat(documents).force_break(arena)
1086 }
1087
1088 pub(crate) fn expression_flattening_blocks(
1089 &mut self,
1090 arena: &'doc DocumentArena<'a, 'doc>,
1091 expression: &'a TypedExpr,
1092 ) -> Document<'a, 'doc> {
1093 if let TypedExpr::Block { statements, .. } = expression {
1094 self.statements(arena, statements)
1095 } else {
1096 self.expression(arena, expression)
1097 }
1098 }
1099
1100 fn block(
1101 &mut self,
1102 arena: &'doc DocumentArena<'a, 'doc>,
1103 statements: &'a Vec1<TypedStatement>,
1104 ) -> Document<'a, 'doc> {
1105 if statements.len() == 1 {
1106 match statements.first() {
1107 Statement::Expression(expression) => {
1108 return self.child_expression(arena, expression);
1109 }
1110
1111 Statement::Assignment(assignment) => match &assignment.kind {
1112 AssignmentKind::Let | AssignmentKind::Generated => {
1113 return self.child_expression(arena, &assignment.value);
1114 }
1115 // We can't just return the right-hand side of a `let assert`
1116 // assignment; we still need to check that the pattern matches.
1117 AssignmentKind::Assert { .. } => {}
1118 },
1119
1120 Statement::Use(use_) => return self.child_expression(arena, &use_.call),
1121
1122 // Similar to `let assert`, we can't immediately return the value
1123 // that is asserted; we have to actually perform the assertion.
1124 Statement::Assert(_) => {}
1125 }
1126 }
1127 match &self.scope_position {
1128 Position::Tail | Position::Assign(_) | Position::Statement => {
1129 self.block_document(arena, statements)
1130 }
1131 Position::Expression(Ordering::Strict) => self.immediately_invoked_function_expression(
1132 arena,
1133 statements,
1134 |this, statements| this.statements(arena, statements),
1135 ),
1136 Position::Expression(Ordering::Loose) => self.wrap_block(arena, |this| {
1137 // Save previous scope
1138 let current_scope = this.current_scope.clone();
1139
1140 let document = this.block_document(arena, statements);
1141
1142 // Restore previous state
1143 this.current_scope = current_scope;
1144
1145 document
1146 }),
1147 }
1148 }
1149
1150 fn block_document(
1151 &mut self,
1152 arena: &'doc DocumentArena<'a, 'doc>,
1153 statements: &'a Vec1<TypedStatement>,
1154 ) -> Document<'a, 'doc> {
1155 let statements = self.statements(arena, statements);
1156 docvec![
1157 arena,
1158 OPEN_CURLY_DOCUMENT,
1159 docvec![arena, LINE_DOCUMENT, statements].nest(arena, INDENT),
1160 LINE_DOCUMENT,
1161 CLOSE_CURLY_DOCUMENT
1162 ]
1163 }
1164
1165 fn statements(
1166 &mut self,
1167 arena: &'doc DocumentArena<'a, 'doc>,
1168 statements: &'a [TypedStatement],
1169 ) -> Document<'a, 'doc> {
1170 // If there are any statements that need to be printed at statement level, that's
1171 // for an outer scope so we don't want to print them inside this one.
1172 let statement_level = std::mem::take(&mut self.statement_level);
1173 let count = statements.len();
1174 let mut documents = Vec::with_capacity(count * 3);
1175 for (i, statement) in statements.iter().enumerate() {
1176 if i + 1 < count {
1177 let function_position =
1178 std::mem::replace(&mut self.function_position, Position::Statement);
1179 let scope_position =
1180 std::mem::replace(&mut self.scope_position, Position::Statement);
1181
1182 documents.push(self.statement(arena, statement));
1183
1184 self.function_position = function_position;
1185 self.scope_position = scope_position;
1186
1187 documents.push(LINE_DOCUMENT);
1188 } else {
1189 documents.push(self.statement(arena, statement));
1190 }
1191
1192 // If we've generated code for a statement that always throws, we
1193 // can skip code generation for all the following ones.
1194 if self.let_assert_always_panics {
1195 self.let_assert_always_panics = false;
1196 break;
1197 }
1198 }
1199 self.statement_level = statement_level;
1200 if count == 1 {
1201 arena.concat(documents)
1202 } else {
1203 arena.concat(documents).force_break(arena)
1204 }
1205 }
1206
1207 fn simple_variable_assignment(
1208 &mut self,
1209 arena: &'doc DocumentArena<'a, 'doc>,
1210 name: &'a EcoString,
1211 value: &'a TypedExpr,
1212 location: SrcSpan,
1213 ) -> Document<'a, 'doc> {
1214 // Subject must be rendered before the variable for variable numbering
1215 let subject = self.not_in_tail_position(Some(Ordering::Loose), |this| {
1216 this.wrap_expression(arena, value)
1217 });
1218 let js_name = self.next_local_var(name);
1219 let assignment = docvec![
1220 arena,
1221 self.source_map_tracker(arena, location.start),
1222 LET_SPACE_DOCUMENT,
1223 js_name.clone(),
1224 SPACE_EQUAL_SPACE_DOCUMENT,
1225 subject,
1226 SEMICOLON_DOCUMENT
1227 ];
1228 let assignment = match &self.scope_position {
1229 Position::Expression(_) | Position::Statement => assignment,
1230 Position::Tail => docvec![
1231 arena,
1232 assignment,
1233 LINE_DOCUMENT,
1234 RETURN_SPACE_DOCUMENT,
1235 js_name,
1236 SEMICOLON_DOCUMENT
1237 ],
1238 Position::Assign(block_variable) => docvec![
1239 arena,
1240 assignment,
1241 LINE_DOCUMENT,
1242 block_variable.clone(),
1243 SPACE_EQUAL_SPACE_DOCUMENT,
1244 js_name,
1245 SEMICOLON_DOCUMENT
1246 ],
1247 };
1248
1249 assignment.force_break(arena)
1250 }
1251
1252 fn assignment(
1253 &mut self,
1254 arena: &'doc DocumentArena<'a, 'doc>,
1255 assignment: &'a TypedAssignment,
1256 ) -> Document<'a, 'doc> {
1257 let TypedAssignment {
1258 pattern,
1259 kind,
1260 value,
1261 compiled_case,
1262 location,
1263 annotation: _,
1264 } = assignment;
1265
1266 // In case the pattern is just a variable, we special case it to
1267 // generate just a simple assignment instead of using the decision tree
1268 // for the code generation step.
1269 if let TypedPattern::Variable { name, .. } = pattern {
1270 return self.simple_variable_assignment(arena, name, value, *location);
1271 }
1272
1273 docvec![
1274 arena,
1275 self.source_map_tracker(arena, location.start),
1276 decision::let_(arena, compiled_case, value, kind, self, pattern)
1277 ]
1278 }
1279
1280 fn assert(
1281 &mut self,
1282 arena: &'doc DocumentArena<'a, 'doc>,
1283 assert: &'a TypedAssert,
1284 ) -> Document<'a, 'doc> {
1285 let TypedAssert {
1286 location,
1287 value,
1288 message,
1289 } = assert;
1290
1291 let message = match message {
1292 Some(message) => self.not_in_tail_position(Some(Ordering::Strict), |this| {
1293 this.expression(arena, message)
1294 }),
1295 None => string(arena, "Assertion failed."),
1296 };
1297
1298 let check = self.not_in_tail_position(Some(Ordering::Loose), |this| {
1299 this.assert_check(arena, value, &message, *location)
1300 });
1301
1302 match &self.scope_position {
1303 Position::Expression(_) | Position::Statement => check,
1304 Position::Tail | Position::Assign(_) => {
1305 docvec![
1306 arena,
1307 check,
1308 LINE_DOCUMENT,
1309 self.wrap_return(arena, UNDEFINED_DOCUMENT)
1310 ]
1311 }
1312 }
1313 }
1314
1315 fn assert_check(
1316 &mut self,
1317 arena: &'doc DocumentArena<'a, 'doc>,
1318 subject: &'a TypedExpr,
1319 message: &Document<'a, 'doc>,
1320 location: SrcSpan,
1321 ) -> Document<'a, 'doc> {
1322 let (subject_document, mut fields) = match subject {
1323 TypedExpr::Call { fun, arguments, .. } => {
1324 let argument_variables = arguments
1325 .iter()
1326 .map(|element| {
1327 self.not_in_tail_position(Some(Ordering::Strict), |this| {
1328 this.assign_to_variable(arena, &element.value)
1329 })
1330 })
1331 .collect_vec();
1332 (
1333 self.call_with_doc_arguments(arena, fun, argument_variables.clone()),
1334 vec![
1335 ("kind", string(arena, "function_call")),
1336 (
1337 "arguments",
1338 array(
1339 arena,
1340 argument_variables.into_iter().zip(arguments).map(
1341 |(variable, argument)| {
1342 self.asserted_expression(
1343 arena,
1344 AssertExpression::from_expression(&argument.value),
1345 Some(variable),
1346 argument.location(),
1347 )
1348 },
1349 ),
1350 ),
1351 ),
1352 ],
1353 )
1354 }
1355
1356 TypedExpr::BinOp {
1357 operator,
1358 left,
1359 right,
1360 ..
1361 } => {
1362 match operator {
1363 BinOp::And => return self.assert_and(arena, left, right, message, location),
1364 BinOp::Or => return self.assert_or(arena, left, right, message, location),
1365 BinOp::Eq
1366 | BinOp::NotEq
1367 | BinOp::LtInt
1368 | BinOp::LtEqInt
1369 | BinOp::LtFloat
1370 | BinOp::LtEqFloat
1371 | BinOp::GtEqInt
1372 | BinOp::GtInt
1373 | BinOp::GtEqFloat
1374 | BinOp::GtFloat
1375 | BinOp::AddInt
1376 | BinOp::AddFloat
1377 | BinOp::SubInt
1378 | BinOp::SubFloat
1379 | BinOp::MultInt
1380 | BinOp::MultFloat
1381 | BinOp::DivInt
1382 | BinOp::DivFloat
1383 | BinOp::RemainderInt
1384 | BinOp::Concatenate => {}
1385 }
1386
1387 let left_document = self.not_in_tail_position(Some(Ordering::Loose), |this| {
1388 this.assign_to_variable(arena, left)
1389 });
1390 let right_document = self.not_in_tail_position(Some(Ordering::Loose), |this| {
1391 this.assign_to_variable(arena, right)
1392 });
1393
1394 (
1395 self.bin_op_with_doc_operands(
1396 arena,
1397 *operator,
1398 left_document,
1399 right_document,
1400 &left.type_(),
1401 )
1402 .surround(arena, OPEN_PAREN_DOCUMENT, CLOSE_PAREN_DOCUMENT),
1403 vec![
1404 ("kind", string(arena, "binary_operator")),
1405 ("operator", string(arena, operator.name())),
1406 (
1407 "left",
1408 self.asserted_expression(
1409 arena,
1410 AssertExpression::from_expression(left),
1411 Some(left_document),
1412 left.location(),
1413 ),
1414 ),
1415 (
1416 "right",
1417 self.asserted_expression(
1418 arena,
1419 AssertExpression::from_expression(right),
1420 Some(right_document),
1421 right.location(),
1422 ),
1423 ),
1424 ],
1425 )
1426 }
1427
1428 TypedExpr::Int { .. }
1429 | TypedExpr::Float { .. }
1430 | TypedExpr::String { .. }
1431 | TypedExpr::Block { .. }
1432 | TypedExpr::Pipeline { .. }
1433 | TypedExpr::Var { .. }
1434 | TypedExpr::Fn { .. }
1435 | TypedExpr::List { .. }
1436 | TypedExpr::Case { .. }
1437 | TypedExpr::RecordAccess { .. }
1438 | TypedExpr::PositionalAccess { .. }
1439 | TypedExpr::ModuleSelect { .. }
1440 | TypedExpr::Tuple { .. }
1441 | TypedExpr::TupleIndex { .. }
1442 | TypedExpr::Todo { .. }
1443 | TypedExpr::Panic { .. }
1444 | TypedExpr::Echo { .. }
1445 | TypedExpr::BitArray { .. }
1446 | TypedExpr::RecordUpdate { .. }
1447 | TypedExpr::NegateBool { .. }
1448 | TypedExpr::NegateInt { .. }
1449 | TypedExpr::Invalid { .. } => (
1450 self.wrap_expression(arena, subject),
1451 vec![
1452 ("kind", string(arena, "expression")),
1453 (
1454 "expression",
1455 self.asserted_expression(
1456 arena,
1457 AssertExpression::from_expression(subject),
1458 Some(FALSE_LOWERCASE_DOCUMENT),
1459 subject.location(),
1460 ),
1461 ),
1462 ],
1463 ),
1464 };
1465
1466 fields.push(("start", location.start.to_doc(arena)));
1467 fields.push(("end", subject.location().end.to_doc(arena)));
1468 fields.push(("expression_start", subject.location().start.to_doc(arena)));
1469
1470 docvec![
1471 arena,
1472 self.source_map_tracker(arena, location.start),
1473 IF_SPACE_OPEN_PAREN_DOCUMENT,
1474 docvec![arena, EXCLAMATION_MARK_DOCUMENT, subject_document].nest(arena, INDENT),
1475 EMPTY_BREAK_DOCUMENT,
1476 CLOSE_PAREN_SPACE_OPEN_CURLY_DOCUMENT,
1477 docvec![
1478 arena,
1479 LINE_DOCUMENT,
1480 self.throw_error(arena, "assert", message, location, fields),
1481 ]
1482 .nest(arena, INDENT),
1483 LINE_DOCUMENT,
1484 CLOSE_CURLY_DOCUMENT,
1485 ]
1486 .group(arena)
1487 }
1488
1489 fn negate_bool_expression(
1490 &mut self,
1491 arena: &'doc DocumentArena<'a, 'doc>,
1492 value: &'a TypedExpr,
1493 ) -> Document<'a, 'doc> {
1494 match value {
1495 TypedExpr::BinOp {
1496 operator,
1497 left,
1498 right,
1499 ..
1500 } => match operator {
1501 BinOp::And => self.print_bin_op(arena, left, right, "||"),
1502 BinOp::Or => self.print_bin_op(arena, left, right, "&&"),
1503 BinOp::Eq => self.equal(arena, left, right, false),
1504 BinOp::NotEq => self.equal(arena, left, right, true),
1505 BinOp::LtInt | BinOp::LtFloat => self.print_bin_op(arena, left, right, ">="),
1506 BinOp::LtEqInt | BinOp::LtEqFloat => self.print_bin_op(arena, left, right, ">"),
1507 BinOp::GtInt | BinOp::GtFloat => self.print_bin_op(arena, left, right, "<="),
1508 BinOp::GtEqInt | BinOp::GtEqFloat => self.print_bin_op(arena, left, right, "<"),
1509 BinOp::AddInt
1510 | BinOp::AddFloat
1511 | BinOp::SubInt
1512 | BinOp::SubFloat
1513 | BinOp::MultInt
1514 | BinOp::MultFloat
1515 | BinOp::DivInt
1516 | BinOp::DivFloat
1517 | BinOp::RemainderInt
1518 | BinOp::Concatenate => unreachable!("type checking should make this impossible"),
1519 },
1520 TypedExpr::NegateBool { value, .. } => self.wrap_expression(arena, value),
1521 TypedExpr::Int { .. }
1522 | TypedExpr::Float { .. }
1523 | TypedExpr::String { .. }
1524 | TypedExpr::Block { .. }
1525 | TypedExpr::Pipeline { .. }
1526 | TypedExpr::Var { .. }
1527 | TypedExpr::Fn { .. }
1528 | TypedExpr::List { .. }
1529 | TypedExpr::Call { .. }
1530 | TypedExpr::Case { .. }
1531 | TypedExpr::RecordAccess { .. }
1532 | TypedExpr::PositionalAccess { .. }
1533 | TypedExpr::ModuleSelect { .. }
1534 | TypedExpr::Tuple { .. }
1535 | TypedExpr::TupleIndex { .. }
1536 | TypedExpr::Todo { .. }
1537 | TypedExpr::Panic { .. }
1538 | TypedExpr::Echo { .. }
1539 | TypedExpr::BitArray { .. }
1540 | TypedExpr::RecordUpdate { .. }
1541 | TypedExpr::NegateInt { .. }
1542 | TypedExpr::Invalid { .. } => docvec![
1543 arena,
1544 EXCLAMATION_MARK_DOCUMENT,
1545 self.wrap_expression(arena, value)
1546 ],
1547 }
1548 }
1549
1550 /// In Gleam, the `&&` operator is short-circuiting, meaning that we can't
1551 /// pre-evaluate both sides of it, and use them in the exception that is
1552 /// thrown.
1553 /// Instead, we need to implement this short-circuiting logic ourself.
1554 ///
1555 /// If we short-circuit, we must leave the second expression unevaluated,
1556 /// and signal that using the `unevaluated` variant, as detailed in the
1557 /// exception format. For the first expression, we know it must be `false`,
1558 /// otherwise we would have continued by evaluating the second expression.
1559 ///
1560 /// Similarly, if we do evaluate the second expression and fail, we know
1561 /// that the first expression must have evaluated to `true`, and the second
1562 /// to `false`. This way, we avoid needing to evaluate either expression
1563 /// twice.
1564 ///
1565 /// The generated code then looks something like this:
1566 /// ```javascript
1567 /// if (expr1) {
1568 /// if (!expr2) {
1569 /// <throw exception>
1570 /// }
1571 /// } else {
1572 /// <throw exception>
1573 /// }
1574 /// ```
1575 ///
1576 fn assert_and(
1577 &mut self,
1578 arena: &'doc DocumentArena<'a, 'doc>,
1579 left: &'a TypedExpr,
1580 right: &'a TypedExpr,
1581 message: &Document<'a, 'doc>,
1582 location: SrcSpan,
1583 ) -> Document<'a, 'doc> {
1584 let left_kind = AssertExpression::from_expression(left);
1585 let right_kind = AssertExpression::from_expression(right);
1586
1587 let fields_if_short_circuiting = vec![
1588 ("kind", string(arena, "binary_operator")),
1589 ("operator", string(arena, "&&")),
1590 (
1591 "left",
1592 self.asserted_expression(
1593 arena,
1594 left_kind,
1595 Some(FALSE_LOWERCASE_DOCUMENT),
1596 left.location(),
1597 ),
1598 ),
1599 (
1600 "right",
1601 self.asserted_expression(
1602 arena,
1603 AssertExpression::Unevaluated,
1604 None,
1605 right.location(),
1606 ),
1607 ),
1608 ("start", location.start.to_doc(arena)),
1609 ("end", right.location().end.to_doc(arena)),
1610 ("expression_start", left.location().start.to_doc(arena)),
1611 ];
1612
1613 let fields = vec![
1614 ("kind", string(arena, "binary_operator")),
1615 ("operator", string(arena, "&&")),
1616 (
1617 "left",
1618 self.asserted_expression(
1619 arena,
1620 left_kind,
1621 Some(TRUE_LOWERCASE_DOCUMENT),
1622 left.location(),
1623 ),
1624 ),
1625 (
1626 "right",
1627 self.asserted_expression(
1628 arena,
1629 right_kind,
1630 Some(FALSE_LOWERCASE_DOCUMENT),
1631 right.location(),
1632 ),
1633 ),
1634 ("start", location.start.to_doc(arena)),
1635 ("end", right.location().end.to_doc(arena)),
1636 ("expression_start", left.location().start.to_doc(arena)),
1637 ];
1638
1639 let left_value = self.not_in_tail_position(Some(Ordering::Loose), |this| {
1640 this.wrap_expression(arena, left)
1641 });
1642
1643 let right_value = self.not_in_tail_position(Some(Ordering::Strict), |this| {
1644 this.negate_bool_expression(arena, right)
1645 });
1646
1647 let right_check = docvec![
1648 arena,
1649 LINE_DOCUMENT,
1650 IF_SPACE_OPEN_PAREN_DOCUMENT,
1651 right_value.nest(arena, INDENT),
1652 CLOSE_PAREN_SPACE_OPEN_CURLY_DOCUMENT,
1653 docvec![
1654 arena,
1655 LINE_DOCUMENT,
1656 self.throw_error(arena, "assert", message, location, fields)
1657 ]
1658 .nest(arena, INDENT),
1659 LINE_DOCUMENT,
1660 CLOSE_CURLY_DOCUMENT,
1661 ];
1662
1663 docvec![
1664 arena,
1665 self.source_map_tracker(arena, location.start),
1666 IF_SPACE_OPEN_PAREN_DOCUMENT,
1667 left_value.nest(arena, INDENT),
1668 CLOSE_PAREN_SPACE_OPEN_CURLY_DOCUMENT,
1669 right_check.nest(arena, INDENT),
1670 LINE_DOCUMENT,
1671 CLOSE_CURLY_ELSE_OPEN_CURLY_DOCUMENT,
1672 docvec![
1673 arena,
1674 LINE_DOCUMENT,
1675 self.throw_error(
1676 arena,
1677 "assert",
1678 message,
1679 location,
1680 fields_if_short_circuiting
1681 )
1682 ]
1683 .nest(arena, INDENT),
1684 LINE_DOCUMENT,
1685 CLOSE_CURLY_DOCUMENT
1686 ]
1687 }
1688
1689 /// Similar to `&&`, `||` is also short-circuiting in Gleam. However, if `||`
1690 /// short-circuits, that's because the first expression evaluated to `true`,
1691 /// meaning the whole assertion succeeds. This allows us to directly use the
1692 /// `||` operator in JavaScript.
1693 ///
1694 /// The only difference is that due to the nature of `||`, if the assertion fails,
1695 /// we know that both sides must have evaluated to `false`, so we don't
1696 /// need to store the values of them in variables beforehand.
1697 fn assert_or(
1698 &mut self,
1699 arena: &'doc DocumentArena<'a, 'doc>,
1700 left: &'a TypedExpr,
1701 right: &'a TypedExpr,
1702 message: &Document<'a, 'doc>,
1703 location: SrcSpan,
1704 ) -> Document<'a, 'doc> {
1705 let fields = vec![
1706 ("kind", string(arena, "binary_operator")),
1707 ("operator", string(arena, "||")),
1708 (
1709 "left",
1710 self.asserted_expression(
1711 arena,
1712 AssertExpression::from_expression(left),
1713 Some(FALSE_LOWERCASE_DOCUMENT),
1714 left.location(),
1715 ),
1716 ),
1717 (
1718 "right",
1719 self.asserted_expression(
1720 arena,
1721 AssertExpression::from_expression(right),
1722 Some(FALSE_LOWERCASE_DOCUMENT),
1723 right.location(),
1724 ),
1725 ),
1726 ("start", location.start.to_doc(arena)),
1727 ("end", right.location().end.to_doc(arena)),
1728 ("expression_start", left.location().start.to_doc(arena)),
1729 ];
1730
1731 let left_value = self.not_in_tail_position(Some(Ordering::Loose), |this| {
1732 this.child_expression(arena, left)
1733 });
1734
1735 let right_value = self.not_in_tail_position(Some(Ordering::Strict), |this| {
1736 this.child_expression(arena, right)
1737 });
1738
1739 docvec![
1740 arena,
1741 LINE_DOCUMENT,
1742 self.source_map_tracker(arena, location.start),
1743 IF_SPACE_OPEN_PAREN_DOCUMENT,
1744 docvec![
1745 arena,
1746 EXCLAMATION_MARK_OPEN_PAREN_DOCUMENT,
1747 left_value,
1748 SPACE_DOUBLE_VERTICAL_BAR_SPACE_DOCUMENT,
1749 right_value,
1750 CLOSE_PAREN_DOCUMENT
1751 ]
1752 .nest(arena, INDENT),
1753 CLOSE_PAREN_SPACE_OPEN_CURLY_DOCUMENT,
1754 docvec![
1755 arena,
1756 LINE_DOCUMENT,
1757 self.throw_error(arena, "assert", message, location, fields)
1758 ]
1759 .nest(arena, INDENT),
1760 LINE_DOCUMENT,
1761 CLOSE_CURLY_DOCUMENT,
1762 ]
1763 }
1764
1765 fn assign_to_variable(
1766 &mut self,
1767 arena: &'doc DocumentArena<'a, 'doc>,
1768 value: &'a TypedExpr,
1769 ) -> Document<'a, 'doc> {
1770 if let TypedExpr::Var { .. } = value {
1771 self.expression(arena, value)
1772 } else {
1773 let value = self.wrap_expression(arena, value);
1774 let variable = self.next_local_var(&ASSIGNMENT_VAR.into());
1775 let assignment = docvec![
1776 arena,
1777 LET_SPACE_DOCUMENT,
1778 variable.clone(),
1779 SPACE_EQUAL_SPACE_DOCUMENT,
1780 value,
1781 SEMICOLON_DOCUMENT
1782 ];
1783 self.statement_level.push(assignment);
1784 variable.to_doc(arena)
1785 }
1786 }
1787
1788 fn asserted_expression(
1789 &mut self,
1790 arena: &'doc DocumentArena<'a, 'doc>,
1791 kind: AssertExpression,
1792 value: Option<Document<'a, 'doc>>,
1793 location: SrcSpan,
1794 ) -> Document<'a, 'doc> {
1795 let kind = match kind {
1796 AssertExpression::Literal => string(arena, "literal"),
1797 AssertExpression::Expression => string(arena, "expression"),
1798 AssertExpression::Unevaluated => string(arena, "unevaluated"),
1799 };
1800
1801 let start = location.start.to_doc(arena);
1802 let end = location.end.to_doc(arena);
1803 let items = if let Some(value) = value {
1804 vec![
1805 ("kind", kind),
1806 ("value", value),
1807 ("start", start),
1808 ("end", end),
1809 ]
1810 } else {
1811 vec![("kind", kind), ("start", start), ("end", end)]
1812 };
1813
1814 wrap_object(
1815 arena,
1816 items
1817 .into_iter()
1818 .map(|(key, value)| (key.to_doc(arena), Some(value))),
1819 )
1820 }
1821
1822 fn tuple(
1823 &mut self,
1824 arena: &'doc DocumentArena<'a, 'doc>,
1825 elements: &'a [TypedExpr],
1826 ) -> Document<'a, 'doc> {
1827 self.not_in_tail_position(Some(Ordering::Strict), |this| {
1828 array(
1829 arena,
1830 elements
1831 .iter()
1832 .map(|element| this.wrap_expression(arena, element)),
1833 )
1834 })
1835 }
1836
1837 fn call(
1838 &mut self,
1839 arena: &'doc DocumentArena<'a, 'doc>,
1840 fun: &'a TypedExpr,
1841 arguments: &'a [TypedCallArg],
1842 ) -> Document<'a, 'doc> {
1843 let arguments = arguments
1844 .iter()
1845 .map(|element| {
1846 self.not_in_tail_position(Some(Ordering::Strict), |this| {
1847 this.wrap_expression(arena, &element.value)
1848 })
1849 })
1850 .collect_vec();
1851
1852 self.call_with_doc_arguments(arena, fun, arguments)
1853 }
1854
1855 fn call_with_doc_arguments(
1856 &mut self,
1857 arena: &'doc DocumentArena<'a, 'doc>,
1858 fun: &'a TypedExpr,
1859 arguments: Vec<Document<'a, 'doc>>,
1860 ) -> Document<'a, 'doc> {
1861 match fun {
1862 // Qualified record construction
1863 TypedExpr::ModuleSelect {
1864 constructor: ModuleValueConstructor::Record { name, .. },
1865 module_alias,
1866 ..
1867 } => self.wrap_return(
1868 arena,
1869 construct_record(arena, Some(module_alias), name, arguments),
1870 ),
1871
1872 // Record construction
1873 TypedExpr::Var {
1874 constructor:
1875 ValueConstructor {
1876 variant: ValueConstructorVariant::Record { .. },
1877 type_,
1878 ..
1879 },
1880 name,
1881 ..
1882 } => {
1883 if type_.is_result_constructor() {
1884 if name == "Ok" {
1885 self.tracker.ok_used = true;
1886 } else if name == "Error" {
1887 self.tracker.error_used = true;
1888 }
1889 }
1890 self.wrap_return(arena, construct_record(arena, None, name, arguments))
1891 }
1892
1893 // Tail call optimisation. If we are calling the current function
1894 // and we are in tail position we can avoid creating a new stack
1895 // frame, enabling recursion with constant memory usage.
1896 TypedExpr::Var { name, .. }
1897 if self.function_name == *name
1898 && self.current_function.can_recurse()
1899 && self.function_position.is_tail()
1900 && self.current_scope.counter(name) == Some(0) =>
1901 {
1902 // Record that tail recursion is happening so that we know to
1903 // render the loop at the top level of the function.
1904 self.tail_recursion_used = true;
1905 arena.concat(
1906 arguments
1907 .into_iter()
1908 .zip(&self.function_arguments)
1909 .enumerate()
1910 .map(|(i, (element, argument))| {
1911 let mut doc = EMPTY_DOCUMENT;
1912
1913 if i != 0 {
1914 doc = doc.append(arena, LINE_DOCUMENT);
1915 }
1916 // Create an assignment for each variable created by the function arguments
1917 if let Some(name) = argument {
1918 doc = docvec![
1919 arena,
1920 doc,
1921 LOOP_DOLLAR_DOCUMENT,
1922 name.to_doc(arena),
1923 SPACE_EQUAL_SPACE_DOCUMENT
1924 ];
1925 }
1926 // Render the value given to the function. Even if it is not
1927 // assigned we still render it because the expression may
1928 // have some side effects.
1929 docvec![arena, doc, element, SEMICOLON_DOCUMENT]
1930 }),
1931 )
1932 }
1933
1934 TypedExpr::Int { .. }
1935 | TypedExpr::Float { .. }
1936 | TypedExpr::String { .. }
1937 | TypedExpr::Block { .. }
1938 | TypedExpr::Pipeline { .. }
1939 | TypedExpr::Var { .. }
1940 | TypedExpr::Fn { .. }
1941 | TypedExpr::List { .. }
1942 | TypedExpr::Call { .. }
1943 | TypedExpr::BinOp { .. }
1944 | TypedExpr::Case { .. }
1945 | TypedExpr::RecordAccess { .. }
1946 | TypedExpr::PositionalAccess { .. }
1947 | TypedExpr::ModuleSelect { .. }
1948 | TypedExpr::Tuple { .. }
1949 | TypedExpr::TupleIndex { .. }
1950 | TypedExpr::Todo { .. }
1951 | TypedExpr::Panic { .. }
1952 | TypedExpr::Echo { .. }
1953 | TypedExpr::BitArray { .. }
1954 | TypedExpr::RecordUpdate { .. }
1955 | TypedExpr::NegateBool { .. }
1956 | TypedExpr::NegateInt { .. }
1957 | TypedExpr::Invalid { .. } => {
1958 let fun = self.not_in_tail_position(None, |this| -> Document<'_, '_> {
1959 let is_fn_literal = matches!(fun, TypedExpr::Fn { .. });
1960 let fun = this.wrap_expression(arena, fun);
1961 if is_fn_literal {
1962 docvec![arena, OPEN_PAREN_DOCUMENT, fun, CLOSE_PAREN_DOCUMENT]
1963 } else {
1964 fun
1965 }
1966 });
1967 let arguments = call_arguments(arena, arguments);
1968 self.wrap_return(arena, docvec![arena, fun, arguments])
1969 }
1970 }
1971 }
1972
1973 fn fn_(
1974 &mut self,
1975 arena: &'doc DocumentArena<'a, 'doc>,
1976 arguments: &'a [TypedArg],
1977 body: &'a [TypedStatement],
1978 kind: &FunctionLiteralKind,
1979 ) -> Document<'a, 'doc> {
1980 // New function, this is now the tail position
1981 let function_position = std::mem::replace(&mut self.function_position, Position::Tail);
1982 let scope_position = std::mem::replace(&mut self.scope_position, Position::Tail);
1983
1984 // And there's a new scope
1985 let scope = self.current_scope.clone();
1986 for name in arguments.iter().flat_map(Arg::get_variable_name) {
1987 self.current_scope.set_counter(name, 0);
1988 }
1989
1990 // This is a new function so track that so that we don't
1991 // mistakenly trigger tail call optimisation
1992 let mut current_function = CurrentFunction::Anonymous;
1993 std::mem::swap(&mut self.current_function, &mut current_function);
1994
1995 // Generate the function body
1996 let result = self.statements(arena, body);
1997
1998 // Reset function name, scope, and tail position tracking
1999 self.function_position = function_position;
2000 self.scope_position = scope_position;
2001 self.current_scope = scope;
2002 std::mem::swap(&mut self.current_function, &mut current_function);
2003
2004 let mut docs = EMPTY_DOCUMENT;
2005
2006 // If the function is a use then we need to add a source map tracker
2007 // before the result to denote that the function is created by the use
2008 if let FunctionLiteralKind::Use { location } = kind {
2009 docs = docs.append(arena, self.source_map_tracker(arena, location.start));
2010 }
2011 docs = docs.append(arena, fun_arguments(arena, arguments, false));
2012 docs = docs.append(arena, SPACE_EQUAL_ARROW_SPACE_OPEN_CURLY_DOCUMENT);
2013 docs = docs.append(arena, BREAKABLE_SPACE_DOCUMENT);
2014 docs = docs.append(arena, result);
2015
2016 docvec![
2017 arena,
2018 docs.nest(arena, INDENT)
2019 .append(arena, BREAKABLE_SPACE_DOCUMENT)
2020 .group(arena),
2021 CLOSE_CURLY_DOCUMENT,
2022 ]
2023 }
2024
2025 fn record_access(
2026 &mut self,
2027 arena: &'doc DocumentArena<'a, 'doc>,
2028 record: &'a TypedExpr,
2029 label: &'a str,
2030 ) -> Document<'a, 'doc> {
2031 self.not_in_tail_position(None, |this| {
2032 let record = this.wrap_expression(arena, record);
2033 docvec![arena, record, DOT_DOCUMENT, maybe_escape_property(label)]
2034 })
2035 }
2036
2037 fn positional_access(
2038 &mut self,
2039 arena: &'doc DocumentArena<'a, 'doc>,
2040 record: &'a TypedExpr,
2041 index: u64,
2042 ) -> Document<'a, 'doc> {
2043 self.not_in_tail_position(None, |this| {
2044 let record = this.wrap_expression(arena, record);
2045 docvec![
2046 arena,
2047 record,
2048 OPEN_SQUARE_DOCUMENT,
2049 index,
2050 CLOSE_SQUARE_DOCUMENT
2051 ]
2052 })
2053 }
2054
2055 fn record_update(
2056 &mut self,
2057 arena: &'doc DocumentArena<'a, 'doc>,
2058 updated_record_assigned_name: &'a Option<EcoString>,
2059 updated_record: &'a TypedExpr,
2060 constructor: &'a TypedExpr,
2061 arguments: &'a [TypedCallArg],
2062 ) -> Document<'a, 'doc> {
2063 match updated_record_assigned_name.as_ref() {
2064 Some(name) => {
2065 docvec![
2066 arena,
2067 self.not_in_tail_position(None, |this| this.simple_variable_assignment(
2068 arena,
2069 name,
2070 updated_record,
2071 updated_record.location(),
2072 )),
2073 LINE_DOCUMENT,
2074 self.call(arena, constructor, arguments),
2075 ]
2076 }
2077 None => self.call(arena, constructor, arguments),
2078 }
2079 }
2080
2081 fn tuple_index(
2082 &mut self,
2083 arena: &'doc DocumentArena<'a, 'doc>,
2084 tuple: &'a TypedExpr,
2085 index: u64,
2086 ) -> Document<'a, 'doc> {
2087 self.not_in_tail_position(None, |this| {
2088 let tuple = this.wrap_expression(arena, tuple);
2089 docvec![arena, tuple, eco_format!("[{index}]")]
2090 })
2091 }
2092
2093 fn bin_op(
2094 &mut self,
2095 arena: &'doc DocumentArena<'a, 'doc>,
2096 name: &'a BinOp,
2097 left: &'a TypedExpr,
2098 right: &'a TypedExpr,
2099 ) -> Document<'a, 'doc> {
2100 match name {
2101 BinOp::And => self.print_bin_op(arena, left, right, "&&"),
2102 BinOp::Or => self.print_bin_op(arena, left, right, "||"),
2103 BinOp::LtInt | BinOp::LtFloat => self.print_bin_op(arena, left, right, "<"),
2104 BinOp::LtEqInt | BinOp::LtEqFloat => self.print_bin_op(arena, left, right, "<="),
2105 BinOp::Eq => self.equal(arena, left, right, true),
2106 BinOp::NotEq => self.equal(arena, left, right, false),
2107 BinOp::GtInt | BinOp::GtFloat => self.print_bin_op(arena, left, right, ">"),
2108 BinOp::GtEqInt | BinOp::GtEqFloat => self.print_bin_op(arena, left, right, ">="),
2109 BinOp::Concatenate | BinOp::AddInt | BinOp::AddFloat => {
2110 self.print_bin_op(arena, left, right, "+")
2111 }
2112 BinOp::SubInt | BinOp::SubFloat => self.print_bin_op(arena, left, right, "-"),
2113 BinOp::MultInt | BinOp::MultFloat => self.print_bin_op(arena, left, right, "*"),
2114 BinOp::RemainderInt => self.remainder_int(arena, left, right),
2115 BinOp::DivInt => self.div_int(arena, left, right),
2116 BinOp::DivFloat => self.div_float(arena, left, right),
2117 }
2118 }
2119
2120 fn div_int(
2121 &mut self,
2122 arena: &'doc DocumentArena<'a, 'doc>,
2123 left: &'a TypedExpr,
2124 right: &'a TypedExpr,
2125 ) -> Document<'a, 'doc> {
2126 let left_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| {
2127 this.child_expression(arena, left)
2128 });
2129 let right_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| {
2130 this.child_expression(arena, right)
2131 });
2132
2133 // If we have a constant value divided by zero then it's safe to replace
2134 // it directly with 0.
2135 if left.is_literal() && right.is_zero_compile_time_number() {
2136 "0".to_doc(arena)
2137 } else if right.is_non_zero_compile_time_number() {
2138 let division = if let TypedExpr::BinOp { .. } = left {
2139 docvec![
2140 arena,
2141 left_doc.surround(arena, OPEN_PAREN_DOCUMENT, CLOSE_PAREN_DOCUMENT),
2142 SPACE_SLASH_SPACE_DOCUMENT,
2143 right_doc
2144 ]
2145 } else {
2146 docvec![arena, left_doc, SPACE_SLASH_SPACE_DOCUMENT, right_doc]
2147 };
2148 docvec![
2149 arena,
2150 GLOBAL_THIS_DOT_MATH_DOT_TRUNC_DOCUMENT,
2151 wrap_arguments(arena, [division])
2152 ]
2153 } else {
2154 self.tracker.int_division_used = true;
2155 docvec![
2156 arena,
2157 DIVIDE_INT_DOCUMENT,
2158 wrap_arguments(arena, [left_doc, right_doc])
2159 ]
2160 }
2161 }
2162
2163 fn remainder_int(
2164 &mut self,
2165 arena: &'doc DocumentArena<'a, 'doc>,
2166 left: &'a TypedExpr,
2167 right: &'a TypedExpr,
2168 ) -> Document<'a, 'doc> {
2169 let left_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| {
2170 this.child_expression(arena, left)
2171 });
2172 let right_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| {
2173 this.child_expression(arena, right)
2174 });
2175
2176 // If we have a constant value divided by zero then it's safe to replace
2177 // it directly with 0.
2178 if left.is_literal() && right.is_zero_compile_time_number() {
2179 "0".to_doc(arena)
2180 } else if right.is_non_zero_compile_time_number() {
2181 if let TypedExpr::BinOp { .. } = left {
2182 docvec![
2183 arena,
2184 left_doc.surround(arena, OPEN_PAREN_DOCUMENT, CLOSE_PAREN_DOCUMENT),
2185 SPACE_MODULO_SPACE_DOCUMENT,
2186 right_doc
2187 ]
2188 } else {
2189 docvec![arena, left_doc, SPACE_MODULO_SPACE_DOCUMENT, right_doc]
2190 }
2191 } else {
2192 self.tracker.int_remainder_used = true;
2193 docvec![
2194 arena,
2195 "remainderInt",
2196 wrap_arguments(arena, [left_doc, right_doc])
2197 ]
2198 }
2199 }
2200
2201 fn div_float(
2202 &mut self,
2203 arena: &'doc DocumentArena<'a, 'doc>,
2204 left: &'a TypedExpr,
2205 right: &'a TypedExpr,
2206 ) -> Document<'a, 'doc> {
2207 let left_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| {
2208 this.child_expression(arena, left)
2209 });
2210 let right_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| {
2211 this.child_expression(arena, right)
2212 });
2213
2214 // If we have a constant value divided by zero then it's safe to replace
2215 // it directly with 0.
2216 if left.is_literal() && right.is_zero_compile_time_number() {
2217 "0.0".to_doc(arena)
2218 } else if right.is_non_zero_compile_time_number() {
2219 if let TypedExpr::BinOp { .. } = left {
2220 docvec![
2221 arena,
2222 left_doc.surround(arena, OPEN_PAREN_DOCUMENT, CLOSE_PAREN_DOCUMENT),
2223 SPACE_SLASH_SPACE_DOCUMENT,
2224 right_doc
2225 ]
2226 } else {
2227 docvec![arena, left_doc, SPACE_SLASH_SPACE_DOCUMENT, right_doc]
2228 }
2229 } else {
2230 self.tracker.float_division_used = true;
2231 docvec![
2232 arena,
2233 "divideFloat",
2234 wrap_arguments(arena, [left_doc, right_doc])
2235 ]
2236 }
2237 }
2238
2239 fn equal(
2240 &mut self,
2241 arena: &'doc DocumentArena<'a, 'doc>,
2242 left: &'a TypedExpr,
2243 right: &'a TypedExpr,
2244 should_be_equal: bool,
2245 ) -> Document<'a, 'doc> {
2246 // If it is a simple scalar type then we can use JS' reference identity
2247 if is_js_scalar(left.type_()) {
2248 let left_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| {
2249 this.child_expression(arena, left)
2250 });
2251 let right_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| {
2252 this.child_expression(arena, right)
2253 });
2254 let operator = if should_be_equal {
2255 SPACE_TRIPLE_EQUAL_SPACE_DOCUMENT
2256 } else {
2257 SPACE_EXCLAMATION_MARK_DOUBLE_EQUAL_SPACE_DOCUMENT
2258 };
2259 return docvec![arena, left_doc, operator, right_doc];
2260 }
2261
2262 // For comparison with singleton custom types, ie, one with no fields.
2263 // If you have some code like this
2264 // ```gleam
2265 // pub type Wibble {
2266 // Wibble
2267 // Wobble
2268 // }
2269 //
2270 // pub fn is_wibble(w: Wibble) -> Bool {
2271 // w == Wibble
2272 // }
2273 // ```
2274 // Instead of `isEqual(w, new Wibble())`, generate `w instanceof Wibble`
2275 // because the first approach needs to construct a new Wibble, and then call the isEqual function,
2276 // which supports any shape of data, and so does a lot of extra logic which isn't necessary.
2277
2278 if let Some(doc) = self.singleton_variant_equality(arena, left, right, should_be_equal) {
2279 return doc;
2280 }
2281
2282 if let Some(doc) = self.singleton_variant_equality(arena, right, left, should_be_equal) {
2283 return doc;
2284 }
2285
2286 // Other types must be compared using structural equality
2287 let left = self.not_in_tail_position(Some(Ordering::Strict), |this| {
2288 this.wrap_expression(arena, left)
2289 });
2290 let right = self.not_in_tail_position(Some(Ordering::Strict), |this| {
2291 this.wrap_expression(arena, right)
2292 });
2293
2294 self.prelude_equal_call(arena, should_be_equal, left, right)
2295 }
2296
2297 fn singleton_variant_equality(
2298 &mut self,
2299 arena: &'doc DocumentArena<'a, 'doc>,
2300 left: &'a TypedExpr,
2301 right: &'a TypedExpr,
2302 should_be_equal: bool,
2303 ) -> Option<Document<'a, 'doc>> {
2304 match right {
2305 TypedExpr::Var {
2306 name,
2307 constructor:
2308 ValueConstructor {
2309 variant:
2310 ValueConstructorVariant::Record {
2311 arity: 0,
2312 name: variant_name,
2313 ..
2314 },
2315 ..
2316 },
2317 ..
2318 } => {
2319 let left_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| {
2320 this.wrap_expression(arena, left)
2321 });
2322 Some(self.singleton_equal(
2323 arena,
2324 left_doc,
2325 None,
2326 name.clone(),
2327 should_be_equal,
2328 variant_name.clone(),
2329 right.type_(),
2330 ))
2331 }
2332 TypedExpr::ModuleSelect {
2333 module_alias,
2334 constructor: ModuleValueConstructor::Record { arity: 0, name, .. },
2335 ..
2336 } => {
2337 let left_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| {
2338 this.wrap_expression(arena, left)
2339 });
2340 Some(self.singleton_equal(
2341 arena,
2342 left_doc,
2343 Some(module_alias),
2344 name.clone(),
2345 should_be_equal,
2346 name.clone(),
2347 right.type_(),
2348 ))
2349 }
2350 // Empty lists are implemented as a variant with no fields, so we can
2351 // use `instanceof` for a faster check.
2352 TypedExpr::List { elements, .. } if elements.is_empty() => {
2353 let left_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| {
2354 this.wrap_expression(arena, left)
2355 });
2356 self.tracker.list_empty_class_used = true;
2357 Some(self.singleton_equal(
2358 arena,
2359 left_doc,
2360 None,
2361 "$Empty".into(),
2362 should_be_equal,
2363 "Empty".into(),
2364 right.type_(),
2365 ))
2366 }
2367 TypedExpr::Int { .. }
2368 | TypedExpr::Float { .. }
2369 | TypedExpr::String { .. }
2370 | TypedExpr::Block { .. }
2371 | TypedExpr::Pipeline { .. }
2372 | TypedExpr::Var { .. }
2373 | TypedExpr::Fn { .. }
2374 | TypedExpr::List { .. }
2375 | TypedExpr::Call { .. }
2376 | TypedExpr::BinOp { .. }
2377 | TypedExpr::Case { .. }
2378 | TypedExpr::RecordAccess { .. }
2379 | TypedExpr::PositionalAccess { .. }
2380 | TypedExpr::ModuleSelect { .. }
2381 | TypedExpr::Tuple { .. }
2382 | TypedExpr::TupleIndex { .. }
2383 | TypedExpr::Todo { .. }
2384 | TypedExpr::Panic { .. }
2385 | TypedExpr::Echo { .. }
2386 | TypedExpr::BitArray { .. }
2387 | TypedExpr::RecordUpdate { .. }
2388 | TypedExpr::NegateBool { .. }
2389 | TypedExpr::NegateInt { .. }
2390 | TypedExpr::Invalid { .. } => None,
2391 }
2392 }
2393
2394 #[allow(clippy::too_many_arguments)]
2395 fn singleton_equal(
2396 &mut self,
2397 arena: &'doc DocumentArena<'a, 'doc>,
2398 value: Document<'a, 'doc>,
2399 module: Option<&'a str>,
2400 name: EcoString,
2401 should_be_equal: bool,
2402 variant_name: EcoString,
2403 type_: Arc<Type>,
2404 ) -> Document<'a, 'doc> {
2405 // If we're using this variant unqualified, register it as used so that
2406 // we know to import it. This `instanceof` check only happens if the
2407 // variant has no fields, so we don't need to check that here.
2408 if module.is_none()
2409 && let Some((package, module, type_name)) = type_.named_type_name_and_package()
2410 {
2411 _ = self
2412 .tracker
2413 .variants_used_in_instanceof
2414 .insert(TypeVariant {
2415 package,
2416 module,
2417 type_name,
2418 name: variant_name,
2419 });
2420 }
2421 let record = if let Some(module) = module {
2422 docvec![arena, DOLLAR_DOCUMENT, module, DOT_DOCUMENT, name]
2423 } else {
2424 name.to_doc(arena)
2425 };
2426
2427 if should_be_equal {
2428 docvec![arena, value, SPACE_INSTANCE_OF_SPACE_DOCUMENT, record]
2429 } else {
2430 docvec![
2431 arena,
2432 EXCLAMATION_MARK_OPEN_PAREN_DOCUMENT,
2433 value,
2434 SPACE_INSTANCE_OF_SPACE_DOCUMENT,
2435 record,
2436 CLOSE_PAREN_DOCUMENT
2437 ]
2438 }
2439 }
2440
2441 fn equal_with_doc_operands(
2442 &mut self,
2443 arena: &'doc DocumentArena<'a, 'doc>,
2444 left: Document<'a, 'doc>,
2445 right: Document<'a, 'doc>,
2446 type_: Arc<Type>,
2447 should_be_equal: bool,
2448 ) -> Document<'a, 'doc> {
2449 // If it is a simple scalar type then we can use JS' reference identity
2450 if is_js_scalar(type_) {
2451 let operator = if should_be_equal {
2452 SPACE_TRIPLE_EQUAL_SPACE_DOCUMENT
2453 } else {
2454 SPACE_EXCLAMATION_MARK_DOUBLE_EQUAL_SPACE_DOCUMENT
2455 };
2456 return docvec![arena, left, operator, right];
2457 }
2458
2459 // Other types must be compared using structural equality
2460 self.prelude_equal_call(arena, should_be_equal, left, right)
2461 }
2462
2463 pub(super) fn prelude_equal_call(
2464 &mut self,
2465 arena: &'doc DocumentArena<'a, 'doc>,
2466 should_be_equal: bool,
2467 left: Document<'a, 'doc>,
2468 right: Document<'a, 'doc>,
2469 ) -> Document<'a, 'doc> {
2470 // Record that we need to import the prelude's isEqual function into the module
2471 self.tracker.object_equality_used = true;
2472 // Construct the call
2473 let arguments = wrap_arguments(arena, [left, right]);
2474 let operator = if should_be_equal {
2475 IS_EQUAL_DOCUMENT
2476 } else {
2477 EXCLAMATION_MARK_IS_EQUAL_DOCUMENT
2478 };
2479 docvec![arena, operator, arguments]
2480 }
2481
2482 fn print_bin_op(
2483 &mut self,
2484 arena: &'doc DocumentArena<'a, 'doc>,
2485 left: &'a TypedExpr,
2486 right: &'a TypedExpr,
2487 op: &'a str,
2488 ) -> Document<'a, 'doc> {
2489 let left = self.not_in_tail_position(Some(Ordering::Strict), |this| {
2490 this.child_expression(arena, left)
2491 });
2492 let right = self.not_in_tail_position(Some(Ordering::Strict), |this| {
2493 this.child_expression(arena, right)
2494 });
2495 docvec![arena, left, SPACE_DOCUMENT, op, SPACE_DOCUMENT, right]
2496 }
2497
2498 pub(super) fn bin_op_with_doc_operands(
2499 &mut self,
2500 arena: &'doc DocumentArena<'a, 'doc>,
2501 name: BinOp,
2502 left: Document<'a, 'doc>,
2503 right: Document<'a, 'doc>,
2504 type_: &Arc<Type>,
2505 ) -> Document<'a, 'doc> {
2506 match name {
2507 BinOp::And => docvec![arena, left, SPACE_DOUBLE_AMPERSAND_SPACE_DOCUMENT, right],
2508 BinOp::Or => docvec![arena, left, SPACE_DOUBLE_VERTICAL_BAR_SPACE_DOCUMENT, right],
2509 BinOp::LtInt | BinOp::LtFloat => {
2510 docvec![arena, left, SPACE_LT_INT_SPACE_DOCUMENT, right]
2511 }
2512 BinOp::LtEqInt | BinOp::LtEqFloat => {
2513 docvec![arena, left, SPACE_LT_EQ_INT_SPACE_DOCUMENT, right]
2514 }
2515 BinOp::Eq => self.equal_with_doc_operands(arena, left, right, type_.clone(), true),
2516 BinOp::NotEq => self.equal_with_doc_operands(arena, left, right, type_.clone(), false),
2517 BinOp::GtInt | BinOp::GtFloat => {
2518 docvec![arena, left, SPACE_GT_INT_SPACE_DOCUMENT, right]
2519 }
2520 BinOp::GtEqInt | BinOp::GtEqFloat => {
2521 docvec![arena, left, SPACE_GT_EQ_INT_SPACE_DOCUMENT, right]
2522 }
2523 BinOp::Concatenate | BinOp::AddInt | BinOp::AddFloat => {
2524 docvec![arena, left, SPACE_PLUS_SPACE_DOCUMENT, right]
2525 }
2526 BinOp::SubInt | BinOp::SubFloat => {
2527 docvec![arena, left, SPACE_MINUS_SPACE_DOCUMENT, right]
2528 }
2529 BinOp::MultInt | BinOp::MultFloat => {
2530 docvec![arena, left, SPACE_TIMES_SPACE_DOCUMENT, right]
2531 }
2532 BinOp::RemainderInt => {
2533 self.tracker.int_remainder_used = true;
2534 docvec![
2535 arena,
2536 CAMEL_CASE_REMAINDER_INT_DOCUMENT,
2537 wrap_arguments(arena, [left, right])
2538 ]
2539 }
2540 BinOp::DivInt => {
2541 self.tracker.int_division_used = true;
2542 docvec![
2543 arena,
2544 DIVIDE_INT_DOCUMENT,
2545 wrap_arguments(arena, [left, right])
2546 ]
2547 }
2548 BinOp::DivFloat => {
2549 self.tracker.float_division_used = true;
2550 docvec![
2551 arena,
2552 DIVIDE_FLOAT_DOCUMENT,
2553 wrap_arguments(arena, [left, right])
2554 ]
2555 }
2556 }
2557 }
2558
2559 fn todo(
2560 &mut self,
2561 arena: &'doc DocumentArena<'a, 'doc>,
2562 message: Option<&'a TypedExpr>,
2563 location: &'a SrcSpan,
2564 ) -> Document<'a, 'doc> {
2565 let message = match message {
2566 Some(m) => self.not_in_tail_position(None, |this| this.wrap_expression(arena, m)),
2567 None => string(
2568 arena,
2569 "`todo` expression evaluated. This code has not yet been implemented.",
2570 ),
2571 };
2572 self.throw_error(arena, "todo", &message, *location, vec![])
2573 }
2574
2575 fn panic(
2576 &mut self,
2577 arena: &'doc DocumentArena<'a, 'doc>,
2578 location: &'a SrcSpan,
2579 message: Option<&'a TypedExpr>,
2580 ) -> Document<'a, 'doc> {
2581 let message = match message {
2582 Some(m) => self.not_in_tail_position(None, |this| this.wrap_expression(arena, m)),
2583 None => string(arena, "`panic` expression evaluated."),
2584 };
2585 self.throw_error(arena, "panic", &message, *location, vec![])
2586 }
2587
2588 pub(crate) fn throw_error<Fields>(
2589 &mut self,
2590 arena: &'doc DocumentArena<'a, 'doc>,
2591 error_name: &'a str,
2592 message: &Document<'a, 'doc>,
2593 location: SrcSpan,
2594 fields: Fields,
2595 ) -> Document<'a, 'doc>
2596 where
2597 Fields: IntoIterator<Item = (&'a str, Document<'a, 'doc>)>,
2598 {
2599 self.tracker.make_error_used = true;
2600 let module = self.module_name.clone().to_doc(arena).surround(
2601 arena,
2602 DOUBLE_QUOTE_DOCUMENT,
2603 DOUBLE_QUOTE_DOCUMENT,
2604 );
2605 let function = self.function_name.clone().to_doc(arena).surround(
2606 arena,
2607 DOUBLE_QUOTE_DOCUMENT,
2608 DOUBLE_QUOTE_DOCUMENT,
2609 );
2610 let line = self.line_numbers.line_number(location.start).to_doc(arena);
2611 let fields = wrap_object(
2612 arena,
2613 fields.into_iter().map(|(k, v)| (k.to_doc(arena), Some(v))),
2614 );
2615
2616 docvec![
2617 arena,
2618 self.source_map_tracker(arena, location.start),
2619 THROW_MAKE_ERROR_DOCUMENT,
2620 wrap_arguments(
2621 arena,
2622 [
2623 string(arena, error_name),
2624 FILEPATH_UPPERCASE_DOCUMENT,
2625 module,
2626 line,
2627 function,
2628 *message,
2629 fields
2630 ]
2631 ),
2632 ]
2633 }
2634
2635 fn module_select(
2636 &mut self,
2637 arena: &'doc DocumentArena<'a, 'doc>,
2638 module: &'a str,
2639 label: &'a EcoString,
2640 constructor: &'a ModuleValueConstructor,
2641 ) -> Document<'a, 'doc> {
2642 match constructor {
2643 ModuleValueConstructor::Fn { .. } | ModuleValueConstructor::Constant { .. } => {
2644 docvec![
2645 arena,
2646 DOLLAR_DOCUMENT,
2647 module,
2648 DOT_DOCUMENT,
2649 maybe_escape_identifier(label)
2650 ]
2651 }
2652
2653 ModuleValueConstructor::Record {
2654 name, arity, type_, ..
2655 } => self.record_constructor(arena, type_.clone(), Some(module), name, name, *arity),
2656 }
2657 }
2658
2659 fn echo(
2660 &mut self,
2661 arena: &'doc DocumentArena<'a, 'doc>,
2662 expression: Document<'a, 'doc>,
2663 message: Option<&'a TypedExpr>,
2664 location: &'a SrcSpan,
2665 ) -> Document<'a, 'doc> {
2666 self.tracker.echo_used = true;
2667
2668 let message = match message {
2669 Some(message) => self.not_in_tail_position(Some(Ordering::Strict), |this| {
2670 this.wrap_expression(arena, message)
2671 }),
2672 None => UNDEFINED_DOCUMENT,
2673 };
2674
2675 let echo_arguments = call_arguments(
2676 arena,
2677 vec![
2678 expression,
2679 message,
2680 self.src_path.clone().to_doc(arena),
2681 self.line_numbers.line_number(location.start).to_doc(arena),
2682 ],
2683 );
2684 self.wrap_return(arena, docvec![arena, ECHO_DOCUMENT, echo_arguments])
2685 }
2686
2687 pub(crate) fn constant_expression(
2688 &mut self,
2689 arena: &'doc DocumentArena<'a, 'doc>,
2690 context: Context,
2691 expression: &'a TypedConstant,
2692 ) -> Document<'a, 'doc> {
2693 match expression {
2694 Constant::Int { value, .. } => int(arena, value),
2695 Constant::Float { value, .. } => float(arena, value),
2696 Constant::String { value, .. } => string(arena, value),
2697 Constant::Tuple { elements, .. } => array(
2698 arena,
2699 elements
2700 .iter()
2701 .map(|element| self.constant_expression(arena, context, element)),
2702 ),
2703
2704 Constant::List { elements, tail, .. } => {
2705 if tail.is_none() && elements.is_empty() {
2706 return self.empty_list();
2707 }
2708
2709 self.tracker.list_used = true;
2710 let list = match tail {
2711 // There's no tail in the list, we join all the elements and
2712 // call it a day.
2713 None => list(
2714 arena,
2715 elements
2716 .iter()
2717 .map(|element| self.constant_expression(arena, context, element)),
2718 ),
2719
2720 Some(tail) => match tail.list_elements() {
2721 // There's a tail in the list whose elements are all
2722 // known at compile time. In this case we replace the
2723 // tail with those elements and create a single flat
2724 // list.
2725 Some(tail_elements) => list(
2726 arena,
2727 elements
2728 .iter()
2729 .chain(tail_elements)
2730 .map(|element| self.constant_expression(arena, context, element)),
2731 ),
2732 // There's a tail in the list but we can't really tell
2733 // what its elements are at compile time. This means we
2734 // have to prepend to this list.
2735 None => {
2736 self.tracker.prepend_used = true;
2737 let tail = self.constant_expression(arena, context, tail);
2738 prepend(
2739 arena,
2740 elements.iter().map(|element| {
2741 self.constant_expression(arena, context, element)
2742 }),
2743 tail,
2744 )
2745 }
2746 },
2747 };
2748 match context {
2749 Context::Constant => docvec![arena, PURE_JAVASCRIPT_COMMENT_DOCUMENT, list],
2750 Context::Guard => list,
2751 }
2752 }
2753
2754 Constant::Record { type_, name, .. } if type_.is_bool() && name == "True" => {
2755 TRUE_LOWERCASE_DOCUMENT
2756 }
2757 Constant::Record { type_, name, .. } if type_.is_bool() && name == "False" => {
2758 FALSE_LOWERCASE_DOCUMENT
2759 }
2760 Constant::Record { type_, .. } if type_.is_nil() => UNDEFINED_DOCUMENT,
2761
2762 Constant::Record {
2763 arguments,
2764 module,
2765 name,
2766 type_,
2767 ..
2768 } => {
2769 let tag = expression
2770 .constant_record_tag()
2771 .expect("record without inferred constructor made it to code generation");
2772
2773 if module.is_none() && type_.is_result() {
2774 if tag == "Ok" {
2775 self.tracker.ok_used = true;
2776 } else {
2777 self.tracker.error_used = true;
2778 }
2779 }
2780
2781 // If there's no arguments, then this is either a constructor
2782 // which takes arguments being referenced rather than called,
2783 // or a variant with no fields at all.
2784 if arguments.is_none() {
2785 // If the constructor is not a function that takes arguments,
2786 // it effectively has zero arity.
2787 let arity = type_.fn_arity().unwrap_or(0) as u16;
2788 return self.record_constructor(
2789 arena,
2790 type_.clone(),
2791 module.as_ref().map(|(name, _)| name.as_str()),
2792 &tag,
2793 name,
2794 arity,
2795 );
2796 }
2797
2798 // Otherwise we're always constructing a record! Record updates
2799 // are fully expanded during type checking, so we just need to
2800 // handle the arguments here.
2801 let field_values = arguments
2802 .iter()
2803 .flatten()
2804 .map(|argument| self.constant_expression(arena, context, &argument.value))
2805 .collect_vec();
2806
2807 let constructor = construct_record(
2808 arena,
2809 module.as_ref().map(|(module, _)| module.as_str()),
2810 name,
2811 field_values,
2812 );
2813 match context {
2814 Context::Constant => {
2815 docvec![arena, PURE_JAVASCRIPT_COMMENT_DOCUMENT, constructor]
2816 }
2817 Context::Guard => constructor,
2818 }
2819 }
2820 Constant::BitArray { segments, .. } => {
2821 let bit_array = self.constant_bit_array(arena, segments, context);
2822 match context {
2823 Context::Constant => {
2824 docvec![arena, PURE_JAVASCRIPT_COMMENT_DOCUMENT, bit_array]
2825 }
2826 Context::Guard => bit_array,
2827 }
2828 }
2829
2830 Constant::Var { name, module, .. } => {
2831 match (module, context) {
2832 (None, Context::Guard) => self.local_var(name).to_doc(arena),
2833 (None, Context::Constant) => maybe_escape_identifier(name).to_doc(arena),
2834 (Some((module, _)), _) => {
2835 // JS keywords can be accessed here, but we must escape anyway
2836 // as we escape when exporting such names in the first place,
2837 // and the imported name has to match the exported name.
2838 docvec![
2839 arena,
2840 DOLLAR_DOCUMENT,
2841 module,
2842 DOT_DOCUMENT,
2843 maybe_escape_identifier(name)
2844 ]
2845 }
2846 }
2847 }
2848
2849 Constant::StringConcatenation { left, right, .. } => {
2850 let left = self.constant_expression(arena, context, left);
2851 let right = self.constant_expression(arena, context, right);
2852 docvec![arena, left, SPACE_PLUS_SPACE_DOCUMENT, right]
2853 }
2854
2855 Constant::RecordUpdate { .. } => {
2856 panic!("record updates should not reach code generation")
2857 }
2858 Constant::Todo { .. } => {
2859 panic!("todo constants should not reach code generation")
2860 }
2861 Constant::Invalid { .. } => {
2862 panic!("invalid constants should not reach code generation")
2863 }
2864 }
2865 }
2866
2867 fn constant_bit_array(
2868 &mut self,
2869 arena: &'doc DocumentArena<'a, 'doc>,
2870 segments: &'a [TypedConstantBitArraySegment],
2871 context: Context,
2872 ) -> Document<'a, 'doc> {
2873 self.tracker.bit_array_literal_used = true;
2874 let segments_array = array(
2875 arena,
2876 segments.iter().map(|segment| {
2877 let value = match context {
2878 Context::Constant => self.constant_expression(arena, context, &segment.value),
2879 Context::Guard => self.guard_constant_expression(arena, &segment.value),
2880 };
2881
2882 let details = self.constant_bit_array_segment_details(arena, segment, context);
2883
2884 match details.type_ {
2885 BitArraySegmentType::BitArray => {
2886 if segment.size().is_some() {
2887 self.tracker.bit_array_slice_used = true;
2888 docvec![
2889 arena,
2890 BIT_ARRAY_SLICE_OPEN_PAREN_DOCUMENT,
2891 value,
2892 COMMA_ZERO_COMMA_SPACE,
2893 details.size,
2894 CLOSE_PAREN_DOCUMENT
2895 ]
2896 } else {
2897 value
2898 }
2899 }
2900 BitArraySegmentType::Int => {
2901 match (details.size_value, segment.value.as_ref()) {
2902 (Some(size_value), Constant::Int { int_value, .. })
2903 if size_value <= SAFE_INT_SEGMENT_MAX_SIZE.into()
2904 && (&size_value % BigInt::from(8) == BigInt::ZERO) =>
2905 {
2906 let bytes = bit_array_segment_int_value_to_bytes(
2907 int_value.clone(),
2908 size_value,
2909 segment.endianness(),
2910 );
2911
2912 u8_slice(arena, &bytes)
2913 }
2914
2915 (Some(size_value), _) if size_value == 8.into() => value,
2916
2917 (Some(size_value), _) if size_value <= 0.into() => EMPTY_DOCUMENT,
2918
2919 _ => {
2920 self.tracker.sized_integer_segment_used = true;
2921 let size = details.size;
2922 let is_big = bool(segment.endianness().is_big());
2923 docvec![
2924 arena,
2925 SIZED_INT_OPEN_PAREN_DOCUMENT,
2926 value,
2927 COMMA_SPACE_DOCUMENT,
2928 size,
2929 COMMA_SPACE_DOCUMENT,
2930 is_big,
2931 CLOSE_PAREN_DOCUMENT
2932 ]
2933 }
2934 }
2935 }
2936 BitArraySegmentType::Float => {
2937 self.tracker.float_bit_array_segment_used = true;
2938 let size = details.size;
2939 let is_big = bool(details.endianness.is_big());
2940 docvec![
2941 arena,
2942 SIZED_FLOAT_OPEN_PAREN_DOCUMENT,
2943 value,
2944 COMMA_SPACE_DOCUMENT,
2945 size,
2946 COMMA_SPACE_DOCUMENT,
2947 is_big,
2948 CLOSE_PAREN_DOCUMENT
2949 ]
2950 }
2951 BitArraySegmentType::String(StringEncoding::Utf8) => {
2952 self.tracker.string_bit_array_segment_used = true;
2953 docvec![
2954 arena,
2955 STRING_BITS_OPEN_PAREN_DOCUMENT,
2956 value,
2957 CLOSE_PAREN_DOCUMENT
2958 ]
2959 }
2960 BitArraySegmentType::String(StringEncoding::Utf16) => {
2961 self.tracker.string_utf16_bit_array_segment_used = true;
2962 let is_big = bool(details.endianness.is_big());
2963 docvec![
2964 arena,
2965 STRING_TO_UTF16_OPEN_PAREN_DOCUMENT,
2966 value,
2967 COMMA_SPACE_DOCUMENT,
2968 is_big,
2969 CLOSE_PAREN_DOCUMENT
2970 ]
2971 }
2972 BitArraySegmentType::String(StringEncoding::Utf32) => {
2973 self.tracker.string_utf32_bit_array_segment_used = true;
2974 let is_big = bool(details.endianness.is_big());
2975 docvec![
2976 arena,
2977 STRING_TO_UTF32_OPEN_PAREN_DOCUMENT,
2978 value,
2979 COMMA_SPACE_DOCUMENT,
2980 is_big,
2981 CLOSE_PAREN_DOCUMENT
2982 ]
2983 }
2984 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf8) => {
2985 self.tracker.codepoint_bit_array_segment_used = true;
2986 docvec![
2987 arena,
2988 CODEPOINT_BITS_OPEN_PAREN_DOCUMENT,
2989 value,
2990 CLOSE_PAREN_DOCUMENT
2991 ]
2992 }
2993 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf16) => {
2994 self.tracker.codepoint_utf16_bit_array_segment_used = true;
2995 let is_big = bool(details.endianness.is_big());
2996 docvec![
2997 arena,
2998 CODEPOINT_TO_UTF16_OPEN_PAREN_DOCUMENT,
2999 value,
3000 COMMA_SPACE_DOCUMENT,
3001 is_big,
3002 CLOSE_PAREN_DOCUMENT
3003 ]
3004 }
3005 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf32) => {
3006 self.tracker.codepoint_utf32_bit_array_segment_used = true;
3007 let is_big = bool(details.endianness.is_big());
3008 docvec![
3009 arena,
3010 CODEPOINT_TO_UTF32_OPEN_PAREN_DOCUMENT,
3011 value,
3012 COMMA_SPACE_DOCUMENT,
3013 is_big,
3014 CLOSE_PAREN_DOCUMENT
3015 ]
3016 }
3017 }
3018 }),
3019 );
3020
3021 docvec![
3022 arena,
3023 TO_BIT_ARRAY_OPEN_PAREN_DOCUMENT,
3024 segments_array,
3025 CLOSE_PAREN_DOCUMENT
3026 ]
3027 }
3028
3029 fn constant_bit_array_segment_details(
3030 &mut self,
3031 arena: &'doc DocumentArena<'a, 'doc>,
3032 segment: &'a TypedConstantBitArraySegment,
3033 context: Context,
3034 ) -> BitArraySegmentDetails<'a, 'doc> {
3035 let size = segment.size();
3036 let unit = segment.unit();
3037 let (size_value, size) = match size {
3038 Some(Constant::Int { int_value, .. }) => {
3039 let size_value = int_value * unit;
3040 let size = eco_format!("{size_value}").to_doc(arena);
3041 (Some(size_value), size)
3042 }
3043
3044 Some(size) => {
3045 let mut size = match context {
3046 Context::Constant => self.constant_expression(arena, context, size),
3047 Context::Guard => self.guard_constant_expression(arena, size),
3048 };
3049 if unit != 1 {
3050 size = size.group(arena).append(
3051 arena,
3052 SPACE_TIMES_SPACE_DOCUMENT.append(arena, unit.to_doc(arena)),
3053 );
3054 }
3055
3056 (None, size)
3057 }
3058
3059 None => {
3060 let size_value: usize = if segment.type_.is_int() { 8 } else { 64 };
3061 (Some(BigInt::from(size_value)), size_value.to_doc(arena))
3062 }
3063 };
3064
3065 let type_ = BitArraySegmentType::from_segment(segment);
3066
3067 BitArraySegmentDetails {
3068 type_,
3069 size,
3070 size_value,
3071 endianness: segment.endianness(),
3072 }
3073 }
3074
3075 pub(crate) fn guard(
3076 &mut self,
3077 arena: &'doc DocumentArena<'a, 'doc>,
3078 guard: &'a TypedClauseGuard,
3079 ) -> Document<'a, 'doc> {
3080 match guard {
3081 ClauseGuard::Invalid { .. } => unreachable!("invalid guard made it to code generation"),
3082
3083 ClauseGuard::Block { value, .. } => {
3084 self.guard(arena, value)
3085 .surround(arena, OPEN_PAREN_DOCUMENT, CLOSE_PAREN_DOCUMENT)
3086 }
3087
3088 ClauseGuard::BinaryOperator {
3089 left,
3090 right,
3091 operator,
3092 ..
3093 } => {
3094 let operator = match operator {
3095 BinOp::Eq if is_js_scalar(left.type_()) => TRIPLE_EQUAL_DOCUMENT,
3096 BinOp::NotEq if is_js_scalar(left.type_()) => {
3097 EXCLAMATION_MARK_DOUBLE_EQUAL_DOCUMENT
3098 }
3099 BinOp::Eq | BinOp::NotEq => {
3100 let should_be_equal = *operator == BinOp::Eq;
3101
3102 // Handle singleton equality optimization for guards
3103 if let Some(doc) = self.singleton_variant_guard_equality(
3104 arena,
3105 left,
3106 right,
3107 should_be_equal,
3108 ) {
3109 return doc;
3110 }
3111
3112 if let Some(doc) = self.singleton_variant_guard_equality(
3113 arena,
3114 right,
3115 left,
3116 should_be_equal,
3117 ) {
3118 return doc;
3119 }
3120
3121 let left_doc = self.guard(arena, left);
3122 let right_doc = self.guard(arena, right);
3123 return self.prelude_equal_call(
3124 arena,
3125 should_be_equal,
3126 left_doc,
3127 right_doc,
3128 );
3129 }
3130
3131 BinOp::GtFloat | BinOp::GtInt => GT_INT_DOCUMENT,
3132 BinOp::GtEqFloat | BinOp::GtEqInt => GT_EQ_INT_DOCUMENT,
3133 BinOp::LtFloat | BinOp::LtInt => LT_INT_DOCUMENT,
3134 BinOp::LtEqFloat | BinOp::LtEqInt => LT_EQ_INT_DOCUMENT,
3135
3136 BinOp::AddFloat | BinOp::AddInt | BinOp::Concatenate => ADD_INT_DOCUMENT,
3137 BinOp::SubFloat | BinOp::SubInt => SUB_INT_DOCUMENT,
3138 BinOp::MultFloat | BinOp::MultInt => MULT_INT_DOCUMENT,
3139
3140 BinOp::DivFloat => {
3141 self.tracker.float_division_used = true;
3142
3143 return docvec![
3144 arena,
3145 DIVIDE_FLOAT_DOCUMENT,
3146 wrap_arguments(
3147 arena,
3148 [self.guard(arena, left), self.guard(arena, right)]
3149 )
3150 ];
3151 }
3152
3153 BinOp::DivInt => {
3154 self.tracker.int_division_used = true;
3155 return docvec![
3156 arena,
3157 DIVIDE_INT_DOCUMENT,
3158 wrap_arguments(
3159 arena,
3160 [self.guard(arena, left), self.guard(arena, right)]
3161 )
3162 ];
3163 }
3164
3165 BinOp::RemainderInt => {
3166 self.tracker.int_remainder_used = true;
3167 return docvec![
3168 arena,
3169 CAMEL_CASE_REMAINDER_INT_DOCUMENT,
3170 wrap_arguments(
3171 arena,
3172 [self.guard(arena, left), self.guard(arena, right)]
3173 )
3174 ];
3175 }
3176
3177 BinOp::And => AND_DOCUMENT,
3178 BinOp::Or => OR_DOCUMENT,
3179 };
3180
3181 let left_document = self.wrapped_guard(arena, left);
3182 let right_document = self.wrapped_guard(arena, right);
3183
3184 docvec![
3185 arena,
3186 left_document,
3187 SPACE_DOCUMENT,
3188 operator,
3189 SPACE_DOCUMENT,
3190 right_document
3191 ]
3192 }
3193
3194 ClauseGuard::Var { name, .. } => self.local_var(name).to_doc(arena),
3195
3196 ClauseGuard::TupleIndex { tuple, index, .. } => {
3197 docvec![
3198 arena,
3199 self.guard(arena, tuple,),
3200 OPEN_SQUARE_DOCUMENT,
3201 *index,
3202 CLOSE_SQUARE_DOCUMENT
3203 ]
3204 }
3205
3206 ClauseGuard::FieldAccess {
3207 label, container, ..
3208 } => docvec![
3209 arena,
3210 self.guard(arena, container),
3211 DOT_DOCUMENT,
3212 maybe_escape_property(label)
3213 ],
3214
3215 ClauseGuard::ModuleSelect {
3216 module_alias,
3217 label,
3218 ..
3219 } => docvec![arena, DOLLAR_DOCUMENT, module_alias, DOT_DOCUMENT, label],
3220
3221 ClauseGuard::Not { expression, .. } => {
3222 docvec![
3223 arena,
3224 EXCLAMATION_MARK_DOCUMENT,
3225 self.guard(arena, expression,)
3226 ]
3227 }
3228
3229 ClauseGuard::Constant(constant) => self.guard_constant_expression(arena, constant),
3230 }
3231 }
3232
3233 fn singleton_variant_guard_equality(
3234 &mut self,
3235 arena: &'doc DocumentArena<'a, 'doc>,
3236 left: &'a TypedClauseGuard,
3237 right: &'a TypedClauseGuard,
3238 should_be_equal: bool,
3239 ) -> Option<Document<'a, 'doc>> {
3240 match right {
3241 ClauseGuard::Constant(Constant::Record {
3242 record_constructor: Some(constructor),
3243 module,
3244 name,
3245 ..
3246 }) if let ValueConstructorVariant::Record {
3247 arity: 0,
3248 name: variant_name,
3249 ..
3250 } = &constructor.variant =>
3251 {
3252 let left_doc = self.guard(arena, left);
3253 Some(self.singleton_equal(
3254 arena,
3255 left_doc,
3256 module.as_ref().map(|(module, _)| module.as_str()),
3257 name.clone(),
3258 should_be_equal,
3259 variant_name.clone(),
3260 right.type_(),
3261 ))
3262 }
3263 ClauseGuard::Constant(Constant::List {
3264 elements,
3265 tail: None,
3266 ..
3267 }) if elements.is_empty() => {
3268 let left_doc = self.guard(arena, left);
3269 self.tracker.list_empty_class_used = true;
3270 Some(self.singleton_equal(
3271 arena,
3272 left_doc,
3273 None,
3274 "$Empty".into(),
3275 should_be_equal,
3276 "Empty".into(),
3277 right.type_(),
3278 ))
3279 }
3280 ClauseGuard::Block { .. }
3281 | ClauseGuard::BinaryOperator { .. }
3282 | ClauseGuard::Not { .. }
3283 | ClauseGuard::Var { .. }
3284 | ClauseGuard::TupleIndex { .. }
3285 | ClauseGuard::FieldAccess { .. }
3286 | ClauseGuard::ModuleSelect { .. }
3287 | ClauseGuard::Constant(_)
3288 | ClauseGuard::Invalid { .. } => None,
3289 }
3290 }
3291
3292 fn wrapped_guard(
3293 &mut self,
3294 arena: &'doc DocumentArena<'a, 'doc>,
3295 guard: &'a TypedClauseGuard,
3296 ) -> Document<'a, 'doc> {
3297 match guard {
3298 ClauseGuard::Invalid { .. } => unreachable!("invalid guard made it to code generation"),
3299 ClauseGuard::Var { .. }
3300 | ClauseGuard::TupleIndex { .. }
3301 | ClauseGuard::Constant(_)
3302 | ClauseGuard::Not { .. }
3303 | ClauseGuard::FieldAccess { .. }
3304 | ClauseGuard::Block { .. } => self.guard(arena, guard),
3305
3306 ClauseGuard::BinaryOperator { .. } | ClauseGuard::ModuleSelect { .. } => {
3307 docvec![
3308 arena,
3309 OPEN_PAREN_DOCUMENT,
3310 self.guard(arena, guard),
3311 CLOSE_PAREN_DOCUMENT
3312 ]
3313 }
3314 }
3315 }
3316
3317 fn guard_constant_expression(
3318 &mut self,
3319 arena: &'doc DocumentArena<'a, 'doc>,
3320 expression: &'a TypedConstant,
3321 ) -> Document<'a, 'doc> {
3322 match expression {
3323 Constant::Tuple { elements, .. } => array(
3324 arena,
3325 elements
3326 .iter()
3327 .map(|element| self.guard_constant_expression(arena, element)),
3328 ),
3329
3330 Constant::Record { type_, name, .. } if type_.is_bool() && name == "True" => {
3331 TRUE_LOWERCASE_DOCUMENT
3332 }
3333 Constant::Record { type_, name, .. } if type_.is_bool() && name == "False" => {
3334 FALSE_LOWERCASE_DOCUMENT
3335 }
3336 Constant::Record { type_, .. } if type_.is_nil() => UNDEFINED_DOCUMENT,
3337
3338 Constant::BitArray { segments, .. } => {
3339 self.constant_bit_array(arena, segments, Context::Guard)
3340 }
3341
3342 Constant::Var { name, .. } => self.local_var(name).to_doc(arena),
3343
3344 Constant::Record { .. }
3345 | Constant::Int { .. }
3346 | Constant::Float { .. }
3347 | Constant::String { .. }
3348 | Constant::List { .. }
3349 | Constant::RecordUpdate { .. }
3350 | Constant::StringConcatenation { .. }
3351 | Constant::Todo { .. }
3352 | Constant::Invalid { .. } => {
3353 self.constant_expression(arena, Context::Guard, expression)
3354 }
3355 }
3356 }
3357
3358 pub fn source_map_tracker(
3359 &mut self,
3360 arena: &'doc DocumentArena<'a, 'doc>,
3361 start_index: u32,
3362 ) -> Document<'a, 'doc> {
3363 create_cursor_position_observer(
3364 arena,
3365 &self.source_map_builder,
3366 self.line_numbers,
3367 start_index,
3368 )
3369 }
3370
3371 pub(crate) fn record_constructor(
3372 &mut self,
3373 arena: &'doc DocumentArena<'a, 'doc>,
3374 type_: Arc<Type>,
3375 qualifier: Option<&'a str>,
3376 variant_name: &EcoString,
3377 name: &'a EcoString,
3378 arity: u16,
3379 ) -> Document<'a, 'doc> {
3380 if qualifier.is_none() && type_.is_result_constructor() {
3381 if name == "Ok" {
3382 self.tracker.ok_used = true;
3383 } else if name == "Error" {
3384 self.tracker.error_used = true;
3385 }
3386 }
3387 if type_.is_bool() && name == "True" {
3388 TRUE_LOWERCASE_DOCUMENT
3389 } else if type_.is_bool() {
3390 FALSE_LOWERCASE_DOCUMENT
3391 } else if type_.is_nil() {
3392 UNDEFINED_DOCUMENT
3393 } else if arity == 0
3394 && let Some((package, module, type_name)) = type_.named_type_name_and_package()
3395 {
3396 // If the variant has no fields, return the singleton constant so
3397 // that all values of the variant are the same underlying reference,
3398 // and are faster to compare.
3399 match qualifier {
3400 Some(module) => docvec![
3401 arena,
3402 DOLLAR_DOCUMENT,
3403 module,
3404 DOT_DOCUMENT,
3405 type_name,
3406 DOLLAR_DOCUMENT,
3407 name,
3408 DOLLAR_CONST_DOCUMENT
3409 ],
3410 None => {
3411 if module != self.module_name {
3412 let alias = if name == variant_name {
3413 None
3414 } else {
3415 Some(eco_format!("{type_name}${name}$const"))
3416 };
3417 // Since this constant is an implementation detail and not
3418 // present in Gleam code, we need to track it so that we
3419 // import it, as it doesn't appear directly in the `import`s
3420 // in the source code.
3421 _ = self.tracker.variant_constants_used.insert(
3422 TypeVariant {
3423 package,
3424 module,
3425 type_name: type_name.clone(),
3426 name: variant_name.clone(),
3427 },
3428 alias,
3429 );
3430 }
3431 docvec![
3432 arena,
3433 type_name,
3434 DOLLAR_DOCUMENT,
3435 name,
3436 DOLLAR_CONST_DOCUMENT
3437 ]
3438 }
3439 }
3440 } else {
3441 let vars = (0..arity).map(|i| eco_format!("var{i}").to_doc(arena));
3442 let body = docvec![
3443 arena,
3444 RETURN_SPACE_DOCUMENT,
3445 construct_record(arena, qualifier, name, vars.clone()),
3446 SEMICOLON_DOCUMENT
3447 ];
3448 docvec![
3449 arena,
3450 docvec![
3451 arena,
3452 wrap_arguments(arena, vars),
3453 SPACE_EQUAL_ARROW_SPACE_OPEN_CURLY_DOCUMENT,
3454 BREAKABLE_SPACE_DOCUMENT,
3455 body
3456 ]
3457 .nest(arena, INDENT)
3458 .append(arena, BREAKABLE_SPACE_DOCUMENT)
3459 .group(arena),
3460 CLOSE_CURLY_DOCUMENT,
3461 ]
3462 }
3463 }
3464}
3465
3466#[derive(Clone, Copy)]
3467enum AssertExpression {
3468 Literal,
3469 Expression,
3470 Unevaluated,
3471}
3472
3473impl AssertExpression {
3474 fn from_expression(expression: &TypedExpr) -> Self {
3475 if expression.is_literal() {
3476 Self::Literal
3477 } else {
3478 Self::Expression
3479 }
3480 }
3481}
3482
3483pub fn int<'a, 'doc>(arena: &'doc DocumentArena<'a, 'doc>, value: &'a str) -> Document<'a, 'doc> {
3484 eco_string_int(arena, value.into())
3485}
3486
3487pub fn eco_string_int<'a, 'doc>(
3488 arena: &'doc DocumentArena<'a, 'doc>,
3489 value: EcoString,
3490) -> Document<'a, 'doc> {
3491 let mut out = EcoString::with_capacity(value.len());
3492
3493 if value.starts_with('-') {
3494 out.push('-');
3495 } else if value.starts_with('+') {
3496 out.push('+');
3497 }
3498 let value = value.trim_start_matches(['+', '-'].as_ref());
3499
3500 let value = if value.starts_with("0x") {
3501 out.push_str("0x");
3502 value.trim_start_matches("0x")
3503 } else if value.starts_with("0o") {
3504 out.push_str("0o");
3505 value.trim_start_matches("0o")
3506 } else if value.starts_with("0b") {
3507 out.push_str("0b");
3508 value.trim_start_matches("0b")
3509 } else {
3510 value
3511 };
3512
3513 let value = value.trim_start_matches(['0', '_']);
3514 if value.is_empty() {
3515 out.push('0');
3516 }
3517
3518 out.push_str(value);
3519
3520 out.to_doc(arena)
3521}
3522
3523pub fn float<'a, 'doc>(arena: &'doc DocumentArena<'a, 'doc>, value: &'a str) -> Document<'a, 'doc> {
3524 let mut out = EcoString::with_capacity(value.len());
3525
3526 if value.starts_with('-') {
3527 out.push('-');
3528 } else if value.starts_with('+') {
3529 out.push('+');
3530 }
3531 let value = value.trim_start_matches(['+', '-'].as_ref());
3532
3533 let value = value.trim_start_matches(['0', '_']);
3534 if value.starts_with(['.', 'e', 'E']) {
3535 out.push('0');
3536 }
3537 out.push_str(value);
3538
3539 out.to_doc(arena)
3540}
3541
3542pub fn float_from_value<'a, 'doc>(
3543 arena: &'doc DocumentArena<'a, 'doc>,
3544 value: f64,
3545) -> Document<'a, 'doc> {
3546 if value.is_infinite() {
3547 if value.is_sign_positive() {
3548 INFINITY_DOCUMENT
3549 } else {
3550 MINUS_INFINITY_DOCUMENT
3551 }
3552 } else if value.is_nan() {
3553 // NOTE: this case is probably unnecessary, as this function is only
3554 // invoked with `LiteralFloatValue` values, which cannot be nan.
3555 NAN_DOCUMENT
3556 } else {
3557 value.to_doc(arena)
3558 }
3559}
3560
3561/// The context where the constant expression is used, it might be inside a
3562/// function call, or in the definition of another constant.
3563///
3564/// Based on the context we might want to annotate pure function calls as
3565/// "@__PURE__".
3566///
3567#[derive(Debug, Clone, Copy)]
3568pub enum Context {
3569 Constant,
3570 Guard,
3571}
3572
3573#[derive(Debug)]
3574struct BitArraySegmentDetails<'a, 'doc> {
3575 type_: BitArraySegmentType,
3576 size: Document<'a, 'doc>,
3577 /// The size of the bit array segment stored as a BigInt.
3578 /// This has a value when the segment's size is known at compile time.
3579 size_value: Option<BigInt>,
3580 endianness: Endianness,
3581}
3582
3583#[derive(Debug, Clone, Copy)]
3584enum BitArraySegmentType {
3585 BitArray,
3586 Int,
3587 Float,
3588 String(StringEncoding),
3589 UtfCodepoint(StringEncoding),
3590}
3591
3592impl BitArraySegmentType {
3593 fn from_segment<Value>(segment: &BitArraySegment<Value, Arc<Type>>) -> Self {
3594 if segment.type_.is_int() {
3595 BitArraySegmentType::Int
3596 } else if segment.type_.is_float() {
3597 BitArraySegmentType::Float
3598 } else if segment.type_.is_bit_array() {
3599 BitArraySegmentType::BitArray
3600 } else if segment.type_.is_string() {
3601 let encoding = if segment.has_utf16_option() {
3602 StringEncoding::Utf16
3603 } else if segment.has_utf32_option() {
3604 StringEncoding::Utf32
3605 } else {
3606 StringEncoding::Utf8
3607 };
3608 BitArraySegmentType::String(encoding)
3609 } else if segment.type_.is_utf_codepoint() {
3610 let encoding = if segment.has_utf16_codepoint_option() {
3611 StringEncoding::Utf16
3612 } else if segment.has_utf32_codepoint_option() {
3613 StringEncoding::Utf32
3614 } else {
3615 StringEncoding::Utf8
3616 };
3617 BitArraySegmentType::UtfCodepoint(encoding)
3618 } else {
3619 panic!(
3620 "Invalid bit array segment type reached code generation: {:?}",
3621 segment.type_
3622 );
3623 }
3624 }
3625}
3626
3627pub fn string<'a, 'doc>(
3628 arena: &'doc DocumentArena<'a, 'doc>,
3629 value: &'a str,
3630) -> Document<'a, 'doc> {
3631 if value.contains('\n') {
3632 EcoString::from(value.replace('\n', r"\n"))
3633 .to_doc(arena)
3634 .surround(arena, DOUBLE_QUOTE_DOCUMENT, DOUBLE_QUOTE_DOCUMENT)
3635 } else {
3636 value
3637 .to_doc(arena)
3638 .surround(arena, DOUBLE_QUOTE_DOCUMENT, DOUBLE_QUOTE_DOCUMENT)
3639 }
3640}
3641
3642pub(crate) fn array<'a, 'doc, Elements: IntoIterator<Item = Document<'a, 'doc>>>(
3643 arena: &'doc DocumentArena<'a, 'doc>,
3644 elements: Elements,
3645) -> Document<'a, 'doc> {
3646 let elements = arena.join(elements, COMMA_BREAK_DOCUMENT);
3647 if elements.is_empty() {
3648 // Do not add a trailing comma since that adds an 'undefined' element
3649 OPEN_CLOSE_SQUARE_DOCUMENT
3650 } else {
3651 docvec![
3652 arena,
3653 OPEN_SQUARE_DOCUMENT,
3654 docvec![arena, EMPTY_BREAK_DOCUMENT, elements].nest(arena, INDENT),
3655 TRAILING_COMMA_BREAK_DOCUMENT,
3656 CLOSE_SQUARE_DOCUMENT
3657 ]
3658 .group(arena)
3659 }
3660}
3661
3662pub(crate) fn list<'a, 'doc, I: IntoIterator<Item = Document<'a, 'doc>>>(
3663 arena: &'doc DocumentArena<'a, 'doc>,
3664 elements: I,
3665) -> Document<'a, 'doc>
3666where
3667 I::IntoIter: DoubleEndedIterator,
3668{
3669 let array = array(arena, elements);
3670 docvec![
3671 arena,
3672 TO_LIST_OPEN_PAREN_DOCUMENT,
3673 array,
3674 CLOSE_PAREN_DOCUMENT
3675 ]
3676}
3677
3678fn prepend<'a, 'doc, I: IntoIterator<Item = Document<'a, 'doc>>>(
3679 arena: &'doc DocumentArena<'a, 'doc>,
3680 elements: I,
3681 tail: Document<'a, 'doc>,
3682) -> Document<'a, 'doc>
3683where
3684 I::IntoIter: DoubleEndedIterator + ExactSizeIterator,
3685{
3686 elements.into_iter().rev().fold(tail, |tail, element| {
3687 let arguments = call_arguments(arena, [element, tail]);
3688 docvec![arena, LIST_PREPEND_DOCUMENT, arguments]
3689 })
3690}
3691
3692fn call_arguments<'a, 'doc, Elements: IntoIterator<Item = Document<'a, 'doc>>>(
3693 arena: &'doc DocumentArena<'a, 'doc>,
3694 elements: Elements,
3695) -> Document<'a, 'doc> {
3696 let elements = arena.join(elements, COMMA_BREAK_DOCUMENT);
3697 if elements.is_empty() {
3698 return OPEN_CLOSE_PAREN_DOCUMENT;
3699 }
3700 docvec![
3701 arena,
3702 OPEN_PAREN_DOCUMENT,
3703 docvec![arena, EMPTY_BREAK_DOCUMENT, elements].nest(arena, INDENT),
3704 TRAILING_COMMA_BREAK_DOCUMENT,
3705 CLOSE_PAREN_DOCUMENT
3706 ]
3707 .group(arena)
3708}
3709
3710pub(crate) fn construct_record<'a, 'doc>(
3711 arena: &'doc DocumentArena<'a, 'doc>,
3712 module: Option<&'a str>,
3713 name: &'a str,
3714 arguments: impl IntoIterator<Item = Document<'a, 'doc>>,
3715) -> Document<'a, 'doc> {
3716 let mut any_arguments = false;
3717 let arguments = arena.join(
3718 arguments.into_iter().inspect(|_| {
3719 any_arguments = true;
3720 }),
3721 COMMA_BREAK_DOCUMENT,
3722 );
3723 let arguments = docvec![arena, EMPTY_BREAK_DOCUMENT, arguments].nest(arena, INDENT);
3724 let name = if let Some(module) = module {
3725 docvec![arena, DOLLAR_DOCUMENT, module, DOT_DOCUMENT, name]
3726 } else {
3727 name.to_doc(arena)
3728 };
3729 if any_arguments {
3730 docvec![
3731 arena,
3732 NEW_SPACE_DOCUMENT,
3733 name,
3734 OPEN_PAREN_DOCUMENT,
3735 arguments,
3736 TRAILING_COMMA_BREAK_DOCUMENT,
3737 CLOSE_PAREN_DOCUMENT
3738 ]
3739 .group(arena)
3740 } else {
3741 docvec![arena, NEW_SPACE_DOCUMENT, name, OPEN_CLOSE_PAREN_DOCUMENT]
3742 }
3743}
3744
3745impl TypedExpr {
3746 fn handles_own_return(&self) -> bool {
3747 match self {
3748 TypedExpr::Todo { .. }
3749 | TypedExpr::Call { .. }
3750 | TypedExpr::Case { .. }
3751 | TypedExpr::Panic { .. }
3752 | TypedExpr::Block { .. }
3753 | TypedExpr::Echo { .. }
3754 | TypedExpr::Pipeline { .. }
3755 | TypedExpr::RecordUpdate { .. } => true,
3756
3757 TypedExpr::Int { .. }
3758 | TypedExpr::Float { .. }
3759 | TypedExpr::String { .. }
3760 | TypedExpr::Var { .. }
3761 | TypedExpr::Fn { .. }
3762 | TypedExpr::List { .. }
3763 | TypedExpr::BinOp { .. }
3764 | TypedExpr::RecordAccess { .. }
3765 | TypedExpr::PositionalAccess { .. }
3766 | TypedExpr::ModuleSelect { .. }
3767 | TypedExpr::Tuple { .. }
3768 | TypedExpr::TupleIndex { .. }
3769 | TypedExpr::BitArray { .. }
3770 | TypedExpr::NegateBool { .. }
3771 | TypedExpr::NegateInt { .. }
3772 | TypedExpr::Invalid { .. } => false,
3773 }
3774 }
3775}
3776
3777impl BinOp {
3778 fn is_operator_to_wrap(&self) -> bool {
3779 match self {
3780 BinOp::And
3781 | BinOp::Or
3782 | BinOp::Eq
3783 | BinOp::NotEq
3784 | BinOp::LtInt
3785 | BinOp::LtEqInt
3786 | BinOp::LtFloat
3787 | BinOp::LtEqFloat
3788 | BinOp::GtEqInt
3789 | BinOp::GtInt
3790 | BinOp::GtEqFloat
3791 | BinOp::GtFloat
3792 | BinOp::AddInt
3793 | BinOp::AddFloat
3794 | BinOp::SubInt
3795 | BinOp::SubFloat
3796 | BinOp::MultFloat
3797 | BinOp::DivInt
3798 | BinOp::DivFloat
3799 | BinOp::RemainderInt
3800 | BinOp::Concatenate => true,
3801 BinOp::MultInt => false,
3802 }
3803 }
3804}
3805
3806pub fn is_js_scalar(t: Arc<Type>) -> bool {
3807 t.is_int() || t.is_float() || t.is_bool() || t.is_nil() || t.is_string()
3808}
3809
3810fn expression_requires_semicolon(expression: &TypedExpr) -> bool {
3811 match expression {
3812 TypedExpr::Int { .. }
3813 | TypedExpr::Fn { .. }
3814 | TypedExpr::Var { .. }
3815 | TypedExpr::List { .. }
3816 | TypedExpr::Call { .. }
3817 | TypedExpr::Echo { .. }
3818 | TypedExpr::Float { .. }
3819 | TypedExpr::String { .. }
3820 | TypedExpr::BinOp { .. }
3821 | TypedExpr::Tuple { .. }
3822 | TypedExpr::NegateInt { .. }
3823 | TypedExpr::BitArray { .. }
3824 | TypedExpr::TupleIndex { .. }
3825 | TypedExpr::NegateBool { .. }
3826 | TypedExpr::RecordAccess { .. }
3827 | TypedExpr::PositionalAccess { .. }
3828 | TypedExpr::ModuleSelect { .. } => true,
3829
3830 TypedExpr::Todo { .. }
3831 | TypedExpr::Case { .. }
3832 | TypedExpr::Panic { .. }
3833 | TypedExpr::Pipeline { .. }
3834 | TypedExpr::RecordUpdate { .. }
3835 | TypedExpr::Invalid { .. }
3836 | TypedExpr::Block { .. } => false,
3837 }
3838}
3839
3840/// Wrap a document in an immediately invoked function expression
3841fn immediately_invoked_function_expression_document<'a, 'doc>(
3842 arena: &'doc DocumentArena<'a, 'doc>,
3843 document: Document<'a, 'doc>,
3844) -> Document<'a, 'doc> {
3845 docvec![
3846 arena,
3847 docvec![
3848 arena,
3849 OPEN_PAREN_OPEN_CLOSE_PAREN_EQUAL_ARROW_OPEN_CURLY_DOCUMENT,
3850 BREAKABLE_SPACE_DOCUMENT,
3851 document
3852 ]
3853 .nest(arena, INDENT),
3854 BREAKABLE_SPACE_DOCUMENT,
3855 CLOSE_CURLY_CLOSE_PAREN_OPEN_CLOSE_PAREN_DOCUMENT,
3856 ]
3857 .group(arena)
3858}
3859
3860fn u8_slice<'a, 'doc>(arena: &'doc DocumentArena<'a, 'doc>, bytes: &[u8]) -> Document<'a, 'doc> {
3861 arena.join(
3862 bytes.iter().map(|byte| byte.to_doc(arena)),
3863 COMMA_SPACE_DOCUMENT,
3864 )
3865}