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