1 use std::sync::Arc;
2
3 use super::{pattern::CompiledPattern, *};
4 use crate::{
5 ast::*,
6 line_numbers::LineNumbers,
7 pretty::*,
8 type_::{HasType, ModuleValueConstructor, Type, ValueConstructor, ValueConstructorVariant},
9 };
10
11 #[derive(Debug)]
12 pub(crate) struct Generator<'module> {
13 module_name: &'module [String],
14 line_numbers: &'module LineNumbers,
15 function_name: Option<&'module str>,
16 function_arguments: Vec<Option<&'module str>>,
17 current_scope_vars: im::HashMap<String, usize>,
18 pub tail_position: bool,
19 // We register whether these features are used within an expression so that
20 // the module generator can output a suitable function if it is needed.
21 pub tracker: &'module mut UsageTracker,
22 // We track whether tail call recusion is used so that we can render a loop
23 // at the top level of the function to use in place of pushing new stack
24 // frames.
25 pub tail_recursion_used: bool,
26 }
27
28 impl<'module> Generator<'module> {
29 #[allow(clippy::too_many_arguments)] // TODO: FIXME
new( module_name: &'module [String], line_numbers: &'module LineNumbers, function_name: &'module str, function_arguments: Vec<Option<&'module str>>, tracker: &'module mut UsageTracker, mut current_scope_vars: im::HashMap<String, usize>, ) -> Self30 pub fn new(
31 module_name: &'module [String],
32 line_numbers: &'module LineNumbers,
33 function_name: &'module str,
34 function_arguments: Vec<Option<&'module str>>,
35 tracker: &'module mut UsageTracker,
36 mut current_scope_vars: im::HashMap<String, usize>,
37 ) -> Self {
38 for &name in function_arguments.iter().flatten() {
39 let _ = current_scope_vars.insert(name.to_string(), 0);
40 }
41 Self {
42 tracker,
43 module_name,
44 line_numbers,
45 function_name: Some(function_name),
46 function_arguments,
47 tail_recursion_used: false,
48 current_scope_vars,
49 tail_position: true,
50 }
51 }
52
local_var<'a>(&mut self, name: &'a str) -> Document<'a>53 pub fn local_var<'a>(&mut self, name: &'a str) -> Document<'a> {
54 match self.current_scope_vars.get(name) {
55 None => {
56 let _ = self.current_scope_vars.insert(name.to_string(), 0);
57 maybe_escape_identifier_doc(name)
58 }
59 Some(0) => maybe_escape_identifier_doc(name),
60 Some(n) if name == "$" => Document::String(format!("${}", n)),
61 Some(n) => Document::String(format!("{}${}", name, n)),
62 }
63 }
64
next_local_var<'a>(&mut self, name: &'a str) -> Document<'a>65 pub fn next_local_var<'a>(&mut self, name: &'a str) -> Document<'a> {
66 let next = self.current_scope_vars.get(name).map_or(0, |i| i + 1);
67 let _ = self.current_scope_vars.insert(name.to_string(), next);
68 self.local_var(name)
69 }
70
function_body<'a>( &mut self, expression: &'a TypedExpr, args: &'a [TypedArg], ) -> Output<'a>71 pub fn function_body<'a>(
72 &mut self,
73 expression: &'a TypedExpr,
74 args: &'a [TypedArg],
75 ) -> Output<'a> {
76 let body = self.expression(expression)?;
77 if self.tail_recursion_used {
78 self.tail_call_loop(body, args)
79 } else {
80 Ok(body)
81 }
82 }
83
tail_call_loop<'a>(&mut self, body: Document<'a>, args: &'a [TypedArg]) -> Output<'a>84 fn tail_call_loop<'a>(&mut self, body: Document<'a>, args: &'a [TypedArg]) -> Output<'a> {
85 let loop_assignments = concat(args.iter().flat_map(Arg::get_variable_name).map(|name| {
86 let var = maybe_escape_identifier_doc(name);
87 docvec!["let ", var, " = loop$", name, ";", line()]
88 }));
89 Ok(docvec!(
90 loop_assignments,
91 "while (true) {",
92 docvec!(line(), body).nest(INDENT),
93 line(),
94 "}"
95 ))
96 }
97
expression<'a>(&mut self, expression: &'a TypedExpr) -> Output<'a>98 pub fn expression<'a>(&mut self, expression: &'a TypedExpr) -> Output<'a> {
99 let document = match expression {
100 TypedExpr::String { value, .. } => Ok(string(value)),
101
102 TypedExpr::Int { value, .. } => Ok(int(value)),
103 TypedExpr::Float { value, .. } => Ok(float(value)),
104
105 TypedExpr::List { elements, tail, .. } => {
106 self.tracker.list_used = true;
107 self.not_in_tail_position(|gen| {
108 let tail = match tail {
109 Some(tail) => Some(gen.wrap_expression(tail)?),
110 None => None,
111 };
112 list(elements.iter().map(|e| gen.wrap_expression(e)), tail)
113 })
114 }
115
116 TypedExpr::Tuple { elems, .. } => self.tuple(elems),
117 TypedExpr::TupleIndex { tuple, index, .. } => self.tuple_index(tuple, *index),
118
119 TypedExpr::Case {
120 location,
121 subjects,
122 clauses,
123 ..
124 } => self.case(*location, subjects, clauses),
125
126 TypedExpr::Call { fun, args, .. } => self.call(fun, args),
127 TypedExpr::Fn { args, body, .. } => self.fn_(args, body),
128
129 TypedExpr::RecordAccess { record, label, .. } => self.record_access(record, label),
130 TypedExpr::RecordUpdate { spread, args, .. } => self.record_update(spread, args),
131
132 TypedExpr::Var {
133 name, constructor, ..
134 } => Ok(self.variable(name, constructor)),
135
136 TypedExpr::Sequence { expressions, .. } => self.sequence(expressions),
137
138 TypedExpr::Assignment { value, pattern, .. } => self.assignment(value, pattern),
139
140 TypedExpr::Try {
141 value,
142 then,
143 pattern,
144 ..
145 } => self.try_(value, pattern, then),
146
147 TypedExpr::BinOp {
148 name, left, right, ..
149 } => self.bin_op(name, left, right),
150
151 TypedExpr::Todo {
152 label, location, ..
153 } => Ok(self.todo(label, location)),
154
155 TypedExpr::BitString { segments, .. } => self.bit_string(segments),
156
157 TypedExpr::ModuleSelect {
158 module_alias,
159 label,
160 constructor,
161 ..
162 } => Ok(self.module_select(module_alias, label, constructor)),
163 }?;
164 Ok(if expression.handles_own_return() {
165 document
166 } else {
167 self.wrap_return(document)
168 })
169 }
170
bit_string<'a>(&mut self, segments: &'a [TypedExprBitStringSegment]) -> Output<'a>171 fn bit_string<'a>(&mut self, segments: &'a [TypedExprBitStringSegment]) -> Output<'a> {
172 self.tracker.bit_string_literal_used = true;
173
174 use BitStringSegmentOption as Opt;
175
176 // Collect all the values used in segments.
177 let segments_array = array(segments.iter().map(|segment| {
178 let value = self.not_in_tail_position(|gen| gen.wrap_expression(&segment.value))?;
179 match segment.options.as_slice() {
180 // Ints
181 [] => Ok(value),
182
183 // UTF8 strings
184 [Opt::Utf8 { .. }] => {
185 self.tracker.string_bit_string_segment_used = true;
186 Ok(docvec!["stringBits(", value, ")"])
187 }
188
189 // UTF8 codepoints
190 [Opt::Utf8Codepoint { .. }] => {
191 self.tracker.codepoint_bit_string_segment_used = true;
192 Ok(docvec!["codepointBits(", value, ")"])
193 }
194
195 // Bit strings
196 [Opt::BitString { .. }] => Ok(docvec![value, ".buffer"]),
197
198 // Anything else
199 _ => Err(Error::Unsupported {
200 feature: "This bit string segment option".to_string(),
201 location: segment.location,
202 }),
203 }
204 }))?;
205
206 Ok(docvec!["toBitString(", segments_array, ")"])
207 }
208
wrap_return<'a>(&self, document: Document<'a>) -> Document<'a>209 pub fn wrap_return<'a>(&self, document: Document<'a>) -> Document<'a> {
210 if self.tail_position {
211 docvec!["return ", document, ";"]
212 } else {
213 document
214 }
215 }
216
not_in_tail_position<'a, CompileFn>(&mut self, compile: CompileFn) -> Output<'a> where CompileFn: Fn(&mut Self) -> Output<'a>,217 pub fn not_in_tail_position<'a, CompileFn>(&mut self, compile: CompileFn) -> Output<'a>
218 where
219 CompileFn: Fn(&mut Self) -> Output<'a>,
220 {
221 let tail = self.tail_position;
222 self.tail_position = false;
223 let result = compile(self);
224 self.tail_position = tail;
225 result
226 }
227
228 /// Wrap an expression in an immediately involked function expression if
229 /// required due to being a JS statement
wrap_expression<'a>(&mut self, expression: &'a TypedExpr) -> Output<'a>230 pub fn wrap_expression<'a>(&mut self, expression: &'a TypedExpr) -> Output<'a> {
231 match expression {
232 TypedExpr::Todo { .. }
233 | TypedExpr::Case { .. }
234 | TypedExpr::Sequence { .. }
235 | TypedExpr::Assignment { .. }
236 | TypedExpr::Try { .. } => self.immediately_involked_function_expression(expression),
237 _ => self.expression(expression),
238 }
239 }
240
241 /// Wrap an expression in an immediately involked function expression if
242 /// required due to being a JS statement, or in parens if required due to
243 /// being an operator
binop_child_expression<'a>(&mut self, expression: &'a TypedExpr) -> Output<'a>244 pub fn binop_child_expression<'a>(&mut self, expression: &'a TypedExpr) -> Output<'a> {
245 match expression {
246 TypedExpr::BinOp { name, .. } if name.is_operator_to_wrap() => {
247 Ok(docvec!("(", self.expression(expression)?, ")"))
248 }
249 TypedExpr::Case { .. }
250 | TypedExpr::Sequence { .. }
251 | TypedExpr::Assignment { .. }
252 | TypedExpr::Try { .. } => self.immediately_involked_function_expression(expression),
253 _ => self.expression(expression),
254 }
255 }
256
257 /// Wrap an expression in an immediately involked function expression
immediately_involked_function_expression<'a>( &mut self, expression: &'a TypedExpr, ) -> Output<'a>258 fn immediately_involked_function_expression<'a>(
259 &mut self,
260 expression: &'a TypedExpr,
261 ) -> Output<'a> {
262 let tail = self.tail_position;
263 self.tail_position = true;
264 let current_scope_vars = self.current_scope_vars.clone();
265 let result = self.expression(expression);
266 self.tail_position = tail;
267 self.current_scope_vars = current_scope_vars;
268 Ok(self.immediately_involked_function_expression_document(result?))
269 }
270
271 /// Wrap a document in an immediately involked function expression
immediately_involked_function_expression_document<'a>( &mut self, document: Document<'a>, ) -> Document<'a>272 fn immediately_involked_function_expression_document<'a>(
273 &mut self,
274 document: Document<'a>,
275 ) -> Document<'a> {
276 docvec!(
277 docvec!("(() => {", break_("", " "), document).nest(INDENT),
278 break_("", " "),
279 "})()",
280 )
281 .group()
282 }
283
variable<'a>(&mut self, name: &'a str, constructor: &'a ValueConstructor) -> Document<'a>284 fn variable<'a>(&mut self, name: &'a str, constructor: &'a ValueConstructor) -> Document<'a> {
285 match &constructor.variant {
286 ValueConstructorVariant::Record { arity, .. } => {
287 self.record_constructor(constructor.type_.clone(), None, name, *arity)
288 }
289 ValueConstructorVariant::ModuleFn { .. }
290 | ValueConstructorVariant::ModuleConstant { .. }
291 | ValueConstructorVariant::LocalVariable => self.local_var(name),
292 }
293 }
294
record_constructor<'a>( &mut self, type_: Arc<Type>, qualifier: Option<&'a str>, name: &'a str, arity: usize, ) -> Document<'a>295 fn record_constructor<'a>(
296 &mut self,
297 type_: Arc<Type>,
298 qualifier: Option<&'a str>,
299 name: &'a str,
300 arity: usize,
301 ) -> Document<'a> {
302 if qualifier.is_none() && type_.is_result_constructor() {
303 if name == "Ok" {
304 self.tracker.ok_used = true;
305 } else if name == "Error" {
306 self.tracker.error_used = true;
307 }
308 }
309 if type_.is_bool() && name == "True" {
310 "true".to_doc()
311 } else if type_.is_bool() {
312 "false".to_doc()
313 } else if type_.is_nil() {
314 "undefined".to_doc()
315 } else if arity == 0 {
316 match qualifier {
317 Some(module) => docvec!["new $", module, ".", name, "()"],
318 None => docvec!["new ", name, "()"],
319 }
320 } else {
321 let vars = (0..arity).map(|i| Document::String(format!("var{}", i)));
322 let body = docvec![
323 "return ",
324 construct_record(qualifier, name, vars.clone()),
325 ";"
326 ];
327 docvec!(
328 docvec!(wrap_args(vars), " => {", break_("", " "), body)
329 .nest(INDENT)
330 .append(break_("", " "))
331 .group(),
332 "}",
333 )
334 }
335 }
336
sequence<'a>(&mut self, expressions: &'a [TypedExpr]) -> Output<'a>337 fn sequence<'a>(&mut self, expressions: &'a [TypedExpr]) -> Output<'a> {
338 let count = expressions.len();
339 let mut documents = Vec::with_capacity(count * 3);
340 documents.push(force_break());
341 for (i, expression) in expressions.iter().enumerate() {
342 if i + 1 < count {
343 documents.push(self.not_in_tail_position(|gen| gen.expression(expression))?);
344 if !matches!(
345 expression,
346 TypedExpr::Assignment { .. } | TypedExpr::Case { .. }
347 ) {
348 documents.push(";".to_doc());
349 }
350 documents.push(line());
351 } else {
352 documents.push(self.expression(expression)?);
353 }
354 }
355 Ok(documents.to_doc())
356 }
357
try_<'a>( &mut self, subject: &'a TypedExpr, pattern: &'a TypedPattern, then: &'a TypedExpr, ) -> Output<'a>358 fn try_<'a>(
359 &mut self,
360 subject: &'a TypedExpr,
361 pattern: &'a TypedPattern,
362 then: &'a TypedExpr,
363 ) -> Output<'a> {
364 let mut docs = vec![force_break()];
365
366 // If the subject is not a variable then we will need to save it to a
367 // variable to prevent any side effects from rendering the same
368 // expression twice.
369 let subject_doc = if let TypedExpr::Var { name, .. } = subject {
370 self.local_var(name)
371 } else {
372 let subject = self.not_in_tail_position(|gen| gen.wrap_expression(subject))?;
373 let name = self.next_local_var(pattern::ASSIGNMENT_VAR);
374 docs.push("let ".to_doc());
375 docs.push(name.clone());
376 docs.push(" = ".to_doc());
377 docs.push(subject);
378 docs.push(";".to_doc());
379 docs.push(line());
380 name
381 };
382
383 // We return early if the subject is an error
384 docs.push("if (!".to_doc());
385 docs.push(subject_doc.clone());
386 docs.push(r#".isOk()) return "#.to_doc());
387 docs.push(subject_doc.clone());
388 docs.push(";".to_doc());
389
390 match pattern {
391 // Assign the inner value to a variable if it used
392 TypedPattern::Var { name, .. } => {
393 docs.push(line());
394 docs.push("let ".to_doc());
395 docs.push(self.next_local_var(name));
396 docs.push(" = ".to_doc());
397 docs.push(subject_doc);
398 docs.push("[0];".to_doc());
399 docs.push(lines(2));
400 }
401
402 TypedPattern::Discard { .. } => {
403 docs.push(lines(1));
404 }
405
406 // TODO: At time of writing to support patterns in trys we will need
407 // to adapt the `assignment` method to take a Document as a value
408 // and pass the subject document into that also rather than letting
409 // the pattern generator determine what it should be.
410 pattern => {
411 let subject = subject_doc.append("[0]");
412 let mut pattern_generator = pattern::Generator::new(self);
413 pattern_generator.traverse_pattern(&subject, pattern)?;
414 let compiled = pattern_generator.take_compiled();
415 docs.push(line());
416 docs.push(self.pattern_into_assignment_doc(
417 compiled,
418 subject,
419 pattern.location(),
420 )?);
421 docs.push(lines(2));
422 }
423 }
424
425 // Lastly, whatever comes next
426 docs.push(self.expression(then)?);
427
428 Ok(docs.to_doc())
429 }
430
assignment<'a>(&mut self, value: &'a TypedExpr, pattern: &'a TypedPattern) -> Output<'a>431 fn assignment<'a>(&mut self, value: &'a TypedExpr, pattern: &'a TypedPattern) -> Output<'a> {
432 // If it is a simple assignment to a variable we can generate a normal
433 // JS assignment
434 if let TypedPattern::Var { name, .. } = pattern {
435 // Subject must be rendered before the variable for variable numbering
436 let subject = self.not_in_tail_position(|gen| gen.wrap_expression(value))?;
437 let name = self.next_local_var(name);
438 return Ok(if self.tail_position {
439 docvec![
440 force_break(),
441 "let ",
442 name.clone(),
443 " = ",
444 subject,
445 ";",
446 line(),
447 "return ",
448 name,
449 ";"
450 ]
451 } else {
452 docvec![force_break(), "let ", name, " = ", subject, ";"]
453 });
454 }
455
456 // Otherwise we need to compile the patterns
457 let (subject, subject_assignment) = pattern::assign_subject(self, value);
458 // Value needs to be rendered before traversing pattern to have correctly incremented variables.
459 let value = self.not_in_tail_position(|gen| gen.wrap_expression(value))?;
460 let mut pattern_generator = pattern::Generator::new(self);
461 pattern_generator.traverse_pattern(&subject, pattern)?;
462 let compiled = pattern_generator.take_compiled();
463
464 // If we are in tail position we can return value being assigned
465 let afterwards = if self.tail_position {
466 line()
467 .append("return ")
468 .append(subject_assignment.clone().unwrap_or_else(|| value.clone()))
469 .append(";")
470 } else {
471 nil()
472 };
473
474 // If there is a subject name given create a variable to hold it for
475 // use in patterns
476 let doc = match subject_assignment {
477 Some(name) => {
478 let compiled =
479 self.pattern_into_assignment_doc(compiled, subject, pattern.location())?;
480 docvec!("let ", name, " = ", value, ";", line(), compiled)
481 }
482 None => self.pattern_into_assignment_doc(compiled, subject, pattern.location())?,
483 };
484
485 Ok(docvec!(force_break(), doc.append(afterwards)))
486 }
487
case<'a>( &mut self, location: SrcSpan, subject_values: &'a [TypedExpr], clauses: &'a [TypedClause], ) -> Output<'a>488 fn case<'a>(
489 &mut self,
490 location: SrcSpan,
491 subject_values: &'a [TypedExpr],
492 clauses: &'a [TypedClause],
493 ) -> Output<'a> {
494 let mut possibility_of_no_match = true;
495
496 let (subjects, subject_assignments): (Vec<_>, Vec<_>) =
497 pattern::assign_subjects(self, subject_values)
498 .into_iter()
499 .unzip();
500 let mut gen = pattern::Generator::new(self);
501
502 let mut doc = nil();
503
504 // We wish to be able to know whether this is the first or clause being
505 // processed, so record the index number. We use this instead of
506 // `Iterator.enumerate` because we are using a nested for loop.
507 let mut clause_number = 0;
508 let total_patterns: usize = clauses
509 .iter()
510 .map(|c| c.alternative_patterns.len())
511 .sum::<usize>()
512 + clauses.len();
513
514 // A case has many clauses `pattern -> consequence`
515 for clause in clauses {
516 let multipattern = std::iter::once(&clause.pattern);
517 let multipatterns = multipattern.chain(&clause.alternative_patterns);
518
519 // A clause can have many patterns `pattern, pattern ->...`
520 for multipatterns in multipatterns {
521 let scope = gen.expression_generator.current_scope_vars.clone();
522 let mut compiled = gen.generate(&subjects, multipatterns, clause.guard.as_ref())?;
523 let consequence = gen.expression_generator.expression(&clause.then)?;
524
525 // We've seen one more clause
526 clause_number += 1;
527
528 // Reset the scope now that this clause has finished, causing the
529 // variables to go out of scope.
530 gen.expression_generator.current_scope_vars = scope;
531
532 // If the pattern assigns any variables we need to render assignments
533 let body = if compiled.has_assignments() {
534 let assignments = gen
535 .expression_generator
536 .pattern_take_assignments_doc(&mut compiled);
537 docvec!(assignments, line(), consequence)
538 } else {
539 consequence
540 };
541
542 let is_final_clause = clause_number == total_patterns;
543 let is_first_clause = clause_number == 1;
544 let is_only_clause = is_final_clause && is_first_clause;
545 let is_catch_all = !compiled.has_checks() && clause.guard.is_none();
546
547 if is_catch_all {
548 possibility_of_no_match = false;
549 }
550
551 doc = if is_only_clause && is_catch_all {
552 // If this is the only clause and there are no checks then we can
553 // render just the body as the case does nothing
554 doc.append(body)
555 } else if is_final_clause && is_catch_all {
556 // If this is the final clause and there are no checks then we can
557 // render `else` instead of `else if (...)`
558 doc.append(" else {")
559 .append(docvec!(line(), body).nest(INDENT))
560 .append(line())
561 .append("}")
562 } else {
563 doc.append(if is_first_clause {
564 "if ("
565 } else {
566 " else if ("
567 })
568 .append(
569 gen.expression_generator
570 .pattern_take_checks_doc(&mut compiled, true),
571 )
572 .append(") {")
573 .append(docvec!(line(), body).nest(INDENT))
574 .append(line())
575 .append("}")
576 };
577 }
578 }
579
580 if possibility_of_no_match {
581 // Lastly append an error if no clause matches.
582 // We can remove this when we get exhaustiveness checking.
583 doc = doc
584 .append(" else {")
585 .append(
586 docvec!(line(), self.case_no_match(location, subjects.into_iter())?)
587 .nest(INDENT),
588 )
589 .append(line())
590 .append("}")
591 }
592
593 // If there is a subject name given create a variable to hold it for
594 // use in patterns
595 let subject_assignments: Vec<_> = subject_assignments
596 .into_iter()
597 .zip(subject_values)
598 .flat_map(|(assignment_name, value)| assignment_name.map(|name| (name, value)))
599 .map(|(name, value)| {
600 let value = self.not_in_tail_position(|gen| gen.wrap_expression(value))?;
601 Ok(docvec!("let ", name, " = ", value, ";", line()))
602 })
603 .try_collect()?;
604
605 Ok(docvec![force_break(), subject_assignments, doc])
606 }
607
case_no_match<'a, Subjects>(&mut self, location: SrcSpan, subjects: Subjects) -> Output<'a> where Subjects: IntoIterator<Item = Document<'a>>,608 fn case_no_match<'a, Subjects>(&mut self, location: SrcSpan, subjects: Subjects) -> Output<'a>
609 where
610 Subjects: IntoIterator<Item = Document<'a>>,
611 {
612 Ok(self.throw_error(
613 "case_no_match",
614 "No case clause matched",
615 location,
616 [("values", array(subjects.into_iter().map(Ok))?)],
617 ))
618 }
619
assignment_no_match<'a>(&mut self, location: SrcSpan, subject: Document<'a>) -> Output<'a>620 fn assignment_no_match<'a>(&mut self, location: SrcSpan, subject: Document<'a>) -> Output<'a> {
621 Ok(self.throw_error(
622 "assignment_no_match",
623 "Assignment pattern did not much",
624 location,
625 [("value", subject)],
626 ))
627 }
628
tuple<'a>(&mut self, elements: &'a [TypedExpr]) -> Output<'a>629 fn tuple<'a>(&mut self, elements: &'a [TypedExpr]) -> Output<'a> {
630 self.not_in_tail_position(|gen| {
631 array(elements.iter().map(|element| gen.wrap_expression(element)))
632 })
633 }
634
call<'a>(&mut self, fun: &'a TypedExpr, arguments: &'a [CallArg<TypedExpr>]) -> Output<'a>635 fn call<'a>(&mut self, fun: &'a TypedExpr, arguments: &'a [CallArg<TypedExpr>]) -> Output<'a> {
636 let tail = self.tail_position;
637 self.tail_position = false;
638 let arguments = arguments
639 .iter()
640 .map(|element| self.wrap_expression(&element.value))
641 .try_collect()?;
642 self.tail_position = tail;
643 self.call_with_doc_args(fun, arguments)
644 }
645
call_with_doc_args<'a>( &mut self, fun: &'a TypedExpr, arguments: Vec<Document<'a>>, ) -> Output<'a>646 fn call_with_doc_args<'a>(
647 &mut self,
648 fun: &'a TypedExpr,
649 arguments: Vec<Document<'a>>,
650 ) -> Output<'a> {
651 match fun {
652 // Qualified record construction
653 TypedExpr::ModuleSelect {
654 constructor: ModuleValueConstructor::Record { name, .. },
655 module_alias,
656 ..
657 } => Ok(self.wrap_return(construct_record(Some(module_alias), name, arguments))),
658
659 // Record construction
660 TypedExpr::Var {
661 constructor:
662 ValueConstructor {
663 variant: ValueConstructorVariant::Record { .. },
664 type_,
665 ..
666 },
667 name,
668 ..
669 } => {
670 if type_.is_result_constructor() {
671 if name == "Ok" {
672 self.tracker.ok_used = true;
673 } else if name == "Error" {
674 self.tracker.error_used = true;
675 }
676 }
677 Ok(self.wrap_return(construct_record(None, name, arguments)))
678 }
679
680 // Tail call optimisation. If we are calling the current function
681 // and we are in tail position we can avoid creating a new stack
682 // frame, enabling recursion with constant memory usage.
683 TypedExpr::Var { name, .. }
684 if self.function_name == Some(name)
685 && self.tail_position
686 && self.current_scope_vars.get(name) == Some(&0) =>
687 {
688 let mut docs = Vec::with_capacity(arguments.len() * 4);
689 // Record that tail recursion is happening so that we know to
690 // render the loop at the top level of the function.
691 self.tail_recursion_used = true;
692
693 for (i, (element, argument)) in arguments
694 .into_iter()
695 .zip(self.function_arguments.clone())
696 .enumerate()
697 {
698 if i != 0 {
699 docs.push(line());
700 }
701 // Create an assignment for each variable created by the function arguments
702 if let Some(name) = argument {
703 docs.push(Document::String(maybe_escape_identifier_string(name)));
704 docs.push(" = ".to_doc());
705 }
706 // Render the value given to the function. Even if it is not
707 // assigned we still render it because the expression may
708 // have some side effects.
709 docs.push(element);
710 docs.push(";".to_doc());
711 }
712 Ok(docs.to_doc())
713 }
714
715 _ => {
716 let fun = self.not_in_tail_position(|gen| {
717 let is_fn_literal = matches!(fun, TypedExpr::Fn { .. });
718 let fun = gen.expression(fun)?;
719 if is_fn_literal {
720 Ok(docvec!("(", fun, ")"))
721 } else {
722 Ok(fun)
723 }
724 })?;
725 let arguments = call_arguments(arguments.into_iter().map(Ok))?;
726 Ok(self.wrap_return(docvec![fun, arguments]))
727 }
728 }
729 }
730
fn_<'a>(&mut self, arguments: &'a [TypedArg], body: &'a TypedExpr) -> Output<'a>731 fn fn_<'a>(&mut self, arguments: &'a [TypedArg], body: &'a TypedExpr) -> Output<'a> {
732 // New function, this is now the tail position
733 let tail = self.tail_position;
734 self.tail_position = true;
735 // And there's a new scope
736 let scope = self.current_scope_vars.clone();
737 for name in arguments.iter().flat_map(Arg::get_variable_name) {
738 let _ = self.current_scope_vars.remove(name);
739 }
740
741 // This is a new function so unset the recorded name so that we don't
742 // mistakenly trigger tail call optimisation
743 let mut name = None;
744 std::mem::swap(&mut self.function_name, &mut name);
745
746 // Generate the function body
747 let result = self.expression(body);
748
749 // Reset function name, scope, and tail position tracking
750 self.tail_position = tail;
751 self.current_scope_vars = scope;
752 std::mem::swap(&mut self.function_name, &mut name);
753
754 Ok(docvec!(
755 docvec!(
756 fun_args(arguments, false),
757 " => {",
758 break_("", " "),
759 result?
760 )
761 .nest(INDENT)
762 .append(break_("", " "))
763 .group(),
764 "}",
765 ))
766 }
767
record_access<'a>(&mut self, record: &'a TypedExpr, label: &'a str) -> Output<'a>768 fn record_access<'a>(&mut self, record: &'a TypedExpr, label: &'a str) -> Output<'a> {
769 self.not_in_tail_position(|gen| {
770 let record = gen.wrap_expression(record)?;
771 Ok(docvec![record, ".", label])
772 })
773 }
774
record_update<'a>( &mut self, record: &'a TypedExpr, updates: &'a [TypedRecordUpdateArg], ) -> Output<'a>775 fn record_update<'a>(
776 &mut self,
777 record: &'a TypedExpr,
778 updates: &'a [TypedRecordUpdateArg],
779 ) -> Output<'a> {
780 self.not_in_tail_position(|gen| {
781 let record = gen.wrap_expression(record)?;
782 let fields = updates
783 .iter()
784 .map(|TypedRecordUpdateArg { label, value, .. }| {
785 (label.to_doc(), gen.wrap_expression(value))
786 });
787 let object = try_wrap_object(fields)?;
788 Ok(docvec![record, ".withFields(", object, ")"])
789 })
790 }
791
tuple_index<'a>(&mut self, tuple: &'a TypedExpr, index: u64) -> Output<'a>792 fn tuple_index<'a>(&mut self, tuple: &'a TypedExpr, index: u64) -> Output<'a> {
793 self.not_in_tail_position(|gen| {
794 let tuple = gen.wrap_expression(tuple)?;
795 Ok(docvec![tuple, Document::String(format!("[{}]", index))])
796 })
797 }
798
bin_op<'a>( &mut self, name: &'a BinOp, left: &'a TypedExpr, right: &'a TypedExpr, ) -> Output<'a>799 fn bin_op<'a>(
800 &mut self,
801 name: &'a BinOp,
802 left: &'a TypedExpr,
803 right: &'a TypedExpr,
804 ) -> Output<'a> {
805 match name {
806 BinOp::And => self.print_bin_op(left, right, "&&"),
807 BinOp::Or => self.print_bin_op(left, right, "||"),
808 BinOp::LtInt | BinOp::LtFloat => self.print_bin_op(left, right, "<"),
809 BinOp::LtEqInt | BinOp::LtEqFloat => self.print_bin_op(left, right, "<="),
810 BinOp::Eq => self.equal(left, right, true),
811 BinOp::NotEq => self.equal(left, right, false),
812 BinOp::GtInt | BinOp::GtFloat => self.print_bin_op(left, right, ">"),
813 BinOp::GtEqInt | BinOp::GtEqFloat => self.print_bin_op(left, right, ">="),
814 BinOp::AddInt | BinOp::AddFloat => self.print_bin_op(left, right, "+"),
815 BinOp::SubInt | BinOp::SubFloat => self.print_bin_op(left, right, "-"),
816 BinOp::MultInt => self.mult_int(left, right),
817 BinOp::MultFloat => self.print_bin_op(left, right, "*"),
818 BinOp::ModuloInt => self.print_bin_op(left, right, "%"),
819 BinOp::DivInt => self.div_int(left, right),
820 BinOp::DivFloat => self.div_float(left, right),
821 }
822 }
823
mult_int<'a>(&mut self, left: &'a TypedExpr, right: &'a TypedExpr) -> Output<'a>824 fn mult_int<'a>(&mut self, left: &'a TypedExpr, right: &'a TypedExpr) -> Output<'a> {
825 let left = self.not_in_tail_position(|gen| gen.expression(left))?;
826 let right = self.not_in_tail_position(|gen| gen.expression(right))?;
827 Ok(docvec!("Math.imul", wrap_args([left, right])))
828 }
829
div_int<'a>(&mut self, left: &'a TypedExpr, right: &'a TypedExpr) -> Output<'a>830 fn div_int<'a>(&mut self, left: &'a TypedExpr, right: &'a TypedExpr) -> Output<'a> {
831 let left = self.not_in_tail_position(|gen| gen.expression(left))?;
832 let right = self.not_in_tail_position(|gen| gen.expression(right))?;
833 self.tracker.int_division_used = true;
834 Ok(docvec!("divideInt", wrap_args([left, right])))
835 }
836
div_float<'a>(&mut self, left: &'a TypedExpr, right: &'a TypedExpr) -> Output<'a>837 fn div_float<'a>(&mut self, left: &'a TypedExpr, right: &'a TypedExpr) -> Output<'a> {
838 let left = self.not_in_tail_position(|gen| gen.expression(left))?;
839 let right = self.not_in_tail_position(|gen| gen.expression(right))?;
840 self.tracker.float_division_used = true;
841 Ok(docvec!("divideFloat", wrap_args([left, right])))
842 }
843
equal<'a>( &mut self, left: &'a TypedExpr, right: &'a TypedExpr, should_be_equal: bool, ) -> Output<'a>844 fn equal<'a>(
845 &mut self,
846 left: &'a TypedExpr,
847 right: &'a TypedExpr,
848 should_be_equal: bool,
849 ) -> Output<'a> {
850 // If it is a simple scalar type then we can use JS' reference identity
851 if is_js_scalar(left.type_()) {
852 let left_doc = self.not_in_tail_position(|gen| gen.binop_child_expression(left))?;
853 let right_doc = self.not_in_tail_position(|gen| gen.binop_child_expression(right))?;
854 let operator = if should_be_equal { " === " } else { " !== " };
855 return Ok(docvec!(left_doc, operator, right_doc));
856 }
857
858 // Other types must be compared using structural equality
859 let left = self.not_in_tail_position(|gen| gen.wrap_expression(left))?;
860 let right = self.not_in_tail_position(|gen| gen.wrap_expression(right))?;
861 Ok(self.prelude_equal_call(should_be_equal, left, right))
862 }
863
prelude_equal_call<'a>( &mut self, should_be_equal: bool, left: Document<'a>, right: Document<'a>, ) -> Document<'a>864 pub(super) fn prelude_equal_call<'a>(
865 &mut self,
866 should_be_equal: bool,
867 left: Document<'a>,
868 right: Document<'a>,
869 ) -> Document<'a> {
870 // Record that we need to import the prelude's isEqual function into the module
871 self.tracker.object_equality_used = true;
872 // Construct the call
873 let args = wrap_args([left, right]);
874 let operator = if should_be_equal {
875 "isEqual"
876 } else {
877 "!isEqual"
878 };
879 docvec!(operator, args)
880 }
881
print_bin_op<'a>( &mut self, left: &'a TypedExpr, right: &'a TypedExpr, op: &'a str, ) -> Output<'a>882 fn print_bin_op<'a>(
883 &mut self,
884 left: &'a TypedExpr,
885 right: &'a TypedExpr,
886 op: &'a str,
887 ) -> Output<'a> {
888 let left = self.not_in_tail_position(|gen| gen.binop_child_expression(left))?;
889 let right = self.not_in_tail_position(|gen| gen.binop_child_expression(right))?;
890 Ok(docvec!(left, " ", op, " ", right))
891 }
892
todo<'a>(&mut self, message: &'a Option<String>, location: &'a SrcSpan) -> Document<'a>893 fn todo<'a>(&mut self, message: &'a Option<String>, location: &'a SrcSpan) -> Document<'a> {
894 let tail_position = self.tail_position;
895 self.tail_position = false;
896
897 let message = message
898 .as_deref()
899 .unwrap_or("This has not yet been implemented");
900 let doc = self.throw_error("todo", message, *location, vec![]);
901
902 // Reset tail position so later values are returned as needed. i.e.
903 // following clauses in a case expression.
904 self.tail_position = tail_position;
905
906 doc
907 }
908
throw_error<'a, Fields>( &mut self, error_name: &'a str, message: &'a str, location: SrcSpan, fields: Fields, ) -> Document<'a> where Fields: IntoIterator<Item = (&'a str, Document<'a>)>,909 fn throw_error<'a, Fields>(
910 &mut self,
911 error_name: &'a str,
912 message: &'a str,
913 location: SrcSpan,
914 fields: Fields,
915 ) -> Document<'a>
916 where
917 Fields: IntoIterator<Item = (&'a str, Document<'a>)>,
918 {
919 self.tracker.throw_error_used = true;
920 let module = Document::String(self.module_name.join("/")).surround('"', '"');
921 // TODO switch to use `string(self.function_name)`
922 // This will require resolving the
923 // difference in lifetimes 'module and 'a.
924 let function = Document::String(self.function_name.unwrap_or_default().to_string())
925 .surround("\"", "\"");
926 let line = self.line_numbers.line_number(location.start).to_doc();
927 let fields = wrap_object(fields.into_iter().map(|(k, v)| (k.to_doc(), Some(v))));
928 docvec![
929 "throwError",
930 wrap_args([
931 string(error_name),
932 module,
933 line,
934 function,
935 string(message),
936 fields
937 ]),
938 ";"
939 ]
940 }
941
module_select<'a>( &mut self, module: &'a str, label: &'a str, constructor: &'a ModuleValueConstructor, ) -> Document<'a>942 fn module_select<'a>(
943 &mut self,
944 module: &'a str,
945 label: &'a str,
946 constructor: &'a ModuleValueConstructor,
947 ) -> Document<'a> {
948 match constructor {
949 ModuleValueConstructor::Fn | ModuleValueConstructor::Constant { .. } => {
950 docvec!["$", module, ".", maybe_escape_identifier_doc(label)]
951 }
952
953 ModuleValueConstructor::Record {
954 name, arity, type_, ..
955 } => self.record_constructor(type_.clone(), Some(module), name, *arity),
956 }
957 }
958
pattern_into_assignment_doc<'a>( &mut self, compiled_pattern: CompiledPattern<'a>, subject: Document<'a>, location: SrcSpan, ) -> Output<'a>959 fn pattern_into_assignment_doc<'a>(
960 &mut self,
961 compiled_pattern: CompiledPattern<'a>,
962 subject: Document<'a>,
963 location: SrcSpan,
964 ) -> Output<'a> {
965 if compiled_pattern.checks.is_empty() {
966 return Ok(Self::pattern_assignments_doc(compiled_pattern.assignments));
967 }
968 if compiled_pattern.assignments.is_empty() {
969 return self.pattern_checks_or_throw_doc(compiled_pattern.checks, subject, location);
970 }
971
972 Ok(docvec![
973 self.pattern_checks_or_throw_doc(compiled_pattern.checks, subject, location)?,
974 line(),
975 Self::pattern_assignments_doc(compiled_pattern.assignments)
976 ])
977 }
978
pattern_checks_or_throw_doc<'a>( &mut self, checks: Vec<pattern::Check<'a>>, subject: Document<'a>, location: SrcSpan, ) -> Output<'a>979 fn pattern_checks_or_throw_doc<'a>(
980 &mut self,
981 checks: Vec<pattern::Check<'a>>,
982 subject: Document<'a>,
983 location: SrcSpan,
984 ) -> Output<'a> {
985 let checks = self.pattern_checks_doc(checks, false);
986 Ok(docvec![
987 "if (",
988 docvec![break_("", ""), checks].nest(INDENT),
989 break_("", ""),
990 ") {",
991 docvec![line(), self.assignment_no_match(location, subject)?].nest(INDENT),
992 line(),
993 "}",
994 ]
995 .group())
996 }
997
pattern_assignments_doc(assignments: Vec<pattern::Assignment<'_>>) -> Document<'_>998 fn pattern_assignments_doc(assignments: Vec<pattern::Assignment<'_>>) -> Document<'_> {
999 let assignments = assignments.into_iter().map(pattern::Assignment::into_doc);
1000 concat(Itertools::intersperse(assignments, line()))
1001 }
1002
pattern_take_assignments_doc<'a>( &self, compiled_pattern: &mut CompiledPattern<'a>, ) -> Document<'a>1003 fn pattern_take_assignments_doc<'a>(
1004 &self,
1005 compiled_pattern: &mut CompiledPattern<'a>,
1006 ) -> Document<'a> {
1007 let assignments = std::mem::take(&mut compiled_pattern.assignments);
1008 Self::pattern_assignments_doc(assignments)
1009 }
1010
pattern_take_checks_doc<'a>( &self, compiled_pattern: &mut CompiledPattern<'a>, match_desired: bool, ) -> Document<'a>1011 fn pattern_take_checks_doc<'a>(
1012 &self,
1013 compiled_pattern: &mut CompiledPattern<'a>,
1014 match_desired: bool,
1015 ) -> Document<'a> {
1016 let checks = std::mem::take(&mut compiled_pattern.checks);
1017 self.pattern_checks_doc(checks, match_desired)
1018 }
1019
pattern_checks_doc<'a>( &self, checks: Vec<pattern::Check<'a>>, match_desired: bool, ) -> Document<'a>1020 fn pattern_checks_doc<'a>(
1021 &self,
1022 checks: Vec<pattern::Check<'a>>,
1023 match_desired: bool,
1024 ) -> Document<'a> {
1025 if checks.is_empty() {
1026 return "true".to_doc();
1027 };
1028 let operator = if match_desired {
1029 break_(" &&", " && ")
1030 } else {
1031 break_(" ||", " || ")
1032 };
1033
1034 concat(Itertools::intersperse(
1035 checks
1036 .into_iter()
1037 .map(|check| check.into_doc(match_desired)),
1038 operator,
1039 ))
1040 }
1041 }
1042
int(value: &str) -> Document<'_>1043 pub fn int(value: &str) -> Document<'_> {
1044 value.to_doc()
1045 }
1046
float(value: &str) -> Document<'_>1047 pub fn float(value: &str) -> Document<'_> {
1048 value.to_doc()
1049 }
1050
constant_expression<'a>( tracker: &mut UsageTracker, expression: &'a TypedConstant, ) -> Output<'a>1051 pub(crate) fn constant_expression<'a>(
1052 tracker: &mut UsageTracker,
1053 expression: &'a TypedConstant,
1054 ) -> Output<'a> {
1055 match expression {
1056 Constant::Int { value, .. } => Ok(int(value)),
1057 Constant::Float { value, .. } => Ok(float(value)),
1058 Constant::String { value, .. } => Ok(string(value)),
1059 Constant::Tuple { elements, .. } => {
1060 array(elements.iter().map(|e| constant_expression(tracker, e)))
1061 }
1062
1063 Constant::List { elements, .. } => {
1064 tracker.list_used = true;
1065 list(
1066 elements.iter().map(|e| constant_expression(tracker, e)),
1067 None,
1068 )
1069 }
1070
1071 Constant::Record { typ, name, .. } if typ.is_bool() && name == "True" => {
1072 Ok("true".to_doc())
1073 }
1074 Constant::Record { typ, name, .. } if typ.is_bool() && name == "False" => {
1075 Ok("false".to_doc())
1076 }
1077 Constant::Record { typ, .. } if typ.is_nil() => Ok("undefined".to_doc()),
1078
1079 Constant::Record {
1080 tag,
1081 typ,
1082 args,
1083 module,
1084 ..
1085 } => {
1086 if typ.is_result() {
1087 if tag == "Ok" {
1088 tracker.ok_used = true;
1089 } else {
1090 tracker.error_used = true;
1091 }
1092 }
1093 let field_values: Vec<_> = args
1094 .iter()
1095 .map(|arg| constant_expression(tracker, &arg.value))
1096 .try_collect()?;
1097 Ok(construct_record(module.as_deref(), tag, field_values))
1098 }
1099
1100 Constant::BitString { location, .. } => Err(Error::Unsupported {
1101 feature: "Bit string syntax".to_string(),
1102 location: *location,
1103 }),
1104 }
1105 }
1106
string(value: &str) -> Document<'_>1107 pub fn string(value: &str) -> Document<'_> {
1108 if value.contains('\n') {
1109 Document::String(value.replace('\n', r#"\n"#)).surround("\"", "\"")
1110 } else {
1111 value.to_doc().surround("\"", "\"")
1112 }
1113 }
1114
array<'a, Elements: IntoIterator<Item = Output<'a>>>(elements: Elements) -> Output<'a>1115 pub fn array<'a, Elements: IntoIterator<Item = Output<'a>>>(elements: Elements) -> Output<'a> {
1116 let elements = Itertools::intersperse(elements.into_iter(), Ok(break_(",", ", ")))
1117 .collect::<Result<Vec<_>, _>>()?
1118 .to_doc();
1119 Ok(docvec![
1120 "[",
1121 docvec![break_("", ""), elements].nest(INDENT),
1122 break_(",", ""),
1123 "]"
1124 ]
1125 .group())
1126 }
1127
list<'a, I: IntoIterator<Item = Output<'a>>>( elements: I, tail: Option<Document<'a>>, ) -> Output<'a> where I::IntoIter: DoubleEndedIterator + ExactSizeIterator,1128 fn list<'a, I: IntoIterator<Item = Output<'a>>>(
1129 elements: I,
1130 tail: Option<Document<'a>>,
1131 ) -> Output<'a>
1132 where
1133 I::IntoIter: DoubleEndedIterator + ExactSizeIterator,
1134 {
1135 let array = array(elements);
1136 if let Some(tail) = tail {
1137 let args = [array, Ok(tail)];
1138 Ok(docvec!["toList", call_arguments(args)?])
1139 } else {
1140 Ok(docvec!["toList(", array?, ")"])
1141 }
1142 }
1143
call_arguments<'a, Elements: IntoIterator<Item = Output<'a>>>(elements: Elements) -> Output<'a>1144 fn call_arguments<'a, Elements: IntoIterator<Item = Output<'a>>>(elements: Elements) -> Output<'a> {
1145 let elements = Itertools::intersperse(elements.into_iter(), Ok(break_(",", ", ")))
1146 .collect::<Result<Vec<_>, _>>()?
1147 .to_doc();
1148 Ok(docvec![
1149 "(",
1150 docvec![break_("", ""), elements].nest(INDENT),
1151 break_(",", ""),
1152 ")"
1153 ]
1154 .group())
1155 }
1156
construct_record<'a>( module: Option<&'a str>, name: &'a str, arguments: impl IntoIterator<Item = Document<'a>>, ) -> Document<'a>1157 fn construct_record<'a>(
1158 module: Option<&'a str>,
1159 name: &'a str,
1160 arguments: impl IntoIterator<Item = Document<'a>>,
1161 ) -> Document<'a> {
1162 let mut any_arguments = false;
1163 let arguments = concat(Itertools::intersperse(
1164 arguments.into_iter().map(|a| {
1165 any_arguments = true;
1166 a
1167 }),
1168 break_(",", ", "),
1169 ));
1170 let arguments = docvec![break_("", ""), arguments].nest(INDENT);
1171 let name = if let Some(module) = module {
1172 docvec!["$", module, ".", name]
1173 } else {
1174 name.to_doc()
1175 };
1176 if any_arguments {
1177 docvec!["new ", name, "(", arguments, break_(",", ""), ")"].group()
1178 } else {
1179 docvec!["new ", name, "()"]
1180 }
1181 }
1182
1183 impl TypedExpr {
handles_own_return(&self) -> bool1184 fn handles_own_return(&self) -> bool {
1185 matches!(
1186 self,
1187 TypedExpr::Try { .. }
1188 | TypedExpr::Todo { .. }
1189 | TypedExpr::Call { .. }
1190 | TypedExpr::Case { .. }
1191 | TypedExpr::Sequence { .. }
1192 | TypedExpr::Assignment { .. }
1193 )
1194 }
1195 }
1196
1197 impl BinOp {
is_operator_to_wrap(&self) -> bool1198 fn is_operator_to_wrap(&self) -> bool {
1199 match self {
1200 BinOp::And
1201 | BinOp::Or
1202 | BinOp::Eq
1203 | BinOp::NotEq
1204 | BinOp::LtInt
1205 | BinOp::LtEqInt
1206 | BinOp::LtFloat
1207 | BinOp::LtEqFloat
1208 | BinOp::GtEqInt
1209 | BinOp::GtInt
1210 | BinOp::GtEqFloat
1211 | BinOp::GtFloat
1212 | BinOp::AddInt
1213 | BinOp::AddFloat
1214 | BinOp::SubInt
1215 | BinOp::SubFloat
1216 | BinOp::MultFloat
1217 | BinOp::DivInt
1218 | BinOp::DivFloat
1219 | BinOp::ModuloInt => true,
1220 BinOp::MultInt => false,
1221 }
1222 }
1223 }
1224
is_js_scalar(t: Arc<Type>) -> bool1225 pub fn is_js_scalar(t: Arc<Type>) -> bool {
1226 t.is_int() || t.is_float() || t.is_bool() || t.is_nil() || t.is_string()
1227 }
1228