Fork of daniellemaywood.uk/gleam — Wasm codegen work
116 kB
3133 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 pretty::*,
13 type_::{
14 ModuleValueConstructor, Type, TypedCallArg, ValueConstructor, ValueConstructorVariant,
15 },
16};
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 ..Default::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> {
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>>,
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> Generator<'module, 'a> {
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 body: &'a [TypedStatement],
343 arguments: &'a [TypedArg],
344 ) -> Document<'a> {
345 let body = self.statements(body);
346 if self.tail_recursion_used {
347 self.tail_call_loop(body, arguments)
348 } else {
349 body
350 }
351 }
352
353 fn tail_call_loop(&mut self, body: Document<'a>, arguments: &'a [TypedArg]) -> Document<'a> {
354 let loop_assignments = concat(arguments.iter().flat_map(|arg| {
355 arg.get_variable_name().map(|name| {
356 let var = maybe_escape_identifier(name);
357 docvec![
358 self.source_map_tracker(arg.location.start),
359 "let ",
360 var,
361 " = loop$",
362 name,
363 ";",
364 line()
365 ]
366 })
367 }));
368 docvec![
369 "while (true) {",
370 docvec![line(), loop_assignments, body].nest(INDENT),
371 line(),
372 "}"
373 ]
374 }
375
376 fn statement(&mut self, statement: &'a TypedStatement) -> Document<'a> {
377 let expression_doc = match statement {
378 Statement::Expression(expression) => self.expression(expression),
379 Statement::Assignment(assignment) => self.assignment(assignment),
380 Statement::Use(use_) => self.expression(&use_.call),
381 Statement::Assert(assert) => self.assert(assert),
382 };
383 self.add_statement_level(expression_doc)
384 }
385
386 fn add_statement_level(&mut self, expression: Document<'a>) -> Document<'a> {
387 if self.statement_level.is_empty() {
388 expression
389 } else {
390 let mut statements = std::mem::take(&mut self.statement_level);
391 statements.push(expression);
392 join(statements, line())
393 }
394 }
395
396 pub fn expression(&mut self, expression: &'a TypedExpr) -> Document<'a> {
397 let mut document = match expression {
398 TypedExpr::String { value, .. } => string(value),
399
400 TypedExpr::Int { value, .. } => int(value),
401 TypedExpr::Float { float_value, .. } => float_from_value(float_value.value()),
402
403 TypedExpr::List { elements, tail, .. } => {
404 self.not_in_tail_position(Some(Ordering::Strict), |this| match tail {
405 Some(tail) => {
406 this.tracker.prepend_used = true;
407 let tail = this.wrap_expression(tail);
408 prepend(
409 elements.iter().map(|element| this.wrap_expression(element)),
410 tail,
411 )
412 }
413 None if elements.is_empty() => this.empty_list(),
414 None => {
415 this.tracker.list_used = true;
416 list(elements.iter().map(|element| this.wrap_expression(element)))
417 }
418 })
419 }
420
421 TypedExpr::Tuple { elements, .. } => self.tuple(elements),
422 TypedExpr::TupleIndex { tuple, index, .. } => self.tuple_index(tuple, *index),
423
424 TypedExpr::Case {
425 subjects,
426 clauses,
427 compiled_case,
428 ..
429 } => decision::case(compiled_case, clauses, subjects, self),
430
431 TypedExpr::Call { fun, arguments, .. } => self.call(fun, arguments),
432 TypedExpr::Fn {
433 arguments,
434 body,
435 kind,
436 ..
437 } => self.fn_(arguments, body, kind),
438
439 TypedExpr::RecordAccess { record, label, .. } => self.record_access(record, label),
440
441 TypedExpr::PositionalAccess { record, index, .. } => {
442 self.positional_access(record, *index)
443 }
444
445 TypedExpr::RecordUpdate {
446 updated_record_assigned_name,
447 updated_record,
448 constructor,
449 arguments,
450 ..
451 } => self.record_update(
452 updated_record_assigned_name,
453 updated_record,
454 constructor,
455 arguments,
456 ),
457
458 TypedExpr::Var {
459 name, constructor, ..
460 } => self.variable(name, constructor),
461
462 TypedExpr::Pipeline {
463 first_value,
464 assignments,
465 finally,
466 ..
467 } => self.pipeline(first_value, assignments.as_slice(), finally),
468
469 TypedExpr::Block { statements, .. } => self.block(statements),
470
471 TypedExpr::BinOp {
472 operator,
473 left,
474 right,
475 ..
476 } => self.bin_op(operator, left, right),
477
478 TypedExpr::Todo {
479 message, location, ..
480 } => self.todo(message.as_ref().map(|m| &**m), location),
481
482 TypedExpr::Panic {
483 location, message, ..
484 } => self.panic(location, message.as_ref().map(|m| &**m)),
485
486 TypedExpr::BitArray { segments, .. } => self.bit_array(segments),
487
488 TypedExpr::ModuleSelect {
489 module_alias,
490 label,
491 constructor,
492 ..
493 } => self.module_select(module_alias, label, constructor),
494
495 TypedExpr::NegateBool { value, .. } => self.negate_with("!", value),
496
497 TypedExpr::NegateInt { value, .. } => self.negate_with("- ", value),
498
499 TypedExpr::Echo {
500 expression,
501 message,
502 location,
503 ..
504 } => {
505 let expression = expression
506 .as_ref()
507 .expect("echo with no expression outside of pipe");
508 let expresion_doc =
509 self.not_in_tail_position(None, |this| this.wrap_expression(expression));
510 self.echo(expresion_doc, message.as_deref(), location)
511 }
512
513 TypedExpr::Invalid { .. } => {
514 panic!("invalid expressions should not reach code generation")
515 }
516 };
517 if let Position::Statement = self.scope_position
518 && expression_requires_semicolon(expression)
519 {
520 document = document.append(";");
521 }
522 if expression.handles_own_return() {
523 docvec![
524 self.source_map_tracker(expression.location().start),
525 document
526 ]
527 } else {
528 docvec![
529 self.source_map_tracker(expression.location().start),
530 self.wrap_return(document)
531 ]
532 }
533 }
534
535 /// Return the singleton empty list; all empty lists are the same underlying
536 /// reference, which makes comparison faster.
537 fn empty_list(&mut self) -> Document<'static> {
538 self.tracker.list_empty_const_used = true;
539 "$List$Empty$const".to_doc()
540 }
541
542 fn negate_with(&mut self, with: &'static str, value: &'a TypedExpr) -> Document<'a> {
543 self.not_in_tail_position(None, |this| docvec![with, this.wrap_expression(value)])
544 }
545
546 fn bit_array(&mut self, segments: &'a [TypedExprBitArraySegment]) -> Document<'a> {
547 self.tracker.bit_array_literal_used = true;
548
549 // Collect all the values used in segments.
550 let segments_array = array(segments.iter().map(|segment| {
551 let value = self.not_in_tail_position(Some(Ordering::Strict), |this| {
552 this.wrap_expression(&segment.value)
553 });
554
555 let details = self.bit_array_segment_details(segment);
556
557 match details.type_ {
558 BitArraySegmentType::BitArray => {
559 if segment.size().is_some() {
560 self.tracker.bit_array_slice_used = true;
561 docvec!["bitArraySlice(", value, ", 0, ", details.size, ")"]
562 } else {
563 value
564 }
565 }
566 BitArraySegmentType::Int => match (details.size_value, segment.value.as_ref()) {
567 (Some(size_value), TypedExpr::Int { int_value, .. })
568 if size_value <= SAFE_INT_SEGMENT_MAX_SIZE.into()
569 && (&size_value % BigInt::from(8) == BigInt::ZERO) =>
570 {
571 let bytes = bit_array_segment_int_value_to_bytes(
572 int_value.clone(),
573 size_value,
574 segment.endianness(),
575 );
576
577 u8_slice(&bytes)
578 }
579
580 (Some(size_value), _) if size_value == 8.into() => value,
581
582 (Some(size_value), _) if size_value <= 0.into() => nil(),
583
584 _ => {
585 self.tracker.sized_integer_segment_used = true;
586 let size = details.size;
587 let is_big = bool(segment.endianness().is_big());
588 docvec!["sizedInt(", value, ", ", size, ", ", is_big, ")"]
589 }
590 },
591 BitArraySegmentType::Float => {
592 self.tracker.float_bit_array_segment_used = true;
593 let size = details.size;
594 let is_big = bool(details.endianness.is_big());
595 docvec!["sizedFloat(", value, ", ", size, ", ", is_big, ")"]
596 }
597 BitArraySegmentType::String(StringEncoding::Utf8) => {
598 self.tracker.string_bit_array_segment_used = true;
599 docvec!["stringBits(", value, ")"]
600 }
601 BitArraySegmentType::String(StringEncoding::Utf16) => {
602 self.tracker.string_utf16_bit_array_segment_used = true;
603 let is_big = bool(details.endianness.is_big());
604 docvec!["stringToUtf16(", value, ", ", is_big, ")"]
605 }
606 BitArraySegmentType::String(StringEncoding::Utf32) => {
607 self.tracker.string_utf32_bit_array_segment_used = true;
608 let is_big = bool(details.endianness.is_big());
609 docvec!["stringToUtf32(", value, ", ", is_big, ")"]
610 }
611 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf8) => {
612 self.tracker.codepoint_bit_array_segment_used = true;
613 docvec!["codepointBits(", value, ")"]
614 }
615 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf16) => {
616 self.tracker.codepoint_utf16_bit_array_segment_used = true;
617 let is_big = bool(details.endianness.is_big());
618 docvec!["codepointToUtf16(", value, ", ", is_big, ")"]
619 }
620 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf32) => {
621 self.tracker.codepoint_utf32_bit_array_segment_used = true;
622 let is_big = bool(details.endianness.is_big());
623 docvec!["codepointToUtf32(", value, ", ", is_big, ")"]
624 }
625 }
626 }));
627
628 docvec!["toBitArray(", segments_array, ")"]
629 }
630
631 fn bit_array_segment_details(
632 &mut self,
633 segment: &'a TypedExprBitArraySegment,
634 ) -> BitArraySegmentDetails<'a> {
635 let size = segment.size();
636 let unit = segment.unit();
637 let (size_value, size) = match size {
638 Some(TypedExpr::Int { int_value, .. }) => {
639 let size_value = int_value * unit;
640 let size = eco_format!("{}", size_value).to_doc();
641 (Some(size_value), size)
642 }
643 Some(size) => {
644 let mut size = self.not_in_tail_position(Some(Ordering::Strict), |this| {
645 this.wrap_expression(size)
646 });
647
648 if unit != 1 {
649 size = size.group().append(" * ".to_doc().append(unit.to_doc()));
650 }
651
652 (None, size)
653 }
654
655 None => {
656 let size_value: usize = if segment.type_.is_int() { 8 } else { 64 };
657 (Some(BigInt::from(size_value)), docvec![size_value])
658 }
659 };
660
661 let type_ = BitArraySegmentType::from_segment(segment);
662
663 BitArraySegmentDetails {
664 type_,
665 size,
666 size_value,
667 endianness: segment.endianness(),
668 }
669 }
670
671 pub fn wrap_return(&mut self, document: Document<'a>) -> Document<'a> {
672 match &self.scope_position {
673 Position::Tail => docvec!["return ", document, ";"],
674 Position::Expression(_) | Position::Statement => document,
675 Position::Assign(name) => docvec![name.clone(), " = ", document, ";"],
676 }
677 }
678
679 pub fn not_in_tail_position<CompileFn, Output>(
680 &mut self,
681 // If ordering is None, it is inherited from the parent scope.
682 // It will be None in cases like `!x`, where `x` can be lifted
683 // only if the ordering is already loose.
684 ordering: Option<Ordering>,
685 compile: CompileFn,
686 ) -> Output
687 where
688 CompileFn: Fn(&mut Self) -> Output,
689 {
690 let new_ordering = ordering.unwrap_or(self.scope_position.ordering());
691
692 let function_position = std::mem::replace(
693 &mut self.function_position,
694 Position::Expression(new_ordering),
695 );
696 let scope_position =
697 std::mem::replace(&mut self.scope_position, Position::Expression(new_ordering));
698
699 let result = compile(self);
700
701 self.function_position = function_position;
702 self.scope_position = scope_position;
703 result
704 }
705
706 /// Use the `_block` variable if the expression is JS statement.
707 pub fn wrap_expression(&mut self, expression: &'a TypedExpr) -> Document<'a> {
708 match (expression, &self.scope_position) {
709 (_, Position::Tail | Position::Assign(_)) => self.expression(expression),
710 (
711 TypedExpr::Panic { .. }
712 | TypedExpr::Todo { .. }
713 | TypedExpr::Case { .. }
714 | TypedExpr::Pipeline { .. }
715 | TypedExpr::RecordUpdate {
716 // Record updates that assign a variable generate multiple statements
717 updated_record_assigned_name: Some(_),
718 ..
719 },
720 Position::Expression(Ordering::Loose),
721 ) => self.wrap_block(|this| this.expression(expression)),
722 (
723 TypedExpr::Panic { .. }
724 | TypedExpr::Todo { .. }
725 | TypedExpr::Case { .. }
726 | TypedExpr::Pipeline { .. }
727 | TypedExpr::RecordUpdate {
728 // Record updates that assign a variable generate multiple statements
729 updated_record_assigned_name: Some(_),
730 ..
731 },
732 Position::Expression(Ordering::Strict),
733 ) => self.immediately_invoked_function_expression(expression, |this, expr| {
734 this.expression(expr)
735 }),
736 _ => self.expression(expression),
737 }
738 }
739
740 /// Wrap an expression using the `_block` variable if required due to being
741 /// a JS statement, or in parens if required due to being an operator or
742 /// a function literal.
743 pub fn child_expression(&mut self, expression: &'a TypedExpr) -> Document<'a> {
744 match expression {
745 TypedExpr::BinOp { operator, .. } if operator.is_operator_to_wrap() => {}
746 TypedExpr::Fn { .. } => {}
747
748 TypedExpr::Int { .. }
749 | TypedExpr::Float { .. }
750 | TypedExpr::String { .. }
751 | TypedExpr::Block { .. }
752 | TypedExpr::Pipeline { .. }
753 | TypedExpr::Var { .. }
754 | TypedExpr::List { .. }
755 | TypedExpr::Call { .. }
756 | TypedExpr::BinOp { .. }
757 | TypedExpr::Case { .. }
758 | TypedExpr::RecordAccess { .. }
759 | TypedExpr::PositionalAccess { .. }
760 | TypedExpr::ModuleSelect { .. }
761 | TypedExpr::Tuple { .. }
762 | TypedExpr::TupleIndex { .. }
763 | TypedExpr::Todo { .. }
764 | TypedExpr::Panic { .. }
765 | TypedExpr::Echo { .. }
766 | TypedExpr::BitArray { .. }
767 | TypedExpr::RecordUpdate { .. }
768 | TypedExpr::NegateBool { .. }
769 | TypedExpr::NegateInt { .. }
770 | TypedExpr::Invalid { .. } => return self.wrap_expression(expression),
771 }
772
773 let document = self.expression(expression);
774 match &self.scope_position {
775 // Here the document is a return statement: `return <expr>;`
776 // or an assignment: `_block = <expr>;`
777 Position::Tail | Position::Assign(_) | Position::Statement => document,
778 Position::Expression(_) => docvec!["(", document, ")"],
779 }
780 }
781
782 /// Wrap an expression in an immediately invoked function expression
783 fn immediately_invoked_function_expression<T, ToDoc>(
784 &mut self,
785 statements: &'a T,
786 to_doc: ToDoc,
787 ) -> Document<'a>
788 where
789 ToDoc: FnOnce(&mut Self, &'a T) -> Document<'a>,
790 {
791 // Save initial state
792 let scope_position = std::mem::replace(&mut self.scope_position, Position::Tail);
793 let statement_level = std::mem::take(&mut self.statement_level);
794
795 // Set state for in this iife
796 let current_scope = self.current_scope.clone();
797
798 // Generate the expression
799 let result = to_doc(self, statements);
800 let doc = self.add_statement_level(result);
801 let doc = immediately_invoked_function_expression_document(doc);
802
803 // Reset
804 self.current_scope = current_scope;
805 self.scope_position = scope_position;
806 self.statement_level = statement_level;
807
808 self.wrap_return(doc)
809 }
810
811 fn wrap_block<CompileFn>(&mut self, compile: CompileFn) -> Document<'a>
812 where
813 CompileFn: Fn(&mut Self) -> Document<'a>,
814 {
815 let block_variable = self.next_local_var(&BLOCK_VARIABLE.into());
816
817 // Save initial state
818 let scope_position = std::mem::replace(
819 &mut self.scope_position,
820 Position::Assign(block_variable.clone()),
821 );
822 let function_position = std::mem::replace(
823 &mut self.function_position,
824 Position::Expression(Ordering::Strict),
825 );
826
827 // Generate the expression
828 let statement_doc = compile(self);
829
830 // Reset
831 self.scope_position = scope_position;
832 self.function_position = function_position;
833
834 self.statement_level
835 .push(docvec!["let ", block_variable.clone(), ";"]);
836 self.statement_level.push(statement_doc);
837
838 self.wrap_return(block_variable.to_doc())
839 }
840
841 fn variable(&mut self, name: &'a EcoString, constructor: &'a ValueConstructor) -> Document<'a> {
842 match &constructor.variant {
843 ValueConstructorVariant::Record {
844 arity,
845 name: variant_name,
846 ..
847 } => {
848 let type_ = constructor.type_.clone();
849 self.record_constructor(type_, None, variant_name, name, *arity)
850 }
851 ValueConstructorVariant::ModuleFn { .. }
852 | ValueConstructorVariant::ModuleConstant { .. }
853 | ValueConstructorVariant::LocalVariable { .. } => self.local_var(name).to_doc(),
854 }
855 }
856
857 fn pipeline(
858 &mut self,
859 first_value: &'a TypedPipelineAssignment,
860 assignments: &'a [(TypedPipelineAssignment, PipelineAssignmentKind)],
861 finally: &'a TypedExpr,
862 ) -> Document<'a> {
863 let count = assignments.len();
864 let mut documents = Vec::with_capacity((count + 2) * 2);
865
866 let all_assignments = std::iter::once(first_value)
867 .chain(assignments.iter().map(|(assignment, _kind)| assignment));
868
869 let mut latest_local_var: Option<EcoString> = None;
870 for assignment in all_assignments {
871 // An echo in a pipeline won't result in an assignment, instead it
872 // just prints the previous variable assigned in the pipeline.
873 if let TypedExpr::Echo {
874 expression: None,
875 message,
876 location,
877 ..
878 } = assignment.value.as_ref()
879 {
880 documents.push(self.not_in_tail_position(Some(Ordering::Strict), |this| {
881 let var = latest_local_var
882 .as_ref()
883 .expect("echo with no previous step in a pipe");
884 this.echo(var.to_doc(), message.as_deref(), location)
885 }));
886 documents.push(";".to_doc());
887 } else {
888 // Otherwise we assign the intermediate pipe value to a variable.
889 let assignment_document =
890 self.not_in_tail_position(Some(Ordering::Strict), |this| {
891 this.simple_variable_assignment(
892 &assignment.name,
893 &assignment.value,
894 assignment.location,
895 )
896 });
897 documents.push(self.add_statement_level(assignment_document));
898 latest_local_var = Some(self.local_var(&assignment.name));
899 }
900
901 documents.push(line());
902 }
903
904 if let TypedExpr::Echo {
905 expression: None,
906 message,
907 location,
908 ..
909 } = finally
910 {
911 let var = latest_local_var.expect("echo with no previous step in a pipe");
912 documents.push(self.echo(var.to_doc(), message.as_deref(), location));
913 match &self.scope_position {
914 Position::Statement => documents.push(";".to_doc()),
915 Position::Expression(_) | Position::Tail | Position::Assign(_) => {}
916 }
917 } else {
918 let finally_doc = self.expression(finally);
919 documents.push(self.add_statement_level(finally_doc));
920 }
921
922 documents.to_doc().force_break()
923 }
924
925 pub(crate) fn expression_flattening_blocks(
926 &mut self,
927 expression: &'a TypedExpr,
928 ) -> Document<'a> {
929 if let TypedExpr::Block { statements, .. } = expression {
930 self.statements(statements)
931 } else {
932 self.expression(expression)
933 }
934 }
935
936 fn block(&mut self, statements: &'a Vec1<TypedStatement>) -> Document<'a> {
937 if statements.len() == 1 {
938 match statements.first() {
939 Statement::Expression(expression) => return self.child_expression(expression),
940
941 Statement::Assignment(assignment) => match &assignment.kind {
942 AssignmentKind::Let | AssignmentKind::Generated => {
943 return self.child_expression(&assignment.value);
944 }
945 // We can't just return the right-hand side of a `let assert`
946 // assignment; we still need to check that the pattern matches.
947 AssignmentKind::Assert { .. } => {}
948 },
949
950 Statement::Use(use_) => return self.child_expression(&use_.call),
951
952 // Similar to `let assert`, we can't immediately return the value
953 // that is asserted; we have to actually perform the assertion.
954 Statement::Assert(_) => {}
955 }
956 }
957 match &self.scope_position {
958 Position::Tail | Position::Assign(_) | Position::Statement => {
959 self.block_document(statements)
960 }
961 Position::Expression(Ordering::Strict) => self
962 .immediately_invoked_function_expression(statements, |this, statements| {
963 this.statements(statements)
964 }),
965 Position::Expression(Ordering::Loose) => self.wrap_block(|this| {
966 // Save previous scope
967 let current_scope = this.current_scope.clone();
968
969 let document = this.block_document(statements);
970
971 // Restore previous state
972 this.current_scope = current_scope;
973
974 document
975 }),
976 }
977 }
978
979 fn block_document(&mut self, statements: &'a Vec1<TypedStatement>) -> Document<'a> {
980 let statements = self.statements(statements);
981 docvec!["{", docvec![line(), statements].nest(INDENT), line(), "}"]
982 }
983
984 fn statements(&mut self, statements: &'a [TypedStatement]) -> Document<'a> {
985 // If there are any statements that need to be printed at statement level, that's
986 // for an outer scope so we don't want to print them inside this one.
987 let statement_level = std::mem::take(&mut self.statement_level);
988 let count = statements.len();
989 let mut documents = Vec::with_capacity(count * 3);
990 for (i, statement) in statements.iter().enumerate() {
991 if i + 1 < count {
992 let function_position =
993 std::mem::replace(&mut self.function_position, Position::Statement);
994 let scope_position =
995 std::mem::replace(&mut self.scope_position, Position::Statement);
996
997 documents.push(self.statement(statement));
998
999 self.function_position = function_position;
1000 self.scope_position = scope_position;
1001
1002 documents.push(line());
1003 } else {
1004 documents.push(self.statement(statement));
1005 }
1006
1007 // If we've generated code for a statement that always throws, we
1008 // can skip code generation for all the following ones.
1009 if self.let_assert_always_panics {
1010 self.let_assert_always_panics = false;
1011 break;
1012 }
1013 }
1014 self.statement_level = statement_level;
1015 if count == 1 {
1016 documents.to_doc()
1017 } else {
1018 documents.to_doc().force_break()
1019 }
1020 }
1021
1022 fn simple_variable_assignment(
1023 &mut self,
1024 name: &'a EcoString,
1025 value: &'a TypedExpr,
1026 location: SrcSpan,
1027 ) -> Document<'a> {
1028 // Subject must be rendered before the variable for variable numbering
1029 let subject =
1030 self.not_in_tail_position(Some(Ordering::Loose), |this| this.wrap_expression(value));
1031 let js_name = self.next_local_var(name);
1032 let assignment = docvec![
1033 self.source_map_tracker(location.start),
1034 "let ",
1035 js_name.clone(),
1036 " = ",
1037 subject,
1038 ";"
1039 ];
1040 let assignment = match &self.scope_position {
1041 Position::Expression(_) | Position::Statement => assignment,
1042 Position::Tail => docvec![assignment, line(), "return ", js_name, ";"],
1043 Position::Assign(block_variable) => docvec![
1044 assignment,
1045 line(),
1046 block_variable.clone(),
1047 " = ",
1048 js_name,
1049 ";"
1050 ],
1051 };
1052
1053 assignment.force_break()
1054 }
1055
1056 fn assignment(&mut self, assignment: &'a TypedAssignment) -> Document<'a> {
1057 let TypedAssignment {
1058 pattern,
1059 kind,
1060 value,
1061 compiled_case,
1062 location,
1063 annotation: _,
1064 } = assignment;
1065
1066 // In case the pattern is just a variable, we special case it to
1067 // generate just a simple assignment instead of using the decision tree
1068 // for the code generation step.
1069 if let TypedPattern::Variable { name, .. } = pattern {
1070 return self.simple_variable_assignment(name, value, *location);
1071 }
1072
1073 docvec![
1074 self.source_map_tracker(location.start),
1075 decision::let_(compiled_case, value, kind, self, pattern)
1076 ]
1077 }
1078
1079 fn assert(&mut self, assert: &'a TypedAssert) -> Document<'a> {
1080 let TypedAssert {
1081 location,
1082 value,
1083 message,
1084 } = assert;
1085
1086 let message = match message {
1087 Some(message) => {
1088 self.not_in_tail_position(Some(Ordering::Strict), |this| this.expression(message))
1089 }
1090 None => string("Assertion failed."),
1091 };
1092
1093 let check = self.not_in_tail_position(Some(Ordering::Loose), |this| {
1094 this.assert_check(value, &message, *location)
1095 });
1096
1097 match &self.scope_position {
1098 Position::Expression(_) | Position::Statement => check,
1099 Position::Tail | Position::Assign(_) => {
1100 docvec![check, line(), self.wrap_return("undefined".to_doc())]
1101 }
1102 }
1103 }
1104
1105 fn assert_check(
1106 &mut self,
1107 subject: &'a TypedExpr,
1108 message: &Document<'a>,
1109 location: SrcSpan,
1110 ) -> Document<'a> {
1111 let (subject_document, mut fields) = match subject {
1112 TypedExpr::Call { fun, arguments, .. } => {
1113 let argument_variables = arguments
1114 .iter()
1115 .map(|element| {
1116 self.not_in_tail_position(Some(Ordering::Strict), |this| {
1117 this.assign_to_variable(&element.value)
1118 })
1119 })
1120 .collect_vec();
1121 (
1122 self.call_with_doc_arguments(fun, argument_variables.clone()),
1123 vec![
1124 ("kind", string("function_call")),
1125 (
1126 "arguments",
1127 array(argument_variables.into_iter().zip(arguments).map(
1128 |(variable, argument)| {
1129 self.asserted_expression(
1130 AssertExpression::from_expression(&argument.value),
1131 Some(variable),
1132 argument.location(),
1133 )
1134 },
1135 )),
1136 ),
1137 ],
1138 )
1139 }
1140
1141 TypedExpr::BinOp {
1142 operator,
1143 left,
1144 right,
1145 ..
1146 } => {
1147 match operator {
1148 BinOp::And => return self.assert_and(left, right, message, location),
1149 BinOp::Or => return self.assert_or(left, right, message, location),
1150 BinOp::Eq
1151 | BinOp::NotEq
1152 | BinOp::LtInt
1153 | BinOp::LtEqInt
1154 | BinOp::LtFloat
1155 | BinOp::LtEqFloat
1156 | BinOp::GtEqInt
1157 | BinOp::GtInt
1158 | BinOp::GtEqFloat
1159 | BinOp::GtFloat
1160 | BinOp::AddInt
1161 | BinOp::AddFloat
1162 | BinOp::SubInt
1163 | BinOp::SubFloat
1164 | BinOp::MultInt
1165 | BinOp::MultFloat
1166 | BinOp::DivInt
1167 | BinOp::DivFloat
1168 | BinOp::RemainderInt
1169 | BinOp::Concatenate => {}
1170 }
1171
1172 let left_document = self.not_in_tail_position(Some(Ordering::Loose), |this| {
1173 this.assign_to_variable(left)
1174 });
1175 let right_document = self.not_in_tail_position(Some(Ordering::Loose), |this| {
1176 this.assign_to_variable(right)
1177 });
1178
1179 (
1180 self.bin_op_with_doc_operands(
1181 *operator,
1182 left_document.clone(),
1183 right_document.clone(),
1184 &left.type_(),
1185 )
1186 .surround("(", ")"),
1187 vec![
1188 ("kind", string("binary_operator")),
1189 ("operator", string(operator.name())),
1190 (
1191 "left",
1192 self.asserted_expression(
1193 AssertExpression::from_expression(left),
1194 Some(left_document),
1195 left.location(),
1196 ),
1197 ),
1198 (
1199 "right",
1200 self.asserted_expression(
1201 AssertExpression::from_expression(right),
1202 Some(right_document),
1203 right.location(),
1204 ),
1205 ),
1206 ],
1207 )
1208 }
1209
1210 TypedExpr::Int { .. }
1211 | TypedExpr::Float { .. }
1212 | TypedExpr::String { .. }
1213 | TypedExpr::Block { .. }
1214 | TypedExpr::Pipeline { .. }
1215 | TypedExpr::Var { .. }
1216 | TypedExpr::Fn { .. }
1217 | TypedExpr::List { .. }
1218 | TypedExpr::Case { .. }
1219 | TypedExpr::RecordAccess { .. }
1220 | TypedExpr::PositionalAccess { .. }
1221 | TypedExpr::ModuleSelect { .. }
1222 | TypedExpr::Tuple { .. }
1223 | TypedExpr::TupleIndex { .. }
1224 | TypedExpr::Todo { .. }
1225 | TypedExpr::Panic { .. }
1226 | TypedExpr::Echo { .. }
1227 | TypedExpr::BitArray { .. }
1228 | TypedExpr::RecordUpdate { .. }
1229 | TypedExpr::NegateBool { .. }
1230 | TypedExpr::NegateInt { .. }
1231 | TypedExpr::Invalid { .. } => (
1232 self.wrap_expression(subject),
1233 vec![
1234 ("kind", string("expression")),
1235 (
1236 "expression",
1237 self.asserted_expression(
1238 AssertExpression::from_expression(subject),
1239 Some("false".to_doc()),
1240 subject.location(),
1241 ),
1242 ),
1243 ],
1244 ),
1245 };
1246
1247 fields.push(("start", location.start.to_doc()));
1248 fields.push(("end", subject.location().end.to_doc()));
1249 fields.push(("expression_start", subject.location().start.to_doc()));
1250
1251 docvec![
1252 self.source_map_tracker(location.start),
1253 "if (",
1254 docvec!["!", subject_document].nest(INDENT),
1255 break_("", ""),
1256 ") {",
1257 docvec![
1258 line(),
1259 self.throw_error("assert", message, location, fields),
1260 ]
1261 .nest(INDENT),
1262 line(),
1263 "}",
1264 ]
1265 .group()
1266 }
1267
1268 fn negate_bool_expression(&mut self, value: &'a TypedExpr) -> Document<'a> {
1269 match value {
1270 TypedExpr::BinOp {
1271 operator,
1272 left,
1273 right,
1274 ..
1275 } => match operator {
1276 BinOp::And => self.print_bin_op(left, right, "||"),
1277 BinOp::Or => self.print_bin_op(left, right, "&&"),
1278 BinOp::Eq => self.equal(left, right, false),
1279 BinOp::NotEq => self.equal(left, right, true),
1280 BinOp::LtInt | BinOp::LtFloat => self.print_bin_op(left, right, ">="),
1281 BinOp::LtEqInt | BinOp::LtEqFloat => self.print_bin_op(left, right, ">"),
1282 BinOp::GtInt | BinOp::GtFloat => self.print_bin_op(left, right, "<="),
1283 BinOp::GtEqInt | BinOp::GtEqFloat => self.print_bin_op(left, right, "<"),
1284 BinOp::AddInt
1285 | BinOp::AddFloat
1286 | BinOp::SubInt
1287 | BinOp::SubFloat
1288 | BinOp::MultInt
1289 | BinOp::MultFloat
1290 | BinOp::DivInt
1291 | BinOp::DivFloat
1292 | BinOp::RemainderInt
1293 | BinOp::Concatenate => unreachable!("type checking should make this impossible"),
1294 },
1295 TypedExpr::NegateBool { value, .. } => self.wrap_expression(value),
1296 TypedExpr::Int { .. }
1297 | TypedExpr::Float { .. }
1298 | TypedExpr::String { .. }
1299 | TypedExpr::Block { .. }
1300 | TypedExpr::Pipeline { .. }
1301 | TypedExpr::Var { .. }
1302 | TypedExpr::Fn { .. }
1303 | TypedExpr::List { .. }
1304 | TypedExpr::Call { .. }
1305 | TypedExpr::Case { .. }
1306 | TypedExpr::RecordAccess { .. }
1307 | TypedExpr::PositionalAccess { .. }
1308 | TypedExpr::ModuleSelect { .. }
1309 | TypedExpr::Tuple { .. }
1310 | TypedExpr::TupleIndex { .. }
1311 | TypedExpr::Todo { .. }
1312 | TypedExpr::Panic { .. }
1313 | TypedExpr::Echo { .. }
1314 | TypedExpr::BitArray { .. }
1315 | TypedExpr::RecordUpdate { .. }
1316 | TypedExpr::NegateInt { .. }
1317 | TypedExpr::Invalid { .. } => docvec!["!", self.wrap_expression(value)],
1318 }
1319 }
1320
1321 /// In Gleam, the `&&` operator is short-circuiting, meaning that we can't
1322 /// pre-evaluate both sides of it, and use them in the exception that is
1323 /// thrown.
1324 /// Instead, we need to implement this short-circuiting logic ourself.
1325 ///
1326 /// If we short-circuit, we must leave the second expression unevaluated,
1327 /// and signal that using the `unevaluated` variant, as detailed in the
1328 /// exception format. For the first expression, we know it must be `false`,
1329 /// otherwise we would have continued by evaluating the second expression.
1330 ///
1331 /// Similarly, if we do evaluate the second expression and fail, we know
1332 /// that the first expression must have evaluated to `true`, and the second
1333 /// to `false`. This way, we avoid needing to evaluate either expression
1334 /// twice.
1335 ///
1336 /// The generated code then looks something like this:
1337 /// ```javascript
1338 /// if (expr1) {
1339 /// if (!expr2) {
1340 /// <throw exception>
1341 /// }
1342 /// } else {
1343 /// <throw exception>
1344 /// }
1345 /// ```
1346 ///
1347 fn assert_and(
1348 &mut self,
1349 left: &'a TypedExpr,
1350 right: &'a TypedExpr,
1351 message: &Document<'a>,
1352 location: SrcSpan,
1353 ) -> Document<'a> {
1354 let left_kind = AssertExpression::from_expression(left);
1355 let right_kind = AssertExpression::from_expression(right);
1356
1357 let fields_if_short_circuiting = vec![
1358 ("kind", string("binary_operator")),
1359 ("operator", string("&&")),
1360 (
1361 "left",
1362 self.asserted_expression(left_kind, Some("false".to_doc()), left.location()),
1363 ),
1364 (
1365 "right",
1366 self.asserted_expression(AssertExpression::Unevaluated, None, right.location()),
1367 ),
1368 ("start", location.start.to_doc()),
1369 ("end", right.location().end.to_doc()),
1370 ("expression_start", left.location().start.to_doc()),
1371 ];
1372
1373 let fields = vec![
1374 ("kind", string("binary_operator")),
1375 ("operator", string("&&")),
1376 (
1377 "left",
1378 self.asserted_expression(left_kind, Some("true".to_doc()), left.location()),
1379 ),
1380 (
1381 "right",
1382 self.asserted_expression(right_kind, Some("false".to_doc()), right.location()),
1383 ),
1384 ("start", location.start.to_doc()),
1385 ("end", right.location().end.to_doc()),
1386 ("expression_start", left.location().start.to_doc()),
1387 ];
1388
1389 let left_value =
1390 self.not_in_tail_position(Some(Ordering::Loose), |this| this.wrap_expression(left));
1391
1392 let right_value = self.not_in_tail_position(Some(Ordering::Strict), |this| {
1393 this.negate_bool_expression(right)
1394 });
1395
1396 let right_check = docvec![
1397 line(),
1398 "if (",
1399 right_value.nest(INDENT),
1400 ") {",
1401 docvec![
1402 line(),
1403 self.throw_error("assert", message, location, fields)
1404 ]
1405 .nest(INDENT),
1406 line(),
1407 "}",
1408 ];
1409
1410 docvec![
1411 self.source_map_tracker(location.start),
1412 "if (",
1413 left_value.nest(INDENT),
1414 ") {",
1415 right_check.nest(INDENT),
1416 line(),
1417 "} else {",
1418 docvec![
1419 line(),
1420 self.throw_error("assert", message, location, fields_if_short_circuiting)
1421 ]
1422 .nest(INDENT),
1423 line(),
1424 "}"
1425 ]
1426 }
1427
1428 /// Similar to `&&`, `||` is also short-circuiting in Gleam. However, if `||`
1429 /// short-circuits, that's because the first expression evaluated to `true`,
1430 /// meaning the whole assertion succeeds. This allows us to directly use the
1431 /// `||` operator in JavaScript.
1432 ///
1433 /// The only difference is that due to the nature of `||`, if the assertion fails,
1434 /// we know that both sides must have evaluated to `false`, so we don't
1435 /// need to store the values of them in variables beforehand.
1436 fn assert_or(
1437 &mut self,
1438 left: &'a TypedExpr,
1439 right: &'a TypedExpr,
1440 message: &Document<'a>,
1441 location: SrcSpan,
1442 ) -> Document<'a> {
1443 let fields = vec![
1444 ("kind", string("binary_operator")),
1445 ("operator", string("||")),
1446 (
1447 "left",
1448 self.asserted_expression(
1449 AssertExpression::from_expression(left),
1450 Some("false".to_doc()),
1451 left.location(),
1452 ),
1453 ),
1454 (
1455 "right",
1456 self.asserted_expression(
1457 AssertExpression::from_expression(right),
1458 Some("false".to_doc()),
1459 right.location(),
1460 ),
1461 ),
1462 ("start", location.start.to_doc()),
1463 ("end", right.location().end.to_doc()),
1464 ("expression_start", left.location().start.to_doc()),
1465 ];
1466
1467 let left_value =
1468 self.not_in_tail_position(Some(Ordering::Loose), |this| this.child_expression(left));
1469
1470 let right_value =
1471 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right));
1472
1473 docvec![
1474 line(),
1475 self.source_map_tracker(location.start),
1476 "if (",
1477 docvec!["!(", left_value, " || ", right_value, ")"].nest(INDENT),
1478 ") {",
1479 docvec![
1480 line(),
1481 self.throw_error("assert", message, location, fields)
1482 ]
1483 .nest(INDENT),
1484 line(),
1485 "}",
1486 ]
1487 }
1488
1489 fn assign_to_variable(&mut self, value: &'a TypedExpr) -> Document<'a> {
1490 if let TypedExpr::Var { .. } = value {
1491 self.expression(value)
1492 } else {
1493 let value = self.wrap_expression(value);
1494 let variable = self.next_local_var(&ASSIGNMENT_VAR.into());
1495 let assignment = docvec!["let ", variable.clone(), " = ", value, ";"];
1496 self.statement_level.push(assignment);
1497 variable.to_doc()
1498 }
1499 }
1500
1501 fn asserted_expression(
1502 &mut self,
1503 kind: AssertExpression,
1504 value: Option<Document<'a>>,
1505 location: SrcSpan,
1506 ) -> Document<'a> {
1507 let kind = match kind {
1508 AssertExpression::Literal => string("literal"),
1509 AssertExpression::Expression => string("expression"),
1510 AssertExpression::Unevaluated => string("unevaluated"),
1511 };
1512
1513 let start = location.start.to_doc();
1514 let end = location.end.to_doc();
1515 let items = if let Some(value) = value {
1516 vec![
1517 ("kind", kind),
1518 ("value", value),
1519 ("start", start),
1520 ("end", end),
1521 ]
1522 } else {
1523 vec![("kind", kind), ("start", start), ("end", end)]
1524 };
1525
1526 wrap_object(
1527 items
1528 .into_iter()
1529 .map(|(key, value)| (key.to_doc(), Some(value))),
1530 )
1531 }
1532
1533 fn tuple(&mut self, elements: &'a [TypedExpr]) -> Document<'a> {
1534 self.not_in_tail_position(Some(Ordering::Strict), |this| {
1535 array(elements.iter().map(|element| this.wrap_expression(element)))
1536 })
1537 }
1538
1539 fn call(&mut self, fun: &'a TypedExpr, arguments: &'a [TypedCallArg]) -> Document<'a> {
1540 let arguments = arguments
1541 .iter()
1542 .map(|element| {
1543 self.not_in_tail_position(Some(Ordering::Strict), |this| {
1544 this.wrap_expression(&element.value)
1545 })
1546 })
1547 .collect_vec();
1548
1549 self.call_with_doc_arguments(fun, arguments)
1550 }
1551
1552 fn call_with_doc_arguments(
1553 &mut self,
1554 fun: &'a TypedExpr,
1555 arguments: Vec<Document<'a>>,
1556 ) -> Document<'a> {
1557 match fun {
1558 // Qualified record construction
1559 TypedExpr::ModuleSelect {
1560 constructor: ModuleValueConstructor::Record { name, .. },
1561 module_alias,
1562 ..
1563 } => self.wrap_return(construct_record(Some(module_alias), name, arguments)),
1564
1565 // Record construction
1566 TypedExpr::Var {
1567 constructor:
1568 ValueConstructor {
1569 variant: ValueConstructorVariant::Record { .. },
1570 type_,
1571 ..
1572 },
1573 name,
1574 ..
1575 } => {
1576 if type_.is_result_constructor() {
1577 if name == "Ok" {
1578 self.tracker.ok_used = true;
1579 } else if name == "Error" {
1580 self.tracker.error_used = true;
1581 }
1582 }
1583 self.wrap_return(construct_record(None, name, arguments))
1584 }
1585
1586 // Tail call optimisation. If we are calling the current function
1587 // and we are in tail position we can avoid creating a new stack
1588 // frame, enabling recursion with constant memory usage.
1589 TypedExpr::Var { name, .. }
1590 if self.function_name == *name
1591 && self.current_function.can_recurse()
1592 && self.function_position.is_tail()
1593 && self.current_scope.counter(name) == Some(0) =>
1594 {
1595 let mut docs = Vec::with_capacity(arguments.len() * 4);
1596 // Record that tail recursion is happening so that we know to
1597 // render the loop at the top level of the function.
1598 self.tail_recursion_used = true;
1599
1600 for (i, (element, argument)) in arguments
1601 .into_iter()
1602 .zip(&self.function_arguments)
1603 .enumerate()
1604 {
1605 if i != 0 {
1606 docs.push(line());
1607 }
1608 // Create an assignment for each variable created by the function arguments
1609 if let Some(name) = argument {
1610 docs.push("loop$".to_doc());
1611 docs.push(name.to_doc());
1612 docs.push(" = ".to_doc());
1613 }
1614 // Render the value given to the function. Even if it is not
1615 // assigned we still render it because the expression may
1616 // have some side effects.
1617 docs.push(element);
1618 docs.push(";".to_doc());
1619 }
1620 docs.to_doc()
1621 }
1622
1623 TypedExpr::Int { .. }
1624 | TypedExpr::Float { .. }
1625 | TypedExpr::String { .. }
1626 | TypedExpr::Block { .. }
1627 | TypedExpr::Pipeline { .. }
1628 | TypedExpr::Var { .. }
1629 | TypedExpr::Fn { .. }
1630 | TypedExpr::List { .. }
1631 | TypedExpr::Call { .. }
1632 | TypedExpr::BinOp { .. }
1633 | TypedExpr::Case { .. }
1634 | TypedExpr::RecordAccess { .. }
1635 | TypedExpr::PositionalAccess { .. }
1636 | TypedExpr::ModuleSelect { .. }
1637 | TypedExpr::Tuple { .. }
1638 | TypedExpr::TupleIndex { .. }
1639 | TypedExpr::Todo { .. }
1640 | TypedExpr::Panic { .. }
1641 | TypedExpr::Echo { .. }
1642 | TypedExpr::BitArray { .. }
1643 | TypedExpr::RecordUpdate { .. }
1644 | TypedExpr::NegateBool { .. }
1645 | TypedExpr::NegateInt { .. }
1646 | TypedExpr::Invalid { .. } => {
1647 let fun = self.not_in_tail_position(None, |this| -> Document<'_> {
1648 let is_fn_literal = matches!(fun, TypedExpr::Fn { .. });
1649 let fun = this.wrap_expression(fun);
1650 if is_fn_literal {
1651 docvec!["(", fun, ")"]
1652 } else {
1653 fun
1654 }
1655 });
1656 let arguments = call_arguments(arguments);
1657 self.wrap_return(docvec![fun, arguments])
1658 }
1659 }
1660 }
1661
1662 fn fn_(
1663 &mut self,
1664 arguments: &'a [TypedArg],
1665 body: &'a [TypedStatement],
1666 kind: &FunctionLiteralKind,
1667 ) -> Document<'a> {
1668 // New function, this is now the tail position
1669 let function_position = std::mem::replace(&mut self.function_position, Position::Tail);
1670 let scope_position = std::mem::replace(&mut self.scope_position, Position::Tail);
1671
1672 // And there's a new scope
1673 let scope = self.current_scope.clone();
1674 for name in arguments.iter().flat_map(Arg::get_variable_name) {
1675 self.current_scope.set_counter(name, 0);
1676 }
1677
1678 // This is a new function so track that so that we don't
1679 // mistakenly trigger tail call optimisation
1680 let mut current_function = CurrentFunction::Anonymous;
1681 std::mem::swap(&mut self.current_function, &mut current_function);
1682
1683 // Generate the function body
1684 let result = self.statements(body);
1685
1686 // Reset function name, scope, and tail position tracking
1687 self.function_position = function_position;
1688 self.scope_position = scope_position;
1689 self.current_scope = scope;
1690 std::mem::swap(&mut self.current_function, &mut current_function);
1691
1692 let mut docs = docvec![];
1693
1694 // If the function is a use then we need to add a source map tracker
1695 // before the result to denote that the function is created by the use
1696 if let FunctionLiteralKind::Use { location } = kind {
1697 docs = docs.append(self.source_map_tracker(location.start));
1698 }
1699 docs = docs.append(fun_arguments(arguments, false));
1700 docs = docs.append(" => {".to_doc());
1701 docs = docs.append(break_("", " "));
1702 docs = docs.append(result);
1703
1704 docvec![docs.nest(INDENT).append(break_("", " ")).group(), "}",]
1705 }
1706
1707 fn record_access(&mut self, record: &'a TypedExpr, label: &'a str) -> Document<'a> {
1708 self.not_in_tail_position(None, |this| {
1709 let record = this.wrap_expression(record);
1710 docvec![record, ".", maybe_escape_property(label)]
1711 })
1712 }
1713
1714 fn positional_access(&mut self, record: &'a TypedExpr, index: u64) -> Document<'a> {
1715 self.not_in_tail_position(None, |this| {
1716 let record = this.wrap_expression(record);
1717 docvec![record, "[", index, "]"]
1718 })
1719 }
1720
1721 fn record_update(
1722 &mut self,
1723 updated_record_assigned_name: &'a Option<EcoString>,
1724 updated_record: &'a TypedExpr,
1725 constructor: &'a TypedExpr,
1726 arguments: &'a [TypedCallArg],
1727 ) -> Document<'a> {
1728 match updated_record_assigned_name.as_ref() {
1729 Some(name) => {
1730 docvec![
1731 self.not_in_tail_position(None, |this| this.simple_variable_assignment(
1732 name,
1733 updated_record,
1734 updated_record.location(),
1735 )),
1736 line(),
1737 self.call(constructor, arguments),
1738 ]
1739 }
1740 None => self.call(constructor, arguments),
1741 }
1742 }
1743
1744 fn tuple_index(&mut self, tuple: &'a TypedExpr, index: u64) -> Document<'a> {
1745 self.not_in_tail_position(None, |this| {
1746 let tuple = this.wrap_expression(tuple);
1747 docvec![tuple, eco_format!("[{index}]")]
1748 })
1749 }
1750
1751 fn bin_op(
1752 &mut self,
1753 name: &'a BinOp,
1754 left: &'a TypedExpr,
1755 right: &'a TypedExpr,
1756 ) -> Document<'a> {
1757 match name {
1758 BinOp::And => self.print_bin_op(left, right, "&&"),
1759 BinOp::Or => self.print_bin_op(left, right, "||"),
1760 BinOp::LtInt | BinOp::LtFloat => self.print_bin_op(left, right, "<"),
1761 BinOp::LtEqInt | BinOp::LtEqFloat => self.print_bin_op(left, right, "<="),
1762 BinOp::Eq => self.equal(left, right, true),
1763 BinOp::NotEq => self.equal(left, right, false),
1764 BinOp::GtInt | BinOp::GtFloat => self.print_bin_op(left, right, ">"),
1765 BinOp::GtEqInt | BinOp::GtEqFloat => self.print_bin_op(left, right, ">="),
1766 BinOp::Concatenate | BinOp::AddInt | BinOp::AddFloat => {
1767 self.print_bin_op(left, right, "+")
1768 }
1769 BinOp::SubInt | BinOp::SubFloat => self.print_bin_op(left, right, "-"),
1770 BinOp::MultInt | BinOp::MultFloat => self.print_bin_op(left, right, "*"),
1771 BinOp::RemainderInt => self.remainder_int(left, right),
1772 BinOp::DivInt => self.div_int(left, right),
1773 BinOp::DivFloat => self.div_float(left, right),
1774 }
1775 }
1776
1777 fn div_int(&mut self, left: &'a TypedExpr, right: &'a TypedExpr) -> Document<'a> {
1778 let left_doc =
1779 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left));
1780 let right_doc =
1781 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right));
1782
1783 // If we have a constant value divided by zero then it's safe to replace
1784 // it directly with 0.
1785 if left.is_literal() && right.is_zero_compile_time_number() {
1786 "0".to_doc()
1787 } else if right.is_non_zero_compile_time_number() {
1788 let division = if let TypedExpr::BinOp { .. } = left {
1789 docvec![left_doc.surround("(", ")"), " / ", right_doc]
1790 } else {
1791 docvec![left_doc, " / ", right_doc]
1792 };
1793 docvec!["globalThis.Math.trunc", wrap_arguments([division])]
1794 } else {
1795 self.tracker.int_division_used = true;
1796 docvec!["divideInt", wrap_arguments([left_doc, right_doc])]
1797 }
1798 }
1799
1800 fn remainder_int(&mut self, left: &'a TypedExpr, right: &'a TypedExpr) -> Document<'a> {
1801 let left_doc =
1802 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left));
1803 let right_doc =
1804 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right));
1805
1806 // If we have a constant value divided by zero then it's safe to replace
1807 // it directly with 0.
1808 if left.is_literal() && right.is_zero_compile_time_number() {
1809 "0".to_doc()
1810 } else if right.is_non_zero_compile_time_number() {
1811 if let TypedExpr::BinOp { .. } = left {
1812 docvec![left_doc.surround("(", ")"), " % ", right_doc]
1813 } else {
1814 docvec![left_doc, " % ", right_doc]
1815 }
1816 } else {
1817 self.tracker.int_remainder_used = true;
1818 docvec!["remainderInt", wrap_arguments([left_doc, right_doc])]
1819 }
1820 }
1821
1822 fn div_float(&mut self, left: &'a TypedExpr, right: &'a TypedExpr) -> Document<'a> {
1823 let left_doc =
1824 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left));
1825 let right_doc =
1826 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right));
1827
1828 // If we have a constant value divided by zero then it's safe to replace
1829 // it directly with 0.
1830 if left.is_literal() && right.is_zero_compile_time_number() {
1831 "0.0".to_doc()
1832 } else if right.is_non_zero_compile_time_number() {
1833 if let TypedExpr::BinOp { .. } = left {
1834 docvec![left_doc.surround("(", ")"), " / ", right_doc]
1835 } else {
1836 docvec![left_doc, " / ", right_doc]
1837 }
1838 } else {
1839 self.tracker.float_division_used = true;
1840 docvec!["divideFloat", wrap_arguments([left_doc, right_doc])]
1841 }
1842 }
1843
1844 fn equal(
1845 &mut self,
1846 left: &'a TypedExpr,
1847 right: &'a TypedExpr,
1848 should_be_equal: bool,
1849 ) -> Document<'a> {
1850 // If it is a simple scalar type then we can use JS' reference identity
1851 if is_js_scalar(left.type_()) {
1852 let left_doc = self
1853 .not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left));
1854 let right_doc = self
1855 .not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right));
1856 let operator = if should_be_equal { " === " } else { " !== " };
1857 return docvec![left_doc, operator, right_doc];
1858 }
1859
1860 // For comparison with singleton custom types, ie, one with no fields.
1861 // If you have some code like this
1862 // ```gleam
1863 // pub type Wibble {
1864 // Wibble
1865 // Wobble
1866 // }
1867 //
1868 // pub fn is_wibble(w: Wibble) -> Bool {
1869 // w == Wibble
1870 // }
1871 // ```
1872 // Instead of `isEqual(w, new Wibble())`, generate `w instanceof Wibble`
1873 // because the first approach needs to construct a new Wibble, and then call the isEqual function,
1874 // which supports any shape of data, and so does a lot of extra logic which isn't necessary.
1875
1876 if let Some(doc) = self.singleton_variant_equality(left, right, should_be_equal) {
1877 return doc;
1878 }
1879
1880 if let Some(doc) = self.singleton_variant_equality(right, left, should_be_equal) {
1881 return doc;
1882 }
1883
1884 // Other types must be compared using structural equality
1885 let left =
1886 self.not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(left));
1887 let right =
1888 self.not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(right));
1889
1890 self.prelude_equal_call(should_be_equal, left, right)
1891 }
1892
1893 fn singleton_variant_equality(
1894 &mut self,
1895 left: &'a TypedExpr,
1896 right: &'a TypedExpr,
1897 should_be_equal: bool,
1898 ) -> Option<Document<'a>> {
1899 match right {
1900 TypedExpr::Var {
1901 name,
1902 constructor:
1903 ValueConstructor {
1904 variant:
1905 ValueConstructorVariant::Record {
1906 arity: 0,
1907 name: variant_name,
1908 ..
1909 },
1910 ..
1911 },
1912 ..
1913 } => {
1914 let left_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| {
1915 this.wrap_expression(left)
1916 });
1917 Some(self.singleton_equal(
1918 left_doc,
1919 None,
1920 name.clone(),
1921 should_be_equal,
1922 variant_name.clone(),
1923 right.type_(),
1924 ))
1925 }
1926 TypedExpr::ModuleSelect {
1927 module_alias,
1928 constructor: ModuleValueConstructor::Record { arity: 0, name, .. },
1929 ..
1930 } => {
1931 let left_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| {
1932 this.wrap_expression(left)
1933 });
1934 Some(self.singleton_equal(
1935 left_doc,
1936 Some(module_alias),
1937 name.clone(),
1938 should_be_equal,
1939 name.clone(),
1940 right.type_(),
1941 ))
1942 }
1943 // Empty lists are implemented as a variant with no fields, so we can
1944 // use `instanceof` for a faster check.
1945 TypedExpr::List { elements, .. } if elements.is_empty() => {
1946 let left_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| {
1947 this.wrap_expression(left)
1948 });
1949 self.tracker.list_empty_class_used = true;
1950 Some(self.singleton_equal(
1951 left_doc,
1952 None,
1953 "$Empty".into(),
1954 should_be_equal,
1955 "Empty".into(),
1956 right.type_(),
1957 ))
1958 }
1959 TypedExpr::Int { .. }
1960 | TypedExpr::Float { .. }
1961 | TypedExpr::String { .. }
1962 | TypedExpr::Block { .. }
1963 | TypedExpr::Pipeline { .. }
1964 | TypedExpr::Var { .. }
1965 | TypedExpr::Fn { .. }
1966 | TypedExpr::List { .. }
1967 | TypedExpr::Call { .. }
1968 | TypedExpr::BinOp { .. }
1969 | TypedExpr::Case { .. }
1970 | TypedExpr::RecordAccess { .. }
1971 | TypedExpr::PositionalAccess { .. }
1972 | TypedExpr::ModuleSelect { .. }
1973 | TypedExpr::Tuple { .. }
1974 | TypedExpr::TupleIndex { .. }
1975 | TypedExpr::Todo { .. }
1976 | TypedExpr::Panic { .. }
1977 | TypedExpr::Echo { .. }
1978 | TypedExpr::BitArray { .. }
1979 | TypedExpr::RecordUpdate { .. }
1980 | TypedExpr::NegateBool { .. }
1981 | TypedExpr::NegateInt { .. }
1982 | TypedExpr::Invalid { .. } => None,
1983 }
1984 }
1985
1986 fn singleton_equal(
1987 &mut self,
1988 value: Document<'a>,
1989 module: Option<&'a str>,
1990 name: EcoString,
1991 should_be_equal: bool,
1992 variant_name: EcoString,
1993 type_: Arc<Type>,
1994 ) -> Document<'a> {
1995 // If we're using this variant unqualified, register it as used so that
1996 // we know to import it. This `instanceof` check only happens if the
1997 // variant has no fields, so we don't need to check that here.
1998 if module.is_none()
1999 && let Some((package, module, type_name)) = type_.named_type_name_and_package()
2000 {
2001 _ = self
2002 .tracker
2003 .variants_used_in_instanceof
2004 .insert(TypeVariant {
2005 package,
2006 module,
2007 type_name,
2008 name: variant_name,
2009 });
2010 }
2011 let record = if let Some(module) = module {
2012 docvec!["$", module, ".", name]
2013 } else {
2014 name.to_doc()
2015 };
2016
2017 if should_be_equal {
2018 docvec![value, " instanceof ", record]
2019 } else {
2020 docvec!["!(", value, " instanceof ", record, ")"]
2021 }
2022 }
2023
2024 fn equal_with_doc_operands(
2025 &mut self,
2026 left: Document<'a>,
2027 right: Document<'a>,
2028 type_: Arc<Type>,
2029 should_be_equal: bool,
2030 ) -> Document<'a> {
2031 // If it is a simple scalar type then we can use JS' reference identity
2032 if is_js_scalar(type_) {
2033 let operator = if should_be_equal { " === " } else { " !== " };
2034 return docvec![left, operator, right];
2035 }
2036
2037 // Other types must be compared using structural equality
2038 self.prelude_equal_call(should_be_equal, left, right)
2039 }
2040
2041 pub(super) fn prelude_equal_call(
2042 &mut self,
2043 should_be_equal: bool,
2044 left: Document<'a>,
2045 right: Document<'a>,
2046 ) -> Document<'a> {
2047 // Record that we need to import the prelude's isEqual function into the module
2048 self.tracker.object_equality_used = true;
2049 // Construct the call
2050 let arguments = wrap_arguments([left, right]);
2051 let operator = if should_be_equal {
2052 "isEqual"
2053 } else {
2054 "!isEqual"
2055 };
2056 docvec![operator, arguments]
2057 }
2058
2059 fn print_bin_op(
2060 &mut self,
2061 left: &'a TypedExpr,
2062 right: &'a TypedExpr,
2063 op: &'a str,
2064 ) -> Document<'a> {
2065 let left =
2066 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left));
2067 let right =
2068 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right));
2069 docvec![left, " ", op, " ", right]
2070 }
2071
2072 pub(super) fn bin_op_with_doc_operands(
2073 &mut self,
2074 name: BinOp,
2075 left: Document<'a>,
2076 right: Document<'a>,
2077 type_: &Arc<Type>,
2078 ) -> Document<'a> {
2079 match name {
2080 BinOp::And => docvec![left, " && ", right],
2081 BinOp::Or => docvec![left, " || ", right],
2082 BinOp::LtInt | BinOp::LtFloat => docvec![left, " < ", right],
2083 BinOp::LtEqInt | BinOp::LtEqFloat => docvec![left, " <= ", right],
2084 BinOp::Eq => self.equal_with_doc_operands(left, right, type_.clone(), true),
2085 BinOp::NotEq => self.equal_with_doc_operands(left, right, type_.clone(), false),
2086 BinOp::GtInt | BinOp::GtFloat => docvec![left, " > ", right],
2087 BinOp::GtEqInt | BinOp::GtEqFloat => docvec![left, " >= ", right],
2088 BinOp::Concatenate | BinOp::AddInt | BinOp::AddFloat => {
2089 docvec![left, " + ", right]
2090 }
2091 BinOp::SubInt | BinOp::SubFloat => docvec![left, " - ", right],
2092 BinOp::MultInt | BinOp::MultFloat => docvec![left, " * ", right],
2093 BinOp::RemainderInt => {
2094 self.tracker.int_remainder_used = true;
2095 docvec!["remainderInt", wrap_arguments([left, right])]
2096 }
2097 BinOp::DivInt => {
2098 self.tracker.int_division_used = true;
2099 docvec!["divideInt", wrap_arguments([left, right])]
2100 }
2101 BinOp::DivFloat => {
2102 self.tracker.float_division_used = true;
2103 docvec!["divideFloat", wrap_arguments([left, right])]
2104 }
2105 }
2106 }
2107
2108 fn todo(&mut self, message: Option<&'a TypedExpr>, location: &'a SrcSpan) -> Document<'a> {
2109 let message = match message {
2110 Some(m) => self.not_in_tail_position(None, |this| this.wrap_expression(m)),
2111 None => string("`todo` expression evaluated. This code has not yet been implemented."),
2112 };
2113 self.throw_error("todo", &message, *location, vec![])
2114 }
2115
2116 fn panic(&mut self, location: &'a SrcSpan, message: Option<&'a TypedExpr>) -> Document<'a> {
2117 let message = match message {
2118 Some(m) => self.not_in_tail_position(None, |this| this.wrap_expression(m)),
2119 None => string("`panic` expression evaluated."),
2120 };
2121 self.throw_error("panic", &message, *location, vec![])
2122 }
2123
2124 pub(crate) fn throw_error<Fields>(
2125 &mut self,
2126 error_name: &'a str,
2127 message: &Document<'a>,
2128 location: SrcSpan,
2129 fields: Fields,
2130 ) -> Document<'a>
2131 where
2132 Fields: IntoIterator<Item = (&'a str, Document<'a>)>,
2133 {
2134 self.tracker.make_error_used = true;
2135 let module = self.module_name.clone().to_doc().surround('"', '"');
2136 let function = self.function_name.clone().to_doc().surround("\"", "\"");
2137 let line = self.line_numbers.line_number(location.start).to_doc();
2138 let fields = wrap_object(fields.into_iter().map(|(k, v)| (k.to_doc(), Some(v))));
2139
2140 docvec![
2141 self.source_map_tracker(location.start),
2142 "throw makeError",
2143 wrap_arguments([
2144 string(error_name),
2145 "FILEPATH".to_doc(),
2146 module,
2147 line,
2148 function,
2149 message.clone(),
2150 fields
2151 ]),
2152 ]
2153 }
2154
2155 fn module_select(
2156 &mut self,
2157 module: &'a str,
2158 label: &'a EcoString,
2159 constructor: &'a ModuleValueConstructor,
2160 ) -> Document<'a> {
2161 match constructor {
2162 ModuleValueConstructor::Fn { .. } | ModuleValueConstructor::Constant { .. } => {
2163 docvec!["$", module, ".", maybe_escape_identifier(label)]
2164 }
2165
2166 ModuleValueConstructor::Record {
2167 name, arity, type_, ..
2168 } => self.record_constructor(type_.clone(), Some(module), name, name, *arity),
2169 }
2170 }
2171
2172 fn echo(
2173 &mut self,
2174 expression: Document<'a>,
2175 message: Option<&'a TypedExpr>,
2176 location: &'a SrcSpan,
2177 ) -> Document<'a> {
2178 self.tracker.echo_used = true;
2179
2180 let message = match message {
2181 Some(message) => self
2182 .not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(message)),
2183 None => "undefined".to_doc(),
2184 };
2185
2186 let echo_arguments = call_arguments(vec![
2187 expression,
2188 message,
2189 self.src_path.clone().to_doc(),
2190 self.line_numbers.line_number(location.start).to_doc(),
2191 ]);
2192 self.wrap_return(docvec!["echo", echo_arguments])
2193 }
2194
2195 pub(crate) fn constant_expression(
2196 &mut self,
2197 context: Context,
2198 expression: &'a TypedConstant,
2199 ) -> Document<'a> {
2200 match expression {
2201 Constant::Int { value, .. } => int(value),
2202 Constant::Float { value, .. } => float(value),
2203 Constant::String { value, .. } => string(value),
2204 Constant::Tuple { elements, .. } => array(
2205 elements
2206 .iter()
2207 .map(|element| self.constant_expression(context, element)),
2208 ),
2209
2210 Constant::List { elements, tail, .. } => {
2211 if tail.is_none() && elements.is_empty() {
2212 return self.empty_list();
2213 }
2214
2215 self.tracker.list_used = true;
2216 let list = match tail {
2217 // There's no tail in the list, we join all the elements and
2218 // call it a day.
2219 None => list(
2220 elements
2221 .iter()
2222 .map(|element| self.constant_expression(context, element)),
2223 ),
2224
2225 Some(tail) => match tail.list_elements() {
2226 // There's a tail in the list whose elements are all
2227 // known at compile time. In this case we replace the
2228 // tail with those elements and create a single flat
2229 // list.
2230 Some(tail_elements) => list(
2231 elements
2232 .iter()
2233 .chain(tail_elements)
2234 .map(|element| self.constant_expression(context, element)),
2235 ),
2236 // There's a tail in the list but we can't really tell
2237 // what its elements are at compile time. This means we
2238 // have to prepend to this list.
2239 None => {
2240 self.tracker.prepend_used = true;
2241 let tail = self.constant_expression(context, tail);
2242 prepend(
2243 elements
2244 .iter()
2245 .map(|element| self.constant_expression(context, element)),
2246 tail,
2247 )
2248 }
2249 },
2250 };
2251 match context {
2252 Context::Constant => docvec!["/* @__PURE__ */ ", list],
2253 Context::Guard => list,
2254 }
2255 }
2256
2257 Constant::Record { type_, name, .. } if type_.is_bool() && name == "True" => {
2258 "true".to_doc()
2259 }
2260 Constant::Record { type_, name, .. } if type_.is_bool() && name == "False" => {
2261 "false".to_doc()
2262 }
2263 Constant::Record { type_, .. } if type_.is_nil() => "undefined".to_doc(),
2264
2265 Constant::Record {
2266 arguments,
2267 module,
2268 name,
2269 type_,
2270 ..
2271 } => {
2272 let tag = expression
2273 .constant_record_tag()
2274 .expect("record without inferred constructor made it to code generation");
2275
2276 if module.is_none() && type_.is_result() {
2277 if tag == "Ok" {
2278 self.tracker.ok_used = true;
2279 } else {
2280 self.tracker.error_used = true;
2281 }
2282 }
2283
2284 // If there's no arguments and the type is a function that takes
2285 // arguments then this is the constructor being referenced, not the
2286 // function being called.
2287 if let Some(arity) = type_.fn_arity()
2288 && arguments.is_none()
2289 && arity != 0
2290 {
2291 let arity = arity as u16;
2292 return self.record_constructor(type_.clone(), None, &tag, name, arity);
2293 }
2294
2295 // Otherwise we're always constructing a record! Even if there's
2296 // no argument list:
2297 // ```gleam
2298 // pub type Wibble { Wibble }
2299 // pub const wibble = Wibble // <- here we're constructing the record!
2300 // ```
2301 //
2302 // Record updates are fully expanded during type checking, so we
2303 // just handle arguments
2304 let field_values = arguments
2305 .iter()
2306 .flatten()
2307 .map(|argument| self.constant_expression(context, &argument.value))
2308 .collect_vec();
2309
2310 let constructor = construct_record(
2311 module.as_ref().map(|(module, _)| module.as_str()),
2312 name,
2313 field_values,
2314 );
2315 match context {
2316 Context::Constant => docvec!["/* @__PURE__ */ ", constructor],
2317 Context::Guard => constructor,
2318 }
2319 }
2320 Constant::BitArray { segments, .. } => {
2321 let bit_array = self.constant_bit_array(segments, context);
2322 match context {
2323 Context::Constant => docvec!["/* @__PURE__ */ ", bit_array],
2324 Context::Guard => bit_array,
2325 }
2326 }
2327
2328 Constant::Var { name, module, .. } => {
2329 match (module, context) {
2330 (None, Context::Guard) => self.local_var(name).to_doc(),
2331 (None, Context::Constant) => maybe_escape_identifier(name).to_doc(),
2332 (Some((module, _)), _) => {
2333 // JS keywords can be accessed here, but we must escape anyway
2334 // as we escape when exporting such names in the first place,
2335 // and the imported name has to match the exported name.
2336 docvec!["$", module, ".", maybe_escape_identifier(name)]
2337 }
2338 }
2339 }
2340
2341 Constant::StringConcatenation { left, right, .. } => {
2342 let left = self.constant_expression(context, left);
2343 let right = self.constant_expression(context, right);
2344 docvec![left, " + ", right]
2345 }
2346
2347 Constant::RecordUpdate { .. } => {
2348 panic!("record updates should not reach code generation")
2349 }
2350 Constant::Todo { .. } => {
2351 panic!("todo constants should not reach code generation")
2352 }
2353 Constant::Invalid { .. } => {
2354 panic!("invalid constants should not reach code generation")
2355 }
2356 }
2357 }
2358
2359 fn constant_bit_array(
2360 &mut self,
2361 segments: &'a [TypedConstantBitArraySegment],
2362 context: Context,
2363 ) -> Document<'a> {
2364 self.tracker.bit_array_literal_used = true;
2365 let segments_array = array(segments.iter().map(|segment| {
2366 let value = match context {
2367 Context::Constant => self.constant_expression(context, &segment.value),
2368 Context::Guard => self.guard_constant_expression(&segment.value),
2369 };
2370
2371 let details = self.constant_bit_array_segment_details(segment, context);
2372
2373 match details.type_ {
2374 BitArraySegmentType::BitArray => {
2375 if segment.size().is_some() {
2376 self.tracker.bit_array_slice_used = true;
2377 docvec!["bitArraySlice(", value, ", 0, ", details.size, ")"]
2378 } else {
2379 value
2380 }
2381 }
2382 BitArraySegmentType::Int => match (details.size_value, segment.value.as_ref()) {
2383 (Some(size_value), Constant::Int { int_value, .. })
2384 if size_value <= SAFE_INT_SEGMENT_MAX_SIZE.into()
2385 && (&size_value % BigInt::from(8) == BigInt::ZERO) =>
2386 {
2387 let bytes = bit_array_segment_int_value_to_bytes(
2388 int_value.clone(),
2389 size_value,
2390 segment.endianness(),
2391 );
2392
2393 u8_slice(&bytes)
2394 }
2395
2396 (Some(size_value), _) if size_value == 8.into() => value,
2397
2398 (Some(size_value), _) if size_value <= 0.into() => nil(),
2399
2400 _ => {
2401 self.tracker.sized_integer_segment_used = true;
2402 let size = details.size;
2403 let is_big = bool(segment.endianness().is_big());
2404 docvec!["sizedInt(", value, ", ", size, ", ", is_big, ")"]
2405 }
2406 },
2407 BitArraySegmentType::Float => {
2408 self.tracker.float_bit_array_segment_used = true;
2409 let size = details.size;
2410 let is_big = bool(details.endianness.is_big());
2411 docvec!["sizedFloat(", value, ", ", size, ", ", is_big, ")"]
2412 }
2413 BitArraySegmentType::String(StringEncoding::Utf8) => {
2414 self.tracker.string_bit_array_segment_used = true;
2415 docvec!["stringBits(", value, ")"]
2416 }
2417 BitArraySegmentType::String(StringEncoding::Utf16) => {
2418 self.tracker.string_utf16_bit_array_segment_used = true;
2419 let is_big = bool(details.endianness.is_big());
2420 docvec!["stringToUtf16(", value, ", ", is_big, ")"]
2421 }
2422 BitArraySegmentType::String(StringEncoding::Utf32) => {
2423 self.tracker.string_utf32_bit_array_segment_used = true;
2424 let is_big = bool(details.endianness.is_big());
2425 docvec!["stringToUtf32(", value, ", ", is_big, ")"]
2426 }
2427 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf8) => {
2428 self.tracker.codepoint_bit_array_segment_used = true;
2429 docvec!["codepointBits(", value, ")"]
2430 }
2431 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf16) => {
2432 self.tracker.codepoint_utf16_bit_array_segment_used = true;
2433 let is_big = bool(details.endianness.is_big());
2434 docvec!["codepointToUtf16(", value, ", ", is_big, ")"]
2435 }
2436 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf32) => {
2437 self.tracker.codepoint_utf32_bit_array_segment_used = true;
2438 let is_big = bool(details.endianness.is_big());
2439 docvec!["codepointToUtf32(", value, ", ", is_big, ")"]
2440 }
2441 }
2442 }));
2443
2444 docvec!["toBitArray(", segments_array, ")"]
2445 }
2446
2447 fn constant_bit_array_segment_details(
2448 &mut self,
2449 segment: &'a TypedConstantBitArraySegment,
2450 context: Context,
2451 ) -> BitArraySegmentDetails<'a> {
2452 let size = segment.size();
2453 let unit = segment.unit();
2454 let (size_value, size) = match size {
2455 Some(Constant::Int { int_value, .. }) => {
2456 let size_value = int_value * unit;
2457 let size = eco_format!("{}", size_value).to_doc();
2458 (Some(size_value), size)
2459 }
2460
2461 Some(size) => {
2462 let mut size = match context {
2463 Context::Constant => self.constant_expression(context, size),
2464 Context::Guard => self.guard_constant_expression(size),
2465 };
2466 if unit != 1 {
2467 size = size.group().append(" * ".to_doc().append(unit.to_doc()));
2468 }
2469
2470 (None, size)
2471 }
2472
2473 None => {
2474 let size_value: usize = if segment.type_.is_int() { 8 } else { 64 };
2475 (Some(BigInt::from(size_value)), docvec![size_value])
2476 }
2477 };
2478
2479 let type_ = BitArraySegmentType::from_segment(segment);
2480
2481 BitArraySegmentDetails {
2482 type_,
2483 size,
2484 size_value,
2485 endianness: segment.endianness(),
2486 }
2487 }
2488
2489 pub(crate) fn guard(&mut self, guard: &'a TypedClauseGuard) -> Document<'a> {
2490 match guard {
2491 ClauseGuard::Invalid { .. } => unreachable!("invalid guard made it to code generation"),
2492
2493 ClauseGuard::Block { value, .. } => self.guard(value).surround("(", ")"),
2494
2495 ClauseGuard::BinaryOperator {
2496 left,
2497 right,
2498 operator,
2499 ..
2500 } => {
2501 let operator = match operator {
2502 BinOp::Eq if is_js_scalar(left.type_()) => "===",
2503 BinOp::NotEq if is_js_scalar(left.type_()) => "!==",
2504 BinOp::Eq | BinOp::NotEq => {
2505 let should_be_equal = *operator == BinOp::Eq;
2506
2507 // Handle singleton equality optimization for guards
2508 if let Some(doc) =
2509 self.singleton_variant_guard_equality(left, right, should_be_equal)
2510 {
2511 return doc;
2512 }
2513
2514 if let Some(doc) =
2515 self.singleton_variant_guard_equality(right, left, should_be_equal)
2516 {
2517 return doc;
2518 }
2519
2520 let left_doc = self.guard(left);
2521 let right_doc = self.guard(right);
2522 return self.prelude_equal_call(should_be_equal, left_doc, right_doc);
2523 }
2524
2525 BinOp::GtFloat | BinOp::GtInt => ">",
2526 BinOp::GtEqFloat | BinOp::GtEqInt => ">=",
2527 BinOp::LtFloat | BinOp::LtInt => "<",
2528 BinOp::LtEqFloat | BinOp::LtEqInt => "<=",
2529
2530 BinOp::AddFloat | BinOp::AddInt | BinOp::Concatenate => "+",
2531 BinOp::SubFloat | BinOp::SubInt => "-",
2532 BinOp::MultFloat | BinOp::MultInt => "*",
2533
2534 BinOp::DivFloat => {
2535 self.tracker.float_division_used = true;
2536
2537 return docvec![
2538 "divideFloat",
2539 wrap_arguments([self.guard(left), self.guard(right)])
2540 ];
2541 }
2542
2543 BinOp::DivInt => {
2544 self.tracker.int_division_used = true;
2545 return docvec![
2546 "divideInt",
2547 wrap_arguments([self.guard(left), self.guard(right)])
2548 ];
2549 }
2550
2551 BinOp::RemainderInt => {
2552 self.tracker.int_remainder_used = true;
2553 return docvec![
2554 "remainderInt",
2555 wrap_arguments([self.guard(left), self.guard(right)])
2556 ];
2557 }
2558
2559 BinOp::And => "&&",
2560 BinOp::Or => "||",
2561 };
2562
2563 let left_document = self.wrapped_guard(left);
2564 let right_document = self.wrapped_guard(right);
2565
2566 docvec![left_document, " ", operator, " ", right_document]
2567 }
2568
2569 ClauseGuard::Var { name, .. } => self.local_var(name).to_doc(),
2570
2571 ClauseGuard::TupleIndex { tuple, index, .. } => {
2572 docvec![self.guard(tuple,), "[", index, "]"]
2573 }
2574
2575 ClauseGuard::FieldAccess {
2576 label, container, ..
2577 } => docvec![self.guard(container), ".", maybe_escape_property(label)],
2578
2579 ClauseGuard::ModuleSelect {
2580 module_alias,
2581 label,
2582 ..
2583 } => docvec!["$", module_alias, ".", label],
2584
2585 ClauseGuard::Not { expression, .. } => docvec!["!", self.guard(expression,)],
2586
2587 ClauseGuard::Constant(constant) => self.guard_constant_expression(constant),
2588 }
2589 }
2590
2591 fn singleton_variant_guard_equality(
2592 &mut self,
2593 left: &'a TypedClauseGuard,
2594 right: &'a TypedClauseGuard,
2595 should_be_equal: bool,
2596 ) -> Option<Document<'a>> {
2597 match right {
2598 ClauseGuard::Constant(Constant::Record {
2599 record_constructor: Some(constructor),
2600 module,
2601 name,
2602 ..
2603 }) if let ValueConstructorVariant::Record {
2604 arity: 0,
2605 name: variant_name,
2606 ..
2607 } = &constructor.variant =>
2608 {
2609 let left_doc = self.guard(left);
2610 Some(self.singleton_equal(
2611 left_doc,
2612 module.as_ref().map(|(module, _)| module.as_str()),
2613 name.clone(),
2614 should_be_equal,
2615 variant_name.clone(),
2616 right.type_(),
2617 ))
2618 }
2619 ClauseGuard::Constant(Constant::List {
2620 elements,
2621 tail: None,
2622 ..
2623 }) if elements.is_empty() => {
2624 let left_doc = self.guard(left);
2625 self.tracker.list_empty_class_used = true;
2626 Some(self.singleton_equal(
2627 left_doc,
2628 None,
2629 "$Empty".into(),
2630 should_be_equal,
2631 "Empty".into(),
2632 right.type_(),
2633 ))
2634 }
2635 ClauseGuard::Block { .. }
2636 | ClauseGuard::BinaryOperator { .. }
2637 | ClauseGuard::Not { .. }
2638 | ClauseGuard::Var { .. }
2639 | ClauseGuard::TupleIndex { .. }
2640 | ClauseGuard::FieldAccess { .. }
2641 | ClauseGuard::ModuleSelect { .. }
2642 | ClauseGuard::Constant(_)
2643 | ClauseGuard::Invalid { .. } => None,
2644 }
2645 }
2646
2647 fn wrapped_guard(&mut self, guard: &'a TypedClauseGuard) -> Document<'a> {
2648 match guard {
2649 ClauseGuard::Invalid { .. } => unreachable!("invalid guard made it to code generation"),
2650 ClauseGuard::Var { .. }
2651 | ClauseGuard::TupleIndex { .. }
2652 | ClauseGuard::Constant(_)
2653 | ClauseGuard::Not { .. }
2654 | ClauseGuard::FieldAccess { .. }
2655 | ClauseGuard::Block { .. } => self.guard(guard),
2656
2657 ClauseGuard::BinaryOperator { .. } | ClauseGuard::ModuleSelect { .. } => {
2658 docvec!["(", self.guard(guard), ")"]
2659 }
2660 }
2661 }
2662
2663 fn guard_constant_expression(&mut self, expression: &'a TypedConstant) -> Document<'a> {
2664 match expression {
2665 Constant::Tuple { elements, .. } => array(
2666 elements
2667 .iter()
2668 .map(|element| self.guard_constant_expression(element)),
2669 ),
2670
2671 Constant::Record { type_, name, .. } if type_.is_bool() && name == "True" => {
2672 "true".to_doc()
2673 }
2674 Constant::Record { type_, name, .. } if type_.is_bool() && name == "False" => {
2675 "false".to_doc()
2676 }
2677 Constant::Record { type_, .. } if type_.is_nil() => "undefined".to_doc(),
2678
2679 Constant::BitArray { segments, .. } => {
2680 self.constant_bit_array(segments, Context::Guard)
2681 }
2682
2683 Constant::Var { name, .. } => self.local_var(name).to_doc(),
2684
2685 Constant::Record { .. }
2686 | Constant::Int { .. }
2687 | Constant::Float { .. }
2688 | Constant::String { .. }
2689 | Constant::List { .. }
2690 | Constant::RecordUpdate { .. }
2691 | Constant::StringConcatenation { .. }
2692 | Constant::Todo { .. }
2693 | Constant::Invalid { .. } => self.constant_expression(Context::Guard, expression),
2694 }
2695 }
2696
2697 pub fn source_map_tracker(&mut self, start_index: u32) -> Document<'a> {
2698 create_cursor_position_observer(&self.source_map_builder, self.line_numbers, start_index)
2699 }
2700
2701 pub(crate) fn record_constructor(
2702 &mut self,
2703 type_: Arc<Type>,
2704 qualifier: Option<&'a str>,
2705 variant_name: &EcoString,
2706 name: &'a EcoString,
2707 arity: u16,
2708 ) -> Document<'a> {
2709 if qualifier.is_none() && type_.is_result_constructor() {
2710 if name == "Ok" {
2711 self.tracker.ok_used = true;
2712 } else if name == "Error" {
2713 self.tracker.error_used = true;
2714 }
2715 }
2716 if type_.is_bool() && name == "True" {
2717 "true".to_doc()
2718 } else if type_.is_bool() {
2719 "false".to_doc()
2720 } else if type_.is_nil() {
2721 "undefined".to_doc()
2722 } else if arity == 0
2723 && let Some((package, module, type_name)) = type_.named_type_name_and_package()
2724 {
2725 // If the variant has no fields, return the singleton constant so
2726 // that all values of the variant are the same underlying reference,
2727 // and are faster to compare.
2728 match qualifier {
2729 Some(module) => docvec!["$", module, ".", type_name, "$", name, "$const"],
2730 None => {
2731 if module != self.module_name {
2732 let alias = if name == variant_name {
2733 None
2734 } else {
2735 Some(eco_format!("{}${}$const", type_name, name))
2736 };
2737 // Since this constant is an implementation detail and not
2738 // present in Gleam code, we need to track it so that we
2739 // import it, as it doesn't appear directly in the `import`s
2740 // in the source code.
2741 _ = self.tracker.variant_constants_used.insert(
2742 TypeVariant {
2743 package,
2744 module,
2745 type_name: type_name.clone(),
2746 name: variant_name.clone(),
2747 },
2748 alias,
2749 );
2750 }
2751 docvec![type_name, "$", name, "$const"]
2752 }
2753 }
2754 } else {
2755 let vars = (0..arity).map(|i| eco_format!("var{i}").to_doc());
2756 let body = docvec![
2757 "return ",
2758 construct_record(qualifier, name, vars.clone()),
2759 ";"
2760 ];
2761 docvec![
2762 docvec![wrap_arguments(vars), " => {", break_("", " "), body]
2763 .nest(INDENT)
2764 .append(break_("", " "))
2765 .group(),
2766 "}",
2767 ]
2768 }
2769 }
2770}
2771
2772#[derive(Clone, Copy)]
2773enum AssertExpression {
2774 Literal,
2775 Expression,
2776 Unevaluated,
2777}
2778
2779impl AssertExpression {
2780 fn from_expression(expression: &TypedExpr) -> Self {
2781 if expression.is_literal() {
2782 Self::Literal
2783 } else {
2784 Self::Expression
2785 }
2786 }
2787}
2788
2789pub fn int(value: &str) -> Document<'_> {
2790 eco_string_int(value.into())
2791}
2792
2793pub fn eco_string_int<'a>(value: EcoString) -> Document<'a> {
2794 let mut out = EcoString::with_capacity(value.len());
2795
2796 if value.starts_with('-') {
2797 out.push('-');
2798 } else if value.starts_with('+') {
2799 out.push('+');
2800 };
2801 let value = value.trim_start_matches(['+', '-'].as_ref());
2802
2803 let value = if value.starts_with("0x") {
2804 out.push_str("0x");
2805 value.trim_start_matches("0x")
2806 } else if value.starts_with("0o") {
2807 out.push_str("0o");
2808 value.trim_start_matches("0o")
2809 } else if value.starts_with("0b") {
2810 out.push_str("0b");
2811 value.trim_start_matches("0b")
2812 } else {
2813 value
2814 };
2815
2816 let value = value.trim_start_matches(['0', '_']);
2817 if value.is_empty() {
2818 out.push('0');
2819 }
2820
2821 out.push_str(value);
2822
2823 out.to_doc()
2824}
2825
2826pub fn float(value: &str) -> Document<'_> {
2827 let mut out = EcoString::with_capacity(value.len());
2828
2829 if value.starts_with('-') {
2830 out.push('-');
2831 } else if value.starts_with('+') {
2832 out.push('+');
2833 };
2834 let value = value.trim_start_matches(['+', '-'].as_ref());
2835
2836 let value = value.trim_start_matches(['0', '_']);
2837 if value.starts_with(['.', 'e', 'E']) {
2838 out.push('0');
2839 }
2840 out.push_str(value);
2841
2842 out.to_doc()
2843}
2844
2845pub fn float_from_value(value: f64) -> Document<'static> {
2846 if value.is_infinite() {
2847 if value.is_sign_positive() {
2848 "Infinity".to_doc()
2849 } else {
2850 "-Infinity".to_doc()
2851 }
2852 } else if value.is_nan() {
2853 // NOTE: this case is probably unnecessary, as this function is only
2854 // invoked with `LiteralFloatValue` values, which cannot be nan.
2855 "NaN".to_doc()
2856 } else {
2857 value.to_doc()
2858 }
2859}
2860
2861/// The context where the constant expression is used, it might be inside a
2862/// function call, or in the definition of another constant.
2863///
2864/// Based on the context we might want to annotate pure function calls as
2865/// "@__PURE__".
2866///
2867#[derive(Debug, Clone, Copy)]
2868pub enum Context {
2869 Constant,
2870 Guard,
2871}
2872
2873#[derive(Debug)]
2874struct BitArraySegmentDetails<'a> {
2875 type_: BitArraySegmentType,
2876 size: Document<'a>,
2877 /// The size of the bit array segment stored as a BigInt.
2878 /// This has a value when the segment's size is known at compile time.
2879 size_value: Option<BigInt>,
2880 endianness: Endianness,
2881}
2882
2883#[derive(Debug, Clone, Copy)]
2884enum BitArraySegmentType {
2885 BitArray,
2886 Int,
2887 Float,
2888 String(StringEncoding),
2889 UtfCodepoint(StringEncoding),
2890}
2891
2892impl BitArraySegmentType {
2893 fn from_segment<Value>(segment: &BitArraySegment<Value, Arc<Type>>) -> Self {
2894 if segment.type_.is_int() {
2895 BitArraySegmentType::Int
2896 } else if segment.type_.is_float() {
2897 BitArraySegmentType::Float
2898 } else if segment.type_.is_bit_array() {
2899 BitArraySegmentType::BitArray
2900 } else if segment.type_.is_string() {
2901 let encoding = if segment.has_utf16_option() {
2902 StringEncoding::Utf16
2903 } else if segment.has_utf32_option() {
2904 StringEncoding::Utf32
2905 } else {
2906 StringEncoding::Utf8
2907 };
2908 BitArraySegmentType::String(encoding)
2909 } else if segment.type_.is_utf_codepoint() {
2910 let encoding = if segment.has_utf16_codepoint_option() {
2911 StringEncoding::Utf16
2912 } else if segment.has_utf32_codepoint_option() {
2913 StringEncoding::Utf32
2914 } else {
2915 StringEncoding::Utf8
2916 };
2917 BitArraySegmentType::UtfCodepoint(encoding)
2918 } else {
2919 panic!(
2920 "Invalid bit array segment type reached code generation: {:?}",
2921 segment.type_
2922 );
2923 }
2924 }
2925}
2926
2927pub fn string<'a>(value: &'a str) -> Document<'a> {
2928 if value.contains('\n') {
2929 EcoString::from(value.replace('\n', r"\n"))
2930 .to_doc()
2931 .surround("\"", "\"")
2932 } else {
2933 value.to_doc().surround("\"", "\"")
2934 }
2935}
2936
2937pub(crate) fn array<'a, Elements: IntoIterator<Item = Document<'a>>>(
2938 elements: Elements,
2939) -> Document<'a> {
2940 let elements = Itertools::intersperse(elements.into_iter(), break_(",", ", ")).collect_vec();
2941 if elements.is_empty() {
2942 // Do not add a trailing comma since that adds an 'undefined' element
2943 "[]".to_doc()
2944 } else {
2945 docvec![
2946 "[",
2947 docvec![break_("", ""), elements].nest(INDENT),
2948 break_(",", ""),
2949 "]"
2950 ]
2951 .group()
2952 }
2953}
2954
2955pub(crate) fn list<'a, I: IntoIterator<Item = Document<'a>>>(elements: I) -> Document<'a>
2956where
2957 I::IntoIter: DoubleEndedIterator,
2958{
2959 let array = array(elements);
2960 docvec!["toList(", array, ")"]
2961}
2962
2963fn prepend<'a, I: IntoIterator<Item = Document<'a>>>(
2964 elements: I,
2965 tail: Document<'a>,
2966) -> Document<'a>
2967where
2968 I::IntoIter: DoubleEndedIterator + ExactSizeIterator,
2969{
2970 elements.into_iter().rev().fold(tail, |tail, element| {
2971 let arguments = call_arguments([element, tail]);
2972 docvec!["listPrepend", arguments]
2973 })
2974}
2975
2976fn call_arguments<'a, Elements: IntoIterator<Item = Document<'a>>>(
2977 elements: Elements,
2978) -> Document<'a> {
2979 let elements = Itertools::intersperse(elements.into_iter(), break_(",", ", "))
2980 .collect_vec()
2981 .to_doc();
2982 if elements.is_empty() {
2983 return "()".to_doc();
2984 }
2985 docvec![
2986 "(",
2987 docvec![break_("", ""), elements].nest(INDENT),
2988 break_(",", ""),
2989 ")"
2990 ]
2991 .group()
2992}
2993
2994pub(crate) fn construct_record<'a>(
2995 module: Option<&'a str>,
2996 name: &'a str,
2997 arguments: impl IntoIterator<Item = Document<'a>>,
2998) -> Document<'a> {
2999 let mut any_arguments = false;
3000 let arguments = join(
3001 arguments.into_iter().inspect(|_| {
3002 any_arguments = true;
3003 }),
3004 break_(",", ", "),
3005 );
3006 let arguments = docvec![break_("", ""), arguments].nest(INDENT);
3007 let name = if let Some(module) = module {
3008 docvec!["$", module, ".", name]
3009 } else {
3010 name.to_doc()
3011 };
3012 if any_arguments {
3013 docvec!["new ", name, "(", arguments, break_(",", ""), ")"].group()
3014 } else {
3015 docvec!["new ", name, "()"]
3016 }
3017}
3018
3019impl TypedExpr {
3020 fn handles_own_return(&self) -> bool {
3021 match self {
3022 TypedExpr::Todo { .. }
3023 | TypedExpr::Call { .. }
3024 | TypedExpr::Case { .. }
3025 | TypedExpr::Panic { .. }
3026 | TypedExpr::Block { .. }
3027 | TypedExpr::Echo { .. }
3028 | TypedExpr::Pipeline { .. }
3029 | TypedExpr::RecordUpdate { .. } => true,
3030
3031 TypedExpr::Int { .. }
3032 | TypedExpr::Float { .. }
3033 | TypedExpr::String { .. }
3034 | TypedExpr::Var { .. }
3035 | TypedExpr::Fn { .. }
3036 | TypedExpr::List { .. }
3037 | TypedExpr::BinOp { .. }
3038 | TypedExpr::RecordAccess { .. }
3039 | TypedExpr::PositionalAccess { .. }
3040 | TypedExpr::ModuleSelect { .. }
3041 | TypedExpr::Tuple { .. }
3042 | TypedExpr::TupleIndex { .. }
3043 | TypedExpr::BitArray { .. }
3044 | TypedExpr::NegateBool { .. }
3045 | TypedExpr::NegateInt { .. }
3046 | TypedExpr::Invalid { .. } => false,
3047 }
3048 }
3049}
3050
3051impl BinOp {
3052 fn is_operator_to_wrap(&self) -> bool {
3053 match self {
3054 BinOp::And
3055 | BinOp::Or
3056 | BinOp::Eq
3057 | BinOp::NotEq
3058 | BinOp::LtInt
3059 | BinOp::LtEqInt
3060 | BinOp::LtFloat
3061 | BinOp::LtEqFloat
3062 | BinOp::GtEqInt
3063 | BinOp::GtInt
3064 | BinOp::GtEqFloat
3065 | BinOp::GtFloat
3066 | BinOp::AddInt
3067 | BinOp::AddFloat
3068 | BinOp::SubInt
3069 | BinOp::SubFloat
3070 | BinOp::MultFloat
3071 | BinOp::DivInt
3072 | BinOp::DivFloat
3073 | BinOp::RemainderInt
3074 | BinOp::Concatenate => true,
3075 BinOp::MultInt => false,
3076 }
3077 }
3078}
3079
3080pub fn is_js_scalar(t: Arc<Type>) -> bool {
3081 t.is_int() || t.is_float() || t.is_bool() || t.is_nil() || t.is_string()
3082}
3083
3084fn expression_requires_semicolon(expression: &TypedExpr) -> bool {
3085 match expression {
3086 TypedExpr::Int { .. }
3087 | TypedExpr::Fn { .. }
3088 | TypedExpr::Var { .. }
3089 | TypedExpr::List { .. }
3090 | TypedExpr::Call { .. }
3091 | TypedExpr::Echo { .. }
3092 | TypedExpr::Float { .. }
3093 | TypedExpr::String { .. }
3094 | TypedExpr::BinOp { .. }
3095 | TypedExpr::Tuple { .. }
3096 | TypedExpr::NegateInt { .. }
3097 | TypedExpr::BitArray { .. }
3098 | TypedExpr::TupleIndex { .. }
3099 | TypedExpr::NegateBool { .. }
3100 | TypedExpr::RecordAccess { .. }
3101 | TypedExpr::PositionalAccess { .. }
3102 | TypedExpr::ModuleSelect { .. } => true,
3103
3104 TypedExpr::Todo { .. }
3105 | TypedExpr::Case { .. }
3106 | TypedExpr::Panic { .. }
3107 | TypedExpr::Pipeline { .. }
3108 | TypedExpr::RecordUpdate { .. }
3109 | TypedExpr::Invalid { .. }
3110 | TypedExpr::Block { .. } => false,
3111 }
3112}
3113
3114/// Wrap a document in an immediately invoked function expression
3115fn immediately_invoked_function_expression_document(document: Document<'_>) -> Document<'_> {
3116 docvec![
3117 docvec!["(() => {", break_("", " "), document].nest(INDENT),
3118 break_("", " "),
3119 "})()",
3120 ]
3121 .group()
3122}
3123
3124fn u8_slice<'a>(bytes: &[u8]) -> Document<'a> {
3125 let s: EcoString = bytes
3126 .iter()
3127 .map(u8::to_string)
3128 .collect::<Vec<_>>()
3129 .join(", ")
3130 .into();
3131
3132 docvec![s]
3133}