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