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