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