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