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