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