Fork of daniellemaywood.uk/gleam — Wasm codegen work
85 kB
2648 lines
1// TODO: Refactor this module to be methods on structs rather than free
2// functions with a load of arguments. See the JavaScript code generator and the
3// formatter for examples.
4
5mod pattern;
6#[cfg(test)]
7mod tests;
8
9use crate::build::{Target, module_erlang_name};
10use crate::strings::convert_string_escape_chars;
11use crate::type_::is_prelude_module;
12use crate::{
13 Result,
14 ast::{CustomType, Function, Import, ModuleConstant, TypeAlias, *},
15 docvec,
16 line_numbers::LineNumbers,
17 pretty::*,
18 type_::{
19 ModuleValueConstructor, PatternConstructor, Type, TypeVar, TypedCallArg, ValueConstructor,
20 ValueConstructorVariant,
21 },
22};
23use camino::Utf8Path;
24use ecow::{EcoString, eco_format};
25use heck::ToSnakeCase;
26use im::HashSet;
27use itertools::Itertools;
28use pattern::pattern;
29use regex::{Captures, Regex};
30use std::sync::OnceLock;
31use std::{collections::HashMap, ops::Deref, str::FromStr, sync::Arc};
32use vec1::Vec1;
33
34const INDENT: isize = 4;
35const MAX_COLUMNS: isize = 80;
36
37fn module_name_to_erlang(module: &str) -> Document<'_> {
38 EcoString::from(module.replace('/', "@")).to_doc()
39}
40
41fn module_name_atom(module: &str) -> Document<'static> {
42 atom_string(module.replace('/', "@"))
43}
44
45#[derive(Debug, Clone)]
46struct Env<'a> {
47 module: &'a str,
48 function: &'a str,
49 src_path: &'a Utf8Path,
50 project_root: &'a Utf8Path,
51 line_numbers: &'a LineNumbers,
52 needs_function_docs: bool,
53 echo_used: bool,
54 current_scope_vars: im::HashMap<String, usize>,
55 erl_function_scope_vars: im::HashMap<String, usize>,
56}
57
58impl<'env> Env<'env> {
59 pub fn new(
60 module: &'env str,
61 src_path: &'env Utf8Path,
62 project_root: &'env Utf8Path,
63 function: &'env str,
64 line_numbers: &'env LineNumbers,
65 ) -> Self {
66 let vars: im::HashMap<_, _> = std::iter::once(("_".into(), 0)).collect();
67 Self {
68 current_scope_vars: vars.clone(),
69 erl_function_scope_vars: vars,
70 needs_function_docs: false,
71 echo_used: false,
72 src_path,
73 project_root,
74 line_numbers,
75 function,
76 module,
77 }
78 }
79
80 pub fn local_var_name<'a>(&mut self, name: &str) -> Document<'a> {
81 match self.current_scope_vars.get(name) {
82 None => {
83 let _ = self.current_scope_vars.insert(name.to_string(), 0);
84 let _ = self.erl_function_scope_vars.insert(name.to_string(), 0);
85 variable_name(name).to_doc()
86 }
87 Some(0) => variable_name(name).to_doc(),
88 Some(n) => {
89 use std::fmt::Write;
90 let mut name = variable_name(name);
91 write!(name, "@{n}").expect("pushing number suffix to name");
92 name.to_doc()
93 }
94 }
95 }
96
97 pub fn next_local_var_name<'a>(&mut self, name: &str) -> Document<'a> {
98 let next = self.erl_function_scope_vars.get(name).map_or(0, |i| i + 1);
99 let _ = self.erl_function_scope_vars.insert(name.to_string(), next);
100 let _ = self.current_scope_vars.insert(name.to_string(), next);
101 self.local_var_name(name)
102 }
103}
104
105pub fn records(module: &TypedModule) -> Vec<(&str, String)> {
106 module
107 .definitions
108 .iter()
109 .filter_map(|s| match s {
110 Definition::CustomType(CustomType {
111 publicity: Publicity::Public,
112 constructors,
113 ..
114 }) => Some(constructors),
115 _ => None,
116 })
117 .flatten()
118 .filter(|constructor| !constructor.arguments.is_empty())
119 .filter_map(|constructor| {
120 constructor
121 .arguments
122 .iter()
123 .map(
124 |RecordConstructorArg {
125 label,
126 ast: _,
127 location: _,
128 type_,
129 ..
130 }| {
131 label
132 .as_ref()
133 .map(|(_, label)| (label.as_str(), type_.clone()))
134 },
135 )
136 .collect::<Option<Vec<_>>>()
137 .map(|fields| (constructor.name.as_str(), fields))
138 })
139 .map(|(name, fields)| (name, record_definition(name, &fields)))
140 .collect()
141}
142
143pub fn record_definition(name: &str, fields: &[(&str, Arc<Type>)]) -> String {
144 let name = &name.to_snake_case();
145 let type_printer = TypePrinter::new("").var_as_any();
146 let fields = fields.iter().map(move |(name, type_)| {
147 let type_ = type_printer.print(type_);
148 docvec![atom_string((*name).to_string()), " :: ", type_.group()]
149 });
150 let fields = break_("", "")
151 .append(join(fields, break_(",", ", ")))
152 .nest(INDENT)
153 .append(break_("", ""))
154 .group();
155 docvec![
156 "-record(",
157 atom_string(name.to_string()),
158 ", {",
159 fields,
160 "}).",
161 line()
162 ]
163 .to_pretty_string(MAX_COLUMNS)
164}
165
166pub fn module<'a>(
167 module: &'a TypedModule,
168 line_numbers: &'a LineNumbers,
169 root: &'a Utf8Path,
170) -> Result<String> {
171 Ok(module_document(module, line_numbers, root)?.to_pretty_string(MAX_COLUMNS))
172}
173
174fn module_document<'a>(
175 module: &'a TypedModule,
176 line_numbers: &'a LineNumbers,
177 root: &'a Utf8Path,
178) -> Result<Document<'a>> {
179 let mut exports = vec![];
180 let mut type_defs = vec![];
181 let mut type_exports = vec![];
182
183 let header = "-module("
184 .to_doc()
185 .append(module.erlang_name())
186 .append(").")
187 .append(line());
188
189 // We need to know which private functions are referenced in importable
190 // constants so that we can export them anyway in the generated Erlang.
191 // This is because otherwise when the constant is used in another module it
192 // would result in an error as it tries to reference this private function.
193 let overridden_publicity = find_private_functions_referenced_in_importable_constants(module);
194
195 for s in &module.definitions {
196 register_imports(
197 s,
198 &mut exports,
199 &mut type_exports,
200 &mut type_defs,
201 &module.name,
202 &overridden_publicity,
203 );
204 }
205
206 let exports = match (!exports.is_empty(), !type_exports.is_empty()) {
207 (false, false) => return Ok(header),
208 (true, false) => "-export(["
209 .to_doc()
210 .append(join(exports, ", ".to_doc()))
211 .append("]).")
212 .append(lines(2)),
213
214 (true, true) => "-export(["
215 .to_doc()
216 .append(join(exports, ", ".to_doc()))
217 .append("]).")
218 .append(line())
219 .append("-export_type([")
220 .to_doc()
221 .append(join(type_exports, ", ".to_doc()))
222 .append("]).")
223 .append(lines(2)),
224
225 (false, true) => "-export_type(["
226 .to_doc()
227 .append(join(type_exports, ", ".to_doc()))
228 .append("]).")
229 .append(lines(2)),
230 };
231
232 let type_defs = if type_defs.is_empty() {
233 nil()
234 } else {
235 join(type_defs, lines(2)).append(lines(2))
236 };
237
238 let src_path_full = &module.type_info.src_path;
239 let src_path_relative = src_path_full.strip_prefix(root).unwrap_or(src_path_full);
240
241 let mut needs_function_docs = false;
242 let mut echo_used = false;
243 let mut statements = Vec::with_capacity(module.definitions.len());
244 for definition in module.definitions.iter() {
245 if let Some((statement_document, env)) = module_statement(
246 definition,
247 &module.name,
248 module.type_info.is_internal,
249 line_numbers,
250 src_path_relative,
251 root,
252 ) {
253 needs_function_docs = needs_function_docs || env.needs_function_docs;
254 echo_used = echo_used || env.echo_used;
255 statements.push(statement_document);
256 }
257 }
258
259 let module_doc = if module.type_info.is_internal {
260 Some(hidden_module_doc().append(lines(2)))
261 } else if module.documentation.is_empty() {
262 None
263 } else {
264 Some(module_doc(&module.documentation).append(lines(2)))
265 };
266
267 // We're going to need the documentation directives if any of the module's
268 // functions need it, or if the module has a module comment that we want to
269 // include in the generated Erlang source, or if the module is internal.
270 let needs_doc_directive = needs_function_docs || module_doc.is_some();
271 let documentation_directive = if needs_doc_directive {
272 "-if(?OTP_RELEASE >= 27).
273-define(MODULEDOC(Str), -moduledoc(Str)).
274-define(DOC(Str), -doc(Str)).
275-else.
276-define(MODULEDOC(Str), -compile([])).
277-define(DOC(Str), -compile([])).
278-endif."
279 .to_doc()
280 .append(lines(2))
281 } else {
282 nil()
283 };
284
285 let module = docvec![
286 header,
287 "-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).",
288 lines(2),
289 exports,
290 documentation_directive,
291 module_doc,
292 type_defs,
293 join(statements, lines(2)),
294 ];
295
296 let module = if echo_used {
297 module
298 .append(lines(2))
299 .append(std::include_str!("../templates/echo.erl").to_doc())
300 } else {
301 module
302 };
303
304 Ok(module.append(line()))
305}
306
307fn register_imports(
308 s: &TypedDefinition,
309 exports: &mut Vec<Document<'_>>,
310 type_exports: &mut Vec<Document<'_>>,
311 type_defs: &mut Vec<Document<'_>>,
312 module_name: &str,
313 overridden_publicity: &HashSet<EcoString>,
314) {
315 match s {
316 Definition::Function(Function {
317 publicity,
318 name: Some((_, name)),
319 arguments: args,
320 implementations,
321 ..
322 }) if publicity.is_importable() || overridden_publicity.contains(name) => {
323 // If the function isn't for this target then don't attempt to export it
324 if implementations.supports(Target::Erlang) {
325 let function_name = escape_erlang_existing_name(name);
326 exports.push(
327 atom_string(function_name.to_string())
328 .append("/")
329 .append(args.len()),
330 )
331 }
332 }
333
334 Definition::CustomType(CustomType {
335 name,
336 constructors,
337 typed_parameters,
338 opaque,
339 ..
340 }) => {
341 // Erlang doesn't allow phantom type variables in type definitions but gleam does
342 // so we check the type declaratinon against its constroctors and generate a phantom
343 // value that uses the unused type variables.
344 let type_var_usages = collect_type_var_usages(HashMap::new(), typed_parameters);
345 let mut constructor_var_usages = HashMap::new();
346 for c in constructors {
347 constructor_var_usages = collect_type_var_usages(
348 constructor_var_usages,
349 c.arguments.iter().map(|a| &a.type_),
350 );
351 }
352 let phantom_vars: Vec<_> = type_var_usages
353 .keys()
354 .filter(|&id| !constructor_var_usages.contains_key(id))
355 .sorted()
356 .map(|&id| Type::Var {
357 type_: Arc::new(std::cell::RefCell::new(TypeVar::Generic { id })),
358 })
359 .collect();
360 let phantom_vars_constructor = if !phantom_vars.is_empty() {
361 let type_printer = TypePrinter::new(module_name);
362 Some(tuple(
363 std::iter::once("gleam_phantom".to_doc())
364 .chain(phantom_vars.iter().map(|pv| type_printer.print(pv))),
365 ))
366 } else {
367 None
368 };
369 // Type Exports
370 type_exports.push(
371 erl_safe_type_name(name.to_snake_case())
372 .to_doc()
373 .append("/")
374 .append(typed_parameters.len()),
375 );
376 // Type definitions
377 let definition = if constructors.is_empty() {
378 let constructors =
379 std::iter::once("any()".to_doc()).chain(phantom_vars_constructor);
380 join(constructors, break_(" |", " | "))
381 } else {
382 let constructors = constructors
383 .iter()
384 .map(|c| {
385 let name = atom_string(c.name.to_snake_case());
386 if c.arguments.is_empty() {
387 name
388 } else {
389 let type_printer = TypePrinter::new(module_name);
390 let args = c.arguments.iter().map(|a| type_printer.print(&a.type_));
391 tuple(std::iter::once(name).chain(args))
392 }
393 })
394 .chain(phantom_vars_constructor);
395 join(constructors, break_(" |", " | "))
396 }
397 .nest(INDENT);
398 let type_printer = TypePrinter::new(module_name);
399 let params = join(
400 typed_parameters.iter().map(|a| type_printer.print(a)),
401 ", ".to_doc(),
402 );
403 let doc = if *opaque { "-opaque " } else { "-type " }
404 .to_doc()
405 .append(erl_safe_type_name(name.to_snake_case()))
406 .append("(")
407 .append(params)
408 .append(") :: ")
409 .append(definition)
410 .group()
411 .append(".");
412 type_defs.push(doc);
413 }
414
415 Definition::Function(Function { .. })
416 | Definition::Import(Import { .. })
417 | Definition::TypeAlias(TypeAlias { .. })
418 | Definition::ModuleConstant(ModuleConstant { .. }) => (),
419 }
420}
421
422fn module_statement<'a>(
423 statement: &'a TypedDefinition,
424 module: &'a str,
425 is_internal_module: bool,
426 line_numbers: &'a LineNumbers,
427 src_path: &'a Utf8Path,
428 project_root: &'a Utf8Path,
429) -> Option<(Document<'a>, Env<'a>)> {
430 match statement {
431 Definition::TypeAlias(TypeAlias { .. })
432 | Definition::CustomType(CustomType { .. })
433 | Definition::Import(Import { .. })
434 | Definition::ModuleConstant(ModuleConstant { .. }) => None,
435
436 Definition::Function(function) => module_function(
437 function,
438 module,
439 is_internal_module,
440 line_numbers,
441 src_path,
442 project_root,
443 ),
444 }
445}
446
447fn module_function<'a>(
448 function: &'a TypedFunction,
449 module: &'a str,
450 is_internal_module: bool,
451 line_numbers: &'a LineNumbers,
452 src_path: &'a Utf8Path,
453 project_root: &'a Utf8Path,
454) -> Option<(Document<'a>, Env<'a>)> {
455 // Private external functions don't need to render anything, the underlying
456 // Erlang implementation is used directly at the call site.
457 if function.external_erlang.is_some() && function.publicity.is_private() {
458 return None;
459 }
460
461 // If the function has no suitable Erlang implementation then there is nothing
462 // to generate for it.
463 if !function.implementations.supports(Target::Erlang) {
464 return None;
465 }
466
467 let (_, function_name) = function
468 .name
469 .as_ref()
470 .expect("A module's function must be named");
471 let function_name = escape_erlang_existing_name(function_name);
472 let file_attribute = file_attribute(src_path, function, line_numbers);
473
474 let mut env = Env::new(module, src_path, project_root, function_name, line_numbers);
475 let var_usages = collect_type_var_usages(
476 HashMap::new(),
477 std::iter::once(&function.return_type).chain(function.arguments.iter().map(|a| &a.type_)),
478 );
479 let type_printer = TypePrinter::new(module).with_var_usages(&var_usages);
480 let args_spec = function
481 .arguments
482 .iter()
483 .map(|a| type_printer.print(&a.type_));
484 let return_spec = type_printer.print(&function.return_type);
485
486 let spec = fun_spec(function_name, args_spec, return_spec);
487 let arguments = if function.external_erlang.is_some() {
488 external_fun_args(&function.arguments, &mut env)
489 } else {
490 fun_args(&function.arguments, &mut env)
491 };
492
493 let body = function
494 .external_erlang
495 .as_ref()
496 .map(|(module, function, _location)| {
497 docvec![
498 atom(module),
499 ":",
500 atom(escape_erlang_existing_name(function)),
501 arguments.clone()
502 ]
503 })
504 .unwrap_or_else(|| statement_sequence(&function.body, &mut env));
505
506 let attributes = file_attribute;
507 let attributes = if is_internal_module || function.publicity.is_internal() {
508 // If a function is marked as internal or comes from an internal module
509 // we want to hide its documentation in the Erlang shell!
510 // So the doc directive will look like this: `-doc(false).`
511 env.needs_function_docs = true;
512 docvec![attributes, line(), hidden_function_doc()]
513 } else {
514 match &function.documentation {
515 Some((_, documentation)) => {
516 env.needs_function_docs = true;
517 let doc_lines = documentation
518 .trim_end()
519 .split('\n')
520 .map(EcoString::from)
521 .collect_vec();
522 docvec![attributes, line(), function_doc(&doc_lines)]
523 }
524 _ => attributes,
525 }
526 };
527
528 Some((
529 docvec![
530 attributes,
531 line(),
532 spec,
533 atom_string(escape_erlang_existing_name(function_name).to_string()),
534 arguments,
535 " ->",
536 line().append(body).nest(INDENT).group(),
537 ".",
538 ],
539 env,
540 ))
541}
542
543fn file_attribute<'a>(
544 path: &'a Utf8Path,
545 function: &'a TypedFunction,
546 line_numbers: &'a LineNumbers,
547) -> Document<'a> {
548 let line = line_numbers.line_number(function.location.start);
549 let path = EcoString::from(path.as_str()).replace("\\", "\\\\");
550 docvec!["-file(\"", path, "\", ", line, ")."]
551}
552
553enum DocCommentKind {
554 Module,
555 Function,
556}
557
558enum DocCommentContent<'a> {
559 String(&'a Vec<EcoString>),
560 False,
561}
562
563fn hidden_module_doc<'a>() -> Document<'a> {
564 doc_attribute(DocCommentKind::Module, DocCommentContent::False)
565}
566
567fn module_doc<'a>(content: &Vec<EcoString>) -> Document<'a> {
568 doc_attribute(DocCommentKind::Module, DocCommentContent::String(content))
569}
570
571fn hidden_function_doc<'a>() -> Document<'a> {
572 doc_attribute(DocCommentKind::Function, DocCommentContent::False)
573}
574
575fn function_doc<'a>(content: &Vec<EcoString>) -> Document<'a> {
576 doc_attribute(DocCommentKind::Function, DocCommentContent::String(content))
577}
578
579fn doc_attribute<'a>(kind: DocCommentKind, content: DocCommentContent<'_>) -> Document<'a> {
580 let prefix = match kind {
581 DocCommentKind::Module => "?MODULEDOC",
582 DocCommentKind::Function => "?DOC",
583 };
584
585 match content {
586 DocCommentContent::False => prefix.to_doc().append("(false)."),
587 DocCommentContent::String(doc_lines) => {
588 let is_multiline_doc_comment = doc_lines.len() > 1;
589 let doc_lines = join(
590 doc_lines.iter().map(|line| {
591 let line = line.replace("\\", "\\\\").replace("\"", "\\\"");
592 docvec!["\"", line, "\\n\""]
593 }),
594 line(),
595 );
596 if is_multiline_doc_comment {
597 let nested_documentation = docvec![line(), doc_lines].nest(INDENT);
598 docvec![prefix, "(", nested_documentation, line(), ")."]
599 } else {
600 docvec![prefix, "(", doc_lines, ")."]
601 }
602 }
603 }
604}
605
606fn external_fun_args<'a>(args: &'a [TypedArg], env: &mut Env<'a>) -> Document<'a> {
607 wrap_args(args.iter().map(|a| {
608 let name = match &a.names {
609 ArgNames::Discard { name, .. }
610 | ArgNames::LabelledDiscard { name, .. }
611 | ArgNames::Named { name, .. }
612 | ArgNames::NamedLabelled { name, .. } => name,
613 };
614 if name.chars().all(|c| c == '_') {
615 env.next_local_var_name("argument")
616 } else {
617 env.next_local_var_name(name)
618 }
619 }))
620}
621
622fn fun_args<'a>(args: &'a [TypedArg], env: &mut Env<'a>) -> Document<'a> {
623 wrap_args(args.iter().map(|a| match &a.names {
624 ArgNames::Discard { .. } | ArgNames::LabelledDiscard { .. } => "_".to_doc(),
625 ArgNames::Named { name, .. } | ArgNames::NamedLabelled { name, .. } => {
626 env.next_local_var_name(name)
627 }
628 }))
629}
630
631fn wrap_args<'a, I>(args: I) -> Document<'a>
632where
633 I: IntoIterator<Item = Document<'a>>,
634{
635 break_("", "")
636 .append(join(args, break_(",", ", ")))
637 .nest(INDENT)
638 .append(break_("", ""))
639 .surround("(", ")")
640 .group()
641}
642
643fn fun_spec<'a>(
644 name: &'a str,
645 args: impl IntoIterator<Item = Document<'a>>,
646 return_: Document<'a>,
647) -> Document<'a> {
648 "-spec "
649 .to_doc()
650 .append(atom(name))
651 .append(wrap_args(args))
652 .append(" -> ")
653 .append(return_)
654 .append(".")
655 .append(line())
656 .group()
657}
658
659fn atom_string(value: String) -> Document<'static> {
660 escape_atom_string(value).to_doc()
661}
662
663fn atom_pattern() -> &'static Regex {
664 static ATOM_PATTERN: OnceLock<Regex> = OnceLock::new();
665 ATOM_PATTERN.get_or_init(|| Regex::new(r"^[a-z][a-z0-9_@]*$").expect("atom RE regex"))
666}
667
668fn atom(value: &str) -> Document<'_> {
669 if is_erlang_reserved_word(value) {
670 // Escape because of keyword collision
671 eco_format!("'{value}'").to_doc()
672 } else if atom_pattern().is_match(value) {
673 // No need to escape
674 EcoString::from(value).to_doc()
675 } else {
676 // Escape because of characters contained
677 eco_format!("'{value}'").to_doc()
678 }
679}
680
681pub fn escape_atom_string(value: String) -> EcoString {
682 if is_erlang_reserved_word(&value) {
683 // Escape because of keyword collision
684 eco_format!("'{value}'")
685 } else if atom_pattern().is_match(&value) {
686 // No need to escape
687 EcoString::from(value)
688 } else {
689 // Escape because of characters contained
690 eco_format!("'{value}'")
691 }
692}
693
694fn unicode_escape_sequence_pattern() -> &'static Regex {
695 static PATTERN: OnceLock<Regex> = OnceLock::new();
696 PATTERN.get_or_init(|| {
697 Regex::new(r#"(\\+)(u)"#).expect("Unicode escape sequence regex cannot be constructed")
698 })
699}
700
701fn string_inner(value: &str) -> Document<'_> {
702 let content = unicode_escape_sequence_pattern()
703 // `\\u`-s should not be affected, so that "\\u..." is not converted to
704 // "\\x...". That's why capturing groups is used to exclude cases that
705 // shouldn't be replaced.
706 .replace_all(value, |caps: &Captures<'_>| {
707 let slashes = caps.get(1).map_or("", |m| m.as_str());
708
709 if slashes.len() % 2 == 0 {
710 format!("{slashes}u")
711 } else {
712 format!("{slashes}x")
713 }
714 });
715 EcoString::from(content).to_doc()
716}
717
718fn string(value: &str) -> Document<'_> {
719 string_inner(value).surround("<<\"", "\"/utf8>>")
720}
721
722fn string_length_utf8_bytes(str: &EcoString) -> usize {
723 convert_string_escape_chars(str).len()
724}
725
726fn tuple<'a>(elements: impl IntoIterator<Item = Document<'a>>) -> Document<'a> {
727 join(elements, break_(",", ", "))
728 .nest(INDENT)
729 .surround("{", "}")
730 .group()
731}
732
733fn const_string_concatenate_bit_array<'a>(
734 elements: impl IntoIterator<Item = Document<'a>>,
735) -> Document<'a> {
736 join(elements, break_(",", ", "))
737 .nest(INDENT)
738 .surround("<<", ">>")
739 .group()
740}
741
742fn const_string_concatenate<'a>(
743 left: &'a TypedConstant,
744 right: &'a TypedConstant,
745 env: &mut Env<'a>,
746) -> Document<'a> {
747 let left = const_string_concatenate_argument(left, env);
748 let right = const_string_concatenate_argument(right, env);
749 const_string_concatenate_bit_array([left, right])
750}
751
752fn const_string_concatenate_inner<'a>(
753 left: &'a TypedConstant,
754 right: &'a TypedConstant,
755 env: &mut Env<'a>,
756) -> Document<'a> {
757 let left = const_string_concatenate_argument(left, env);
758 let right = const_string_concatenate_argument(right, env);
759 join([left, right], break_(",", ", "))
760}
761
762fn const_string_concatenate_argument<'a>(
763 value: &'a TypedConstant,
764 env: &mut Env<'a>,
765) -> Document<'a> {
766 match value {
767 Constant::String { value, .. } => docvec!['"', string_inner(value), "\"/utf8"],
768
769 Constant::Var {
770 constructor: Some(constructor),
771 ..
772 } => match &constructor.variant {
773 ValueConstructorVariant::ModuleConstant {
774 literal: Constant::String { value, .. },
775 ..
776 } => docvec!['"', string_inner(value), "\"/utf8"],
777 ValueConstructorVariant::ModuleConstant {
778 literal: Constant::StringConcatenation { left, right, .. },
779 ..
780 } => const_string_concatenate_inner(left, right, env),
781 _ => const_inline(value, env),
782 },
783
784 Constant::StringConcatenation { left, right, .. } => {
785 const_string_concatenate_inner(left, right, env)
786 }
787
788 _ => const_inline(value, env),
789 }
790}
791
792fn string_concatenate<'a>(
793 left: &'a TypedExpr,
794 right: &'a TypedExpr,
795 env: &mut Env<'a>,
796) -> Document<'a> {
797 let left = string_concatenate_argument(left, env);
798 let right = string_concatenate_argument(right, env);
799 bit_array([left, right])
800}
801
802fn string_concatenate_argument<'a>(value: &'a TypedExpr, env: &mut Env<'a>) -> Document<'a> {
803 match value {
804 TypedExpr::Var {
805 constructor:
806 ValueConstructor {
807 variant:
808 ValueConstructorVariant::ModuleConstant {
809 literal: Constant::String { value, .. },
810 ..
811 },
812 ..
813 },
814 ..
815 }
816 | TypedExpr::String { value, .. } => docvec!['"', string_inner(value), "\"/utf8"],
817
818 TypedExpr::Var {
819 name,
820 constructor:
821 ValueConstructor {
822 variant: ValueConstructorVariant::LocalVariable { .. },
823 ..
824 },
825 ..
826 } => docvec![env.local_var_name(name), "/binary"],
827
828 TypedExpr::BinOp {
829 name: BinOp::Concatenate,
830 ..
831 } => docvec![expr(value, env), "/binary"],
832
833 _ => docvec!["(", maybe_block_expr(value, env), ")/binary"],
834 }
835}
836
837fn bit_array<'a>(elements: impl IntoIterator<Item = Document<'a>>) -> Document<'a> {
838 join(elements, break_(",", ", "))
839 .nest(INDENT)
840 .surround("<<", ">>")
841 .group()
842}
843
844fn const_segment<'a>(
845 value: &'a TypedConstant,
846 options: &'a [TypedConstantBitArraySegmentOption],
847 env: &mut Env<'a>,
848) -> Document<'a> {
849 let value_is_a_string_literal = matches!(value, Constant::String { .. });
850
851 let create_document = |env: &mut Env<'a>| {
852 match value {
853 // Skip the normal <<value/utf8>> surrounds
854 Constant::String { value, .. } => value.to_doc().surround("\"", "\""),
855
856 // As normal
857 Constant::Int { .. } | Constant::Float { .. } | Constant::BitArray { .. } => {
858 const_inline(value, env)
859 }
860
861 // Wrap anything else in parentheses
862 value => const_inline(value, env).surround("(", ")"),
863 }
864 };
865
866 let size = |value: &'a TypedConstant, env: &mut Env<'a>| match value {
867 Constant::Int { .. } => Some(":".to_doc().append(const_inline(value, env))),
868 _ => Some(
869 ":".to_doc()
870 .append(const_inline(value, env).surround("(", ")")),
871 ),
872 };
873
874 let unit = |value: &'a u8| Some(eco_format!("unit:{value}").to_doc());
875
876 bit_array_segment(
877 create_document,
878 options,
879 size,
880 unit,
881 value_is_a_string_literal,
882 false,
883 env,
884 )
885}
886
887fn statement<'a>(statement: &'a TypedStatement, env: &mut Env<'a>) -> Document<'a> {
888 match statement {
889 Statement::Expression(e) => expr(e, env),
890 Statement::Assignment(a) => assignment(a, env),
891 Statement::Use(use_) => expr(&use_.call, env),
892 Statement::Assert(_) => todo!(),
893 }
894}
895
896fn expr_segment<'a>(
897 value: &'a TypedExpr,
898 options: &'a [BitArrayOption<TypedExpr>],
899 env: &mut Env<'a>,
900) -> Document<'a> {
901 let value_is_a_string_literal = matches!(value, TypedExpr::String { .. });
902
903 let create_document = |env: &mut Env<'a>| {
904 match value {
905 // Skip the normal <<value/utf8>> surrounds and set the string literal flag
906 TypedExpr::String { value, .. } => string_inner(value).surround("\"", "\""),
907
908 // As normal
909 TypedExpr::Int { .. }
910 | TypedExpr::Float { .. }
911 | TypedExpr::Var { .. }
912 | TypedExpr::BitArray { .. } => expr(value, env),
913
914 // Wrap anything else in parentheses
915 value => expr(value, env).surround("(", ")"),
916 }
917 };
918
919 let size = |expression: &'a TypedExpr, env: &mut Env<'a>| match expression {
920 TypedExpr::Int { value, .. } => {
921 let v = value.replace("_", "");
922 let v = u64::from_str(&v).unwrap_or(0);
923 Some(eco_format!(":{v}").to_doc())
924 }
925
926 _ => {
927 let inner_expr = expr(expression, env).surround("(", ")");
928 // The value of size must be a non-negative integer, we use lists:max here to ensure
929 // it is at least 0;
930 let value_guard = ":(lists:max(["
931 .to_doc()
932 .append(inner_expr)
933 .append(", 0]))")
934 .group();
935 Some(value_guard)
936 }
937 };
938
939 let unit = |value: &'a u8| Some(eco_format!("unit:{value}").to_doc());
940
941 bit_array_segment(
942 create_document,
943 options,
944 size,
945 unit,
946 value_is_a_string_literal,
947 false,
948 env,
949 )
950}
951
952fn bit_array_segment<'a, Value: 'a, CreateDoc, SizeToDoc, UnitToDoc>(
953 mut create_document: CreateDoc,
954 options: &'a [BitArrayOption<Value>],
955 mut size_to_doc: SizeToDoc,
956 mut unit_to_doc: UnitToDoc,
957 value_is_a_string_literal: bool,
958 value_is_a_discard: bool,
959 env: &mut Env<'a>,
960) -> Document<'a>
961where
962 CreateDoc: FnMut(&mut Env<'a>) -> Document<'a>,
963 SizeToDoc: FnMut(&'a Value, &mut Env<'a>) -> Option<Document<'a>>,
964 UnitToDoc: FnMut(&'a u8) -> Option<Document<'a>>,
965{
966 let mut size: Option<Document<'a>> = None;
967 let mut unit: Option<Document<'a>> = None;
968 let mut others = Vec::new();
969
970 // Erlang only allows valid codepoint integers to be used as values for utf segments
971 // We want to support <<string_var:utf8>> for all string variables, but <<StringVar/utf8>> is invalid
972 // To work around this we use the binary type specifier for these segments instead
973 let override_type = if !value_is_a_string_literal && !value_is_a_discard {
974 Some("binary")
975 } else {
976 None
977 };
978
979 for option in options {
980 use BitArrayOption as Opt;
981 if !others.is_empty() && !matches!(option, Opt::Size { .. } | Opt::Unit { .. }) {
982 others.push("-".to_doc());
983 }
984 match option {
985 Opt::Utf8 { .. } => others.push(override_type.unwrap_or("utf8").to_doc()),
986 Opt::Utf16 { .. } => others.push(override_type.unwrap_or("utf16").to_doc()),
987 Opt::Utf32 { .. } => others.push(override_type.unwrap_or("utf32").to_doc()),
988 Opt::Int { .. } => others.push("integer".to_doc()),
989 Opt::Float { .. } => others.push("float".to_doc()),
990 Opt::Bytes { .. } => others.push("binary".to_doc()),
991 Opt::Bits { .. } => others.push("bitstring".to_doc()),
992 Opt::Utf8Codepoint { .. } => others.push("utf8".to_doc()),
993 Opt::Utf16Codepoint { .. } => others.push("utf16".to_doc()),
994 Opt::Utf32Codepoint { .. } => others.push("utf32".to_doc()),
995 Opt::Signed { .. } => others.push("signed".to_doc()),
996 Opt::Unsigned { .. } => others.push("unsigned".to_doc()),
997 Opt::Big { .. } => others.push("big".to_doc()),
998 Opt::Little { .. } => others.push("little".to_doc()),
999 Opt::Native { .. } => others.push("native".to_doc()),
1000 Opt::Size { value, .. } => size = size_to_doc(value, env),
1001 Opt::Unit { value, .. } => unit = unit_to_doc(value),
1002 }
1003 }
1004
1005 let mut document = create_document(env);
1006
1007 document = document.append(size);
1008 let others_is_empty = others.is_empty();
1009
1010 if !others_is_empty {
1011 document = document.append("/").append(others);
1012 }
1013
1014 if unit.is_some() {
1015 if !others_is_empty {
1016 document = document.append("-").append(unit)
1017 } else {
1018 document = document.append("/").append(unit)
1019 }
1020 }
1021
1022 document
1023}
1024
1025fn block<'a>(statements: &'a Vec1<TypedStatement>, env: &mut Env<'a>) -> Document<'a> {
1026 if statements.len() == 1 {
1027 if let Statement::Expression(expression) = statements.first() {
1028 if !needs_begin_end_wrapping(expression) {
1029 return docvec!['(', expr(expression, env), ')'];
1030 }
1031 }
1032 }
1033
1034 let vars = env.current_scope_vars.clone();
1035 let document = statement_sequence(statements, env);
1036 env.current_scope_vars = vars;
1037
1038 begin_end(document)
1039}
1040
1041fn statement_sequence<'a>(statements: &'a [TypedStatement], env: &mut Env<'a>) -> Document<'a> {
1042 let count = statements.len();
1043 let mut documents = Vec::with_capacity(count * 3);
1044 for (i, expression) in statements.iter().enumerate() {
1045 documents.push(statement(expression, env).group());
1046
1047 if i + 1 < count {
1048 // This isn't the final expression so add the delimeters
1049 documents.push(",".to_doc());
1050 documents.push(line());
1051 }
1052 }
1053 if count == 1 {
1054 documents.to_doc()
1055 } else {
1056 documents.to_doc().force_break()
1057 }
1058}
1059
1060fn float_div<'a>(left: &'a TypedExpr, right: &'a TypedExpr, env: &mut Env<'a>) -> Document<'a> {
1061 if right.non_zero_compile_time_number() {
1062 return binop_exprs(left, "/", right, env);
1063 }
1064
1065 let left = expr(left, env);
1066 let right = expr(right, env);
1067 let denominator = env.next_local_var_name("gleam@denominator");
1068 let clauses = docvec![
1069 line(),
1070 "+0.0 -> +0.0;",
1071 line(),
1072 "-0.0 -> -0.0;",
1073 line(),
1074 denominator.clone(),
1075 " -> ",
1076 binop_documents(left, "/", denominator)
1077 ];
1078 docvec!["case ", right, " of", clauses.nest(INDENT), line(), "end"]
1079}
1080
1081fn int_div<'a>(
1082 left: &'a TypedExpr,
1083 right: &'a TypedExpr,
1084 op: &'static str,
1085 env: &mut Env<'a>,
1086) -> Document<'a> {
1087 if right.non_zero_compile_time_number() {
1088 return binop_exprs(left, op, right, env);
1089 }
1090
1091 let left = expr(left, env);
1092 let right = expr(right, env);
1093 let denominator = env.next_local_var_name("gleam@denominator");
1094 let clauses = docvec![
1095 line(),
1096 "0 -> 0;",
1097 line(),
1098 denominator.clone(),
1099 " -> ",
1100 binop_documents(left, op, denominator)
1101 ];
1102 docvec!["case ", right, " of", clauses.nest(INDENT), line(), "end"]
1103}
1104
1105fn bin_op<'a>(
1106 name: &'a BinOp,
1107 left: &'a TypedExpr,
1108 right: &'a TypedExpr,
1109 env: &mut Env<'a>,
1110) -> Document<'a> {
1111 let op = match name {
1112 BinOp::And => "andalso",
1113 BinOp::Or => "orelse",
1114 BinOp::LtInt | BinOp::LtFloat => "<",
1115 BinOp::LtEqInt | BinOp::LtEqFloat => "=<",
1116 BinOp::Eq => "=:=",
1117 BinOp::NotEq => "/=",
1118 BinOp::GtInt | BinOp::GtFloat => ">",
1119 BinOp::GtEqInt | BinOp::GtEqFloat => ">=",
1120 BinOp::AddInt => "+",
1121 BinOp::AddFloat => "+",
1122 BinOp::SubInt => "-",
1123 BinOp::SubFloat => "-",
1124 BinOp::MultInt => "*",
1125 BinOp::MultFloat => "*",
1126 BinOp::DivFloat => return float_div(left, right, env),
1127 BinOp::DivInt => return int_div(left, right, "div", env),
1128 BinOp::RemainderInt => return int_div(left, right, "rem", env),
1129 BinOp::Concatenate => return string_concatenate(left, right, env),
1130 };
1131
1132 binop_exprs(left, op, right, env)
1133}
1134
1135fn binop_exprs<'a>(
1136 left: &'a TypedExpr,
1137 op: &'static str,
1138 right: &'a TypedExpr,
1139 env: &mut Env<'a>,
1140) -> Document<'a> {
1141 let left = match left {
1142 TypedExpr::BinOp { .. } => expr(left, env).surround("(", ")"),
1143 _ => maybe_block_expr(left, env),
1144 };
1145 let right = match right {
1146 TypedExpr::BinOp { .. } => expr(right, env).surround("(", ")"),
1147 _ => maybe_block_expr(right, env),
1148 };
1149 binop_documents(left, op, right)
1150}
1151
1152fn binop_documents<'a>(left: Document<'a>, op: &'static str, right: Document<'a>) -> Document<'a> {
1153 left.append(break_("", " "))
1154 .append(op)
1155 .group()
1156 .append(" ")
1157 .append(right)
1158}
1159
1160fn let_assert<'a>(
1161 value: &'a TypedExpr,
1162 pat: &'a TypedPattern,
1163 env: &mut Env<'a>,
1164 message: Option<&'a TypedExpr>,
1165) -> Document<'a> {
1166 let mut vars: Vec<&str> = vec![];
1167 let body = maybe_block_expr(value, env);
1168 let (subject_var, subject_definition) = if value.is_var() {
1169 (body, nil())
1170 } else {
1171 let var = env.next_local_var_name(ASSERT_SUBJECT_VARIABLE);
1172 let definition = docvec![var.clone(), " = ", body, ",", line()];
1173 (var, definition)
1174 };
1175
1176 let mut guards = vec![];
1177 let check_pattern = pattern::to_doc_discarding_all(pat, &mut vars, env, &mut guards);
1178 let clause_guard = optional_clause_guard(None, guards, env);
1179
1180 // We don't take the guards from the assign pattern or we would end up with
1181 // all the same guards repeated twice!
1182 let assign_pattern = pattern::to_doc(pat, &mut vars, env, &mut vec![]);
1183 let message = match message {
1184 Some(message) => expr(message, env),
1185 None => string("Pattern match failed, no pattern matched the value."),
1186 };
1187
1188 let clauses = docvec![
1189 check_pattern.clone(),
1190 clause_guard,
1191 " -> ",
1192 subject_var.clone(),
1193 ";",
1194 line(),
1195 env.next_local_var_name(ASSERT_FAIL_VARIABLE),
1196 " ->",
1197 docvec![
1198 line(),
1199 erlang_error(
1200 "let_assert",
1201 &message,
1202 pat.location(),
1203 vec![("value", env.local_var_name(ASSERT_FAIL_VARIABLE))],
1204 env,
1205 )
1206 .nest(INDENT)
1207 ]
1208 .nest(INDENT)
1209 ];
1210 docvec![
1211 subject_definition,
1212 assign_pattern,
1213 " = case ",
1214 subject_var,
1215 " of",
1216 docvec![line(), clauses].nest(INDENT),
1217 line(),
1218 "end",
1219 ]
1220}
1221
1222fn let_<'a>(value: &'a TypedExpr, pat: &'a TypedPattern, env: &mut Env<'a>) -> Document<'a> {
1223 let body = maybe_block_expr(value, env).group();
1224 let mut guards = vec![];
1225 pattern(pat, env, &mut guards).append(" = ").append(body)
1226}
1227
1228fn float<'a>(value: &str) -> Document<'a> {
1229 let mut value = value.replace('_', "");
1230 if value.ends_with('.') {
1231 value.push('0')
1232 }
1233
1234 match value.split('.').collect_vec().as_slice() {
1235 ["0", "0"] => "+0.0".to_doc(),
1236 [before_dot, after_dot] if after_dot.starts_with('e') => {
1237 eco_format!("{before_dot}.0{after_dot}").to_doc()
1238 }
1239 _ => EcoString::from(value).to_doc(),
1240 }
1241}
1242
1243fn expr_list<'a>(
1244 elements: &'a [TypedExpr],
1245 tail: &'a Option<Box<TypedExpr>>,
1246 env: &mut Env<'a>,
1247) -> Document<'a> {
1248 let elements = join(
1249 elements
1250 .iter()
1251 .map(|element| maybe_block_expr(element, env)),
1252 break_(",", ", "),
1253 );
1254 list(
1255 elements,
1256 tail.as_ref().map(|element| maybe_block_expr(element, env)),
1257 )
1258}
1259
1260fn list<'a>(elements: Document<'a>, tail: Option<Document<'a>>) -> Document<'a> {
1261 let elements = match tail {
1262 Some(tail) if elements.is_empty() => return tail.to_doc(),
1263
1264 Some(tail) => elements.append(break_(" |", " | ")).append(tail),
1265
1266 None => elements,
1267 };
1268
1269 elements.to_doc().nest(INDENT).surround("[", "]").group()
1270}
1271
1272fn var<'a>(name: &'a str, constructor: &'a ValueConstructor, env: &mut Env<'a>) -> Document<'a> {
1273 match &constructor.variant {
1274 ValueConstructorVariant::Record {
1275 name: record_name, ..
1276 } => match constructor.type_.deref() {
1277 Type::Fn { args, .. } => {
1278 let chars = incrementing_args_list(args.len());
1279 "fun("
1280 .to_doc()
1281 .append(chars.clone())
1282 .append(") -> {")
1283 .append(atom_string(record_name.to_snake_case()))
1284 .append(", ")
1285 .append(chars)
1286 .append("} end")
1287 }
1288 _ => atom_string(record_name.to_snake_case()),
1289 },
1290
1291 ValueConstructorVariant::LocalVariable { .. } => env.local_var_name(name),
1292
1293 ValueConstructorVariant::ModuleConstant { literal, .. }
1294 | ValueConstructorVariant::LocalConstant { literal } => const_inline(literal, env),
1295
1296 ValueConstructorVariant::ModuleFn {
1297 arity,
1298 external_erlang: Some((module, name)),
1299 ..
1300 } if module == env.module => function_reference(None, name, *arity),
1301
1302 ValueConstructorVariant::ModuleFn {
1303 arity,
1304 external_erlang: Some((module, name)),
1305 ..
1306 } => function_reference(Some(module), name, *arity),
1307
1308 ValueConstructorVariant::ModuleFn { arity, module, .. } if module == env.module => {
1309 function_reference(None, name, *arity)
1310 }
1311
1312 ValueConstructorVariant::ModuleFn {
1313 arity,
1314 module,
1315 name,
1316 ..
1317 } => function_reference(Some(module), name, *arity),
1318 }
1319}
1320
1321fn function_reference<'a>(module: Option<&'a str>, name: &'a str, arity: usize) -> Document<'a> {
1322 match module {
1323 None => "fun ".to_doc(),
1324 Some(module) => "fun ".to_doc().append(module_name_atom(module)).append(":"),
1325 }
1326 .append(atom(escape_erlang_existing_name(name)))
1327 .append("/")
1328 .append(arity)
1329}
1330
1331fn int<'a>(value: &str) -> Document<'a> {
1332 let mut value = value.replace('_', "");
1333 if value.starts_with("0x") {
1334 value.replace_range(..2, "16#");
1335 } else if value.starts_with("0o") {
1336 value.replace_range(..2, "8#");
1337 } else if value.starts_with("0b") {
1338 value.replace_range(..2, "2#");
1339 }
1340
1341 EcoString::from(value).to_doc()
1342}
1343
1344fn const_inline<'a>(literal: &'a TypedConstant, env: &mut Env<'a>) -> Document<'a> {
1345 match literal {
1346 Constant::Int { value, .. } => int(value),
1347 Constant::Float { value, .. } => float(value),
1348 Constant::String { value, .. } => string(value),
1349 Constant::Tuple { elements, .. } => {
1350 tuple(elements.iter().map(|element| const_inline(element, env)))
1351 }
1352
1353 Constant::List { elements, .. } => join(
1354 elements.iter().map(|element| const_inline(element, env)),
1355 break_(",", ", "),
1356 )
1357 .nest(INDENT)
1358 .surround("[", "]")
1359 .group(),
1360
1361 Constant::BitArray { segments, .. } => bit_array(
1362 segments
1363 .iter()
1364 .map(|s| const_segment(&s.value, &s.options, env)),
1365 ),
1366
1367 Constant::Record {
1368 tag, type_, args, ..
1369 } if args.is_empty() => match type_.deref() {
1370 Type::Fn { args, .. } => record_constructor_function(tag, args.len()),
1371 _ => atom_string(tag.to_snake_case()),
1372 },
1373
1374 Constant::Record { tag, args, .. } => {
1375 let args = args.iter().map(|a| const_inline(&a.value, env));
1376 let tag = atom_string(tag.to_snake_case());
1377 tuple(std::iter::once(tag).chain(args))
1378 }
1379
1380 Constant::Var {
1381 name, constructor, ..
1382 } => var(
1383 name,
1384 constructor
1385 .as_ref()
1386 .expect("This is guaranteed to hold a value."),
1387 env,
1388 ),
1389
1390 Constant::StringConcatenation { left, right, .. } => {
1391 const_string_concatenate(left, right, env)
1392 }
1393
1394 Constant::Invalid { .. } => panic!("invalid constants should not reach code generation"),
1395 }
1396}
1397
1398fn record_constructor_function(tag: &EcoString, arity: usize) -> Document<'_> {
1399 let chars = incrementing_args_list(arity);
1400 "fun("
1401 .to_doc()
1402 .append(chars.clone())
1403 .append(") -> {")
1404 .append(atom_string(tag.to_snake_case()))
1405 .append(", ")
1406 .append(chars)
1407 .append("} end")
1408}
1409
1410fn clause<'a>(clause: &'a TypedClause, env: &mut Env<'a>) -> Document<'a> {
1411 let Clause {
1412 guard,
1413 pattern: pat,
1414 alternative_patterns,
1415 then,
1416 ..
1417 } = clause;
1418
1419 // These are required to get the alternative patterns working properly.
1420 // Simply rendering the duplicate erlang clauses breaks the variable
1421 // rewriting because each pattern would define different (rewritten)
1422 // variables names.
1423 let mut then_doc = None;
1424 let initial_erlang_vars = env.erl_function_scope_vars.clone();
1425 let mut end_erlang_vars = im::HashMap::new();
1426
1427 let doc = join(
1428 std::iter::once(pat)
1429 .chain(alternative_patterns)
1430 .map(|patterns| {
1431 let mut additional_guards = vec![];
1432 env.erl_function_scope_vars = initial_erlang_vars.clone();
1433
1434 let patterns_doc = if patterns.len() == 1 {
1435 let p = patterns.first().expect("Single pattern clause printing");
1436 pattern(p, env, &mut additional_guards)
1437 } else {
1438 tuple(
1439 patterns
1440 .iter()
1441 .map(|p| pattern(p, env, &mut additional_guards)),
1442 )
1443 };
1444
1445 let guard = optional_clause_guard(guard.as_ref(), additional_guards, env);
1446 if then_doc.is_none() {
1447 then_doc = Some(clause_consequence(then, env));
1448 end_erlang_vars = env.erl_function_scope_vars.clone();
1449 }
1450
1451 patterns_doc.append(
1452 guard
1453 .append(" ->")
1454 .append(line().append(then_doc.clone()).nest(INDENT).group()),
1455 )
1456 }),
1457 ";".to_doc().append(lines(2)),
1458 );
1459
1460 env.erl_function_scope_vars = end_erlang_vars;
1461 doc
1462}
1463
1464fn clause_consequence<'a>(consequence: &'a TypedExpr, env: &mut Env<'a>) -> Document<'a> {
1465 match consequence {
1466 TypedExpr::Block { statements, .. } => statement_sequence(statements, env),
1467 _ => expr(consequence, env),
1468 }
1469}
1470
1471fn optional_clause_guard<'a>(
1472 guard: Option<&'a TypedClauseGuard>,
1473 additional_guards: Vec<Document<'a>>,
1474 env: &mut Env<'a>,
1475) -> Document<'a> {
1476 let guard_doc = guard.map(|guard| bare_clause_guard(guard, env));
1477
1478 let guards_count = guard_doc.iter().len() + additional_guards.len();
1479 let guards_docs = additional_guards.into_iter().chain(guard_doc).map(|guard| {
1480 if guards_count > 1 {
1481 guard.surround("(", ")")
1482 } else {
1483 guard
1484 }
1485 });
1486 let doc = join(guards_docs, " andalso ".to_doc());
1487 if doc.is_empty() {
1488 doc
1489 } else {
1490 " when ".to_doc().append(doc)
1491 }
1492}
1493
1494fn bare_clause_guard<'a>(guard: &'a TypedClauseGuard, env: &mut Env<'a>) -> Document<'a> {
1495 match guard {
1496 ClauseGuard::Not { expression, .. } => docvec!["not ", bare_clause_guard(expression, env)],
1497
1498 ClauseGuard::Or { left, right, .. } => clause_guard(left, env)
1499 .append(" orelse ")
1500 .append(clause_guard(right, env)),
1501
1502 ClauseGuard::And { left, right, .. } => clause_guard(left, env)
1503 .append(" andalso ")
1504 .append(clause_guard(right, env)),
1505
1506 ClauseGuard::Equals { left, right, .. } => clause_guard(left, env)
1507 .append(" =:= ")
1508 .append(clause_guard(right, env)),
1509
1510 ClauseGuard::NotEquals { left, right, .. } => clause_guard(left, env)
1511 .append(" =/= ")
1512 .append(clause_guard(right, env)),
1513
1514 ClauseGuard::GtInt { left, right, .. } => clause_guard(left, env)
1515 .append(" > ")
1516 .append(clause_guard(right, env)),
1517
1518 ClauseGuard::GtEqInt { left, right, .. } => clause_guard(left, env)
1519 .append(" >= ")
1520 .append(clause_guard(right, env)),
1521
1522 ClauseGuard::LtInt { left, right, .. } => clause_guard(left, env)
1523 .append(" < ")
1524 .append(clause_guard(right, env)),
1525
1526 ClauseGuard::LtEqInt { left, right, .. } => clause_guard(left, env)
1527 .append(" =< ")
1528 .append(clause_guard(right, env)),
1529
1530 ClauseGuard::GtFloat { left, right, .. } => clause_guard(left, env)
1531 .append(" > ")
1532 .append(clause_guard(right, env)),
1533
1534 ClauseGuard::GtEqFloat { left, right, .. } => clause_guard(left, env)
1535 .append(" >= ")
1536 .append(clause_guard(right, env)),
1537
1538 ClauseGuard::LtFloat { left, right, .. } => clause_guard(left, env)
1539 .append(" < ")
1540 .append(clause_guard(right, env)),
1541
1542 ClauseGuard::LtEqFloat { left, right, .. } => clause_guard(left, env)
1543 .append(" =< ")
1544 .append(clause_guard(right, env)),
1545
1546 ClauseGuard::AddInt { left, right, .. } => clause_guard(left, env)
1547 .append(" + ")
1548 .append(clause_guard(right, env)),
1549
1550 ClauseGuard::AddFloat { left, right, .. } => clause_guard(left, env)
1551 .append(" + ")
1552 .append(clause_guard(right, env)),
1553
1554 ClauseGuard::SubInt { left, right, .. } => clause_guard(left, env)
1555 .append(" - ")
1556 .append(clause_guard(right, env)),
1557
1558 ClauseGuard::SubFloat { left, right, .. } => clause_guard(left, env)
1559 .append(" - ")
1560 .append(clause_guard(right, env)),
1561
1562 ClauseGuard::MultInt { left, right, .. } => clause_guard(left, env)
1563 .append(" * ")
1564 .append(clause_guard(right, env)),
1565
1566 ClauseGuard::MultFloat { left, right, .. } => clause_guard(left, env)
1567 .append(" * ")
1568 .append(clause_guard(right, env)),
1569
1570 ClauseGuard::DivInt { left, right, .. } => clause_guard(left, env)
1571 .append(" div ")
1572 .append(clause_guard(right, env)),
1573
1574 ClauseGuard::DivFloat { left, right, .. } => clause_guard(left, env)
1575 .append(" / ")
1576 .append(clause_guard(right, env)),
1577
1578 ClauseGuard::RemainderInt { left, right, .. } => clause_guard(left, env)
1579 .append(" rem ")
1580 .append(clause_guard(right, env)),
1581
1582 // Only local variables are supported and the typer ensures that all
1583 // ClauseGuard::Vars are local variables
1584 ClauseGuard::Var { name, .. } => env.local_var_name(name),
1585
1586 ClauseGuard::TupleIndex { tuple, index, .. } => tuple_index_inline(tuple, *index, env),
1587
1588 ClauseGuard::FieldAccess {
1589 container, index, ..
1590 } => tuple_index_inline(container, index.expect("Unable to find index") + 1, env),
1591
1592 ClauseGuard::ModuleSelect { literal, .. } => const_inline(literal, env),
1593
1594 ClauseGuard::Constant(constant) => const_inline(constant, env),
1595 }
1596}
1597
1598fn tuple_index_inline<'a>(
1599 tuple: &'a TypedClauseGuard,
1600 index: u64,
1601 env: &mut Env<'a>,
1602) -> Document<'a> {
1603 let index_doc = eco_format!("{}", (index + 1)).to_doc();
1604 let tuple_doc = bare_clause_guard(tuple, env);
1605 "erlang:element"
1606 .to_doc()
1607 .append(wrap_args([index_doc, tuple_doc]))
1608}
1609
1610fn clause_guard<'a>(guard: &'a TypedClauseGuard, env: &mut Env<'a>) -> Document<'a> {
1611 match guard {
1612 // Binary operators are wrapped in parens
1613 ClauseGuard::Or { .. }
1614 | ClauseGuard::And { .. }
1615 | ClauseGuard::Equals { .. }
1616 | ClauseGuard::NotEquals { .. }
1617 | ClauseGuard::GtInt { .. }
1618 | ClauseGuard::GtEqInt { .. }
1619 | ClauseGuard::LtInt { .. }
1620 | ClauseGuard::LtEqInt { .. }
1621 | ClauseGuard::GtFloat { .. }
1622 | ClauseGuard::GtEqFloat { .. }
1623 | ClauseGuard::LtFloat { .. }
1624 | ClauseGuard::LtEqFloat { .. }
1625 | ClauseGuard::AddInt { .. }
1626 | ClauseGuard::AddFloat { .. }
1627 | ClauseGuard::SubInt { .. }
1628 | ClauseGuard::SubFloat { .. }
1629 | ClauseGuard::MultInt { .. }
1630 | ClauseGuard::MultFloat { .. }
1631 | ClauseGuard::DivInt { .. }
1632 | ClauseGuard::DivFloat { .. }
1633 | ClauseGuard::RemainderInt { .. } => "("
1634 .to_doc()
1635 .append(bare_clause_guard(guard, env))
1636 .append(")"),
1637
1638 // Other expressions are not
1639 ClauseGuard::Constant(_)
1640 | ClauseGuard::Not { .. }
1641 | ClauseGuard::Var { .. }
1642 | ClauseGuard::TupleIndex { .. }
1643 | ClauseGuard::FieldAccess { .. }
1644 | ClauseGuard::ModuleSelect { .. } => bare_clause_guard(guard, env),
1645 }
1646}
1647
1648fn clauses<'a>(cs: &'a [TypedClause], env: &mut Env<'a>) -> Document<'a> {
1649 join(
1650 cs.iter().map(|c| {
1651 let vars = env.current_scope_vars.clone();
1652 let erl = clause(c, env);
1653 env.current_scope_vars = vars; // Reset the known variables now the clauses' scope has ended
1654 erl
1655 }),
1656 ";".to_doc().append(lines(2)),
1657 )
1658}
1659
1660fn case<'a>(subjects: &'a [TypedExpr], cs: &'a [TypedClause], env: &mut Env<'a>) -> Document<'a> {
1661 let subjects_doc = if subjects.len() == 1 {
1662 let subject = subjects
1663 .first()
1664 .expect("erl case printing of single subject");
1665 maybe_block_expr(subject, env).group()
1666 } else {
1667 tuple(
1668 subjects
1669 .iter()
1670 .map(|element| maybe_block_expr(element, env)),
1671 )
1672 };
1673 "case "
1674 .to_doc()
1675 .append(subjects_doc)
1676 .append(" of")
1677 .append(line().append(clauses(cs, env)).nest(INDENT))
1678 .append(line())
1679 .append("end")
1680 .group()
1681}
1682
1683fn call<'a>(fun: &'a TypedExpr, args: &'a [TypedCallArg], env: &mut Env<'a>) -> Document<'a> {
1684 docs_args_call(
1685 fun,
1686 args.iter()
1687 .map(|arg| maybe_block_expr(&arg.value, env))
1688 .collect(),
1689 env,
1690 )
1691}
1692
1693fn module_fn_with_args<'a>(
1694 module: &'a str,
1695 name: &'a str,
1696 args: Vec<Document<'a>>,
1697 env: &Env<'a>,
1698) -> Document<'a> {
1699 let name = escape_erlang_existing_name(name);
1700 let args = wrap_args(args);
1701 if module == env.module {
1702 atom(name).append(args)
1703 } else {
1704 atom_string(module.replace('/', "@"))
1705 .append(":")
1706 .append(atom(name))
1707 .append(args)
1708 }
1709}
1710
1711fn docs_args_call<'a>(
1712 fun: &'a TypedExpr,
1713 mut args: Vec<Document<'a>>,
1714 env: &mut Env<'a>,
1715) -> Document<'a> {
1716 match fun {
1717 TypedExpr::ModuleSelect {
1718 constructor: ModuleValueConstructor::Record { name, .. },
1719 ..
1720 }
1721 | TypedExpr::Var {
1722 constructor:
1723 ValueConstructor {
1724 variant: ValueConstructorVariant::Record { name, .. },
1725 ..
1726 },
1727 ..
1728 } => tuple(std::iter::once(atom_string(name.to_snake_case())).chain(args)),
1729
1730 TypedExpr::Var {
1731 constructor:
1732 ValueConstructor {
1733 variant:
1734 ValueConstructorVariant::ModuleFn {
1735 external_erlang: Some((module, name)),
1736 ..
1737 }
1738 | ValueConstructorVariant::ModuleFn { module, name, .. },
1739 ..
1740 },
1741 ..
1742 } => module_fn_with_args(module, name, args, env),
1743
1744 // Match against a Constant::Var that contains a function.
1745 // We want this to be emitted like a normal function call, not a function variable
1746 // substitution.
1747 TypedExpr::Var {
1748 constructor:
1749 ValueConstructor {
1750 variant:
1751 ValueConstructorVariant::ModuleConstant {
1752 literal:
1753 Constant::Var {
1754 constructor: Some(constructor),
1755 ..
1756 },
1757 ..
1758 },
1759 ..
1760 },
1761 ..
1762 } if constructor.variant.is_module_fn() => match &constructor.variant {
1763 ValueConstructorVariant::ModuleFn {
1764 external_erlang: Some((module, name)),
1765 ..
1766 }
1767 | ValueConstructorVariant::ModuleFn { module, name, .. } => {
1768 module_fn_with_args(module, name, args, env)
1769 }
1770 _ => {
1771 unreachable!("The above clause guard ensures that this is a module fn")
1772 }
1773 },
1774
1775 TypedExpr::ModuleSelect {
1776 constructor:
1777 ModuleValueConstructor::Fn {
1778 external_erlang: Some((module, name)),
1779 ..
1780 }
1781 | ModuleValueConstructor::Fn { module, name, .. },
1782 ..
1783 } => {
1784 let args = wrap_args(args);
1785 let name = escape_erlang_existing_name(name);
1786 // We use the constructor Fn variant's `module` and function `name`.
1787 // It would also be valid to use the module and label as in the
1788 // Gleam code, but using the variant can result in an optimisation
1789 // in which the target function is used for `external fn`s, removing
1790 // one layer of wrapping.
1791 // This also enables an optimisation in the Erlang compiler in which
1792 // some Erlang BIFs can be replaced with literals if their arguments
1793 // are literals, such as `binary_to_atom`.
1794 atom_string(module_erlang_name(module).to_string())
1795 .append(":")
1796 .append(atom_string(name.to_string()))
1797 .append(args)
1798 }
1799
1800 TypedExpr::Fn { kind, body, .. } if kind.is_capture() => {
1801 if let Statement::Expression(TypedExpr::Call {
1802 fun,
1803 args: inner_args,
1804 ..
1805 }) = body.first()
1806 {
1807 let mut merged_args = Vec::with_capacity(inner_args.len());
1808 for arg in inner_args {
1809 match &arg.value {
1810 TypedExpr::Var { name, .. } if name == CAPTURE_VARIABLE => {
1811 merged_args.push(args.swap_remove(0))
1812 }
1813 e => merged_args.push(maybe_block_expr(e, env)),
1814 }
1815 }
1816 docs_args_call(fun, merged_args, env)
1817 } else {
1818 panic!("Erl printing: Capture was not a call")
1819 }
1820 }
1821
1822 TypedExpr::Fn { .. }
1823 | TypedExpr::Call { .. }
1824 | TypedExpr::Todo { .. }
1825 | TypedExpr::Panic { .. }
1826 | TypedExpr::RecordAccess { .. }
1827 | TypedExpr::TupleIndex { .. } => {
1828 let args = wrap_args(args);
1829 expr(fun, env).surround("(", ")").append(args)
1830 }
1831
1832 other => {
1833 let args = wrap_args(args);
1834 maybe_block_expr(other, env).append(args)
1835 }
1836 }
1837}
1838
1839fn record_update<'a>(
1840 record: &'a TypedAssignment,
1841 constructor: &'a TypedExpr,
1842 args: &'a [TypedCallArg],
1843 env: &mut Env<'a>,
1844) -> Document<'a> {
1845 let vars = env.current_scope_vars.clone();
1846
1847 let document = docvec![
1848 assignment(record, env),
1849 ",",
1850 line(),
1851 call(constructor, args, env)
1852 ];
1853
1854 env.current_scope_vars = vars;
1855
1856 document
1857}
1858
1859/// Wrap a document in begin end
1860///
1861fn begin_end(document: Document<'_>) -> Document<'_> {
1862 docvec!["begin", line().append(document).nest(INDENT), line(), "end"].force_break()
1863}
1864
1865fn maybe_block_expr<'a>(expression: &'a TypedExpr, env: &mut Env<'a>) -> Document<'a> {
1866 if needs_begin_end_wrapping(expression) {
1867 begin_end(expr(expression, env))
1868 } else {
1869 expr(expression, env)
1870 }
1871}
1872
1873fn needs_begin_end_wrapping(expression: &TypedExpr) -> bool {
1874 match expression {
1875 TypedExpr::RecordUpdate { .. } | TypedExpr::Pipeline { .. } => true,
1876
1877 TypedExpr::Int { .. }
1878 | TypedExpr::Float { .. }
1879 | TypedExpr::String { .. }
1880 | TypedExpr::Var { .. }
1881 | TypedExpr::Fn { .. }
1882 | TypedExpr::List { .. }
1883 | TypedExpr::Call { .. }
1884 | TypedExpr::BinOp { .. }
1885 | TypedExpr::Case { .. }
1886 | TypedExpr::RecordAccess { .. }
1887 | TypedExpr::Block { .. }
1888 | TypedExpr::ModuleSelect { .. }
1889 | TypedExpr::Tuple { .. }
1890 | TypedExpr::TupleIndex { .. }
1891 | TypedExpr::Todo { .. }
1892 | TypedExpr::Echo { .. }
1893 | TypedExpr::Panic { .. }
1894 | TypedExpr::BitArray { .. }
1895 | TypedExpr::NegateBool { .. }
1896 | TypedExpr::NegateInt { .. }
1897 | TypedExpr::Invalid { .. } => false,
1898 }
1899}
1900
1901fn todo<'a>(message: Option<&'a TypedExpr>, location: SrcSpan, env: &mut Env<'a>) -> Document<'a> {
1902 let message = match message {
1903 Some(m) => expr(m, env),
1904 None => string("`todo` expression evaluated. This code has not yet been implemented."),
1905 };
1906 erlang_error("todo", &message, location, vec![], env)
1907}
1908
1909fn panic<'a>(location: SrcSpan, message: Option<&'a TypedExpr>, env: &mut Env<'a>) -> Document<'a> {
1910 let message = match message {
1911 Some(m) => expr(m, env),
1912 None => string("`panic` expression evaluated."),
1913 };
1914 erlang_error("panic", &message, location, vec![], env)
1915}
1916
1917fn echo<'a>(body: Document<'a>, location: &SrcSpan, env: &mut Env<'a>) -> Document<'a> {
1918 env.echo_used = true;
1919
1920 let relative_path = env
1921 .src_path
1922 .strip_prefix(env.project_root)
1923 .unwrap_or(env.src_path)
1924 .as_str();
1925
1926 let relative_path_doc = EcoString::from(relative_path)
1927 .replace("\\", "\\\\")
1928 .to_doc();
1929
1930 let relative_path_doc = docvec!["\"", relative_path_doc, "\""];
1931
1932 "echo".to_doc().append(wrap_args(vec![
1933 body,
1934 relative_path_doc,
1935 env.line_numbers.line_number(location.start).to_doc(),
1936 ]))
1937}
1938
1939fn erlang_error<'a>(
1940 name: &'a str,
1941 message: &Document<'a>,
1942 location: SrcSpan,
1943 fields: Vec<(&'a str, Document<'a>)>,
1944 env: &Env<'a>,
1945) -> Document<'a> {
1946 let mut fields_doc = docvec![
1947 "gleam_error => ",
1948 name,
1949 ",",
1950 line(),
1951 "message => ",
1952 message.clone()
1953 ];
1954
1955 for (key, value) in fields {
1956 fields_doc = fields_doc
1957 .append(",")
1958 .append(line())
1959 .append(key)
1960 .append(" => ")
1961 .append(value);
1962 }
1963 let fields_doc = fields_doc
1964 .append(",")
1965 .append(line())
1966 .append("module => ")
1967 .append(env.module.to_doc().surround("<<\"", "\"/utf8>>"))
1968 .append(",")
1969 .append(line())
1970 .append("function => ")
1971 .append(string(env.function))
1972 .append(",")
1973 .append(line())
1974 .append("line => ")
1975 .append(env.line_numbers.line_number(location.start));
1976 let error = "#{"
1977 .to_doc()
1978 .append(fields_doc.group().nest(INDENT))
1979 .append("}");
1980 docvec!["erlang:error", wrap_args([error.group()])]
1981}
1982
1983fn expr<'a>(expression: &'a TypedExpr, env: &mut Env<'a>) -> Document<'a> {
1984 match expression {
1985 TypedExpr::Todo {
1986 message: label,
1987 location,
1988 ..
1989 } => todo(label.as_deref(), *location, env),
1990
1991 TypedExpr::Panic {
1992 location, message, ..
1993 } => panic(*location, message.as_deref(), env),
1994
1995 TypedExpr::Echo {
1996 expression,
1997 location,
1998 ..
1999 } => {
2000 let expression = expression
2001 .as_ref()
2002 .expect("echo with no expression outside of pipe");
2003 echo(maybe_block_expr(expression, env), location, env)
2004 }
2005
2006 TypedExpr::Int { value, .. } => int(value),
2007 TypedExpr::Float { value, .. } => float(value),
2008 TypedExpr::String { value, .. } => string(value),
2009
2010 TypedExpr::Pipeline {
2011 first_value,
2012 assignments,
2013 finally,
2014 ..
2015 } => pipeline(first_value, assignments, finally, env),
2016
2017 TypedExpr::Block { statements, .. } => block(statements, env),
2018
2019 TypedExpr::TupleIndex { tuple, index, .. } => tuple_index(tuple, *index, env),
2020
2021 TypedExpr::Var {
2022 name, constructor, ..
2023 } => var(name, constructor, env),
2024
2025 TypedExpr::Fn { args, body, .. } => fun(args, body, env),
2026
2027 TypedExpr::NegateBool { value, .. } => negate_with("not ", value, env),
2028
2029 TypedExpr::NegateInt { value, .. } => negate_with("- ", value, env),
2030
2031 TypedExpr::List { elements, tail, .. } => expr_list(elements, tail, env),
2032
2033 TypedExpr::Call { fun, args, .. } => call(fun, args, env),
2034
2035 TypedExpr::ModuleSelect {
2036 constructor: ModuleValueConstructor::Record { name, arity: 0, .. },
2037 ..
2038 } => atom_string(name.to_snake_case()),
2039
2040 TypedExpr::ModuleSelect {
2041 constructor: ModuleValueConstructor::Constant { literal, .. },
2042 ..
2043 } => const_inline(literal, env),
2044
2045 TypedExpr::ModuleSelect {
2046 constructor: ModuleValueConstructor::Record { name, arity, .. },
2047 ..
2048 } => record_constructor_function(name, *arity as usize),
2049
2050 TypedExpr::ModuleSelect {
2051 type_,
2052 constructor:
2053 ModuleValueConstructor::Fn {
2054 external_erlang: Some((module, name)),
2055 ..
2056 }
2057 | ModuleValueConstructor::Fn { module, name, .. },
2058 ..
2059 } => module_select_fn(type_.clone(), module, name),
2060
2061 TypedExpr::RecordAccess { record, index, .. } => tuple_index(record, index + 1, env),
2062
2063 TypedExpr::RecordUpdate {
2064 record,
2065 constructor,
2066 args,
2067 ..
2068 } => record_update(record, constructor, args, env),
2069
2070 TypedExpr::Case {
2071 subjects, clauses, ..
2072 } => case(subjects, clauses, env),
2073
2074 TypedExpr::BinOp {
2075 name, left, right, ..
2076 } => bin_op(name, left, right, env),
2077
2078 TypedExpr::Tuple { elements, .. } => tuple(
2079 elements
2080 .iter()
2081 .map(|element| maybe_block_expr(element, env)),
2082 ),
2083
2084 TypedExpr::BitArray { segments, .. } => bit_array(
2085 segments
2086 .iter()
2087 .map(|s| expr_segment(&s.value, &s.options, env)),
2088 ),
2089
2090 TypedExpr::Invalid { .. } => panic!("invalid expressions should not reach code generation"),
2091 }
2092}
2093
2094fn pipeline<'a>(
2095 first_value: &'a TypedPipelineAssignment,
2096 assignments: &'a [(TypedPipelineAssignment, PipelineAssignmentKind)],
2097 finally: &'a TypedExpr,
2098 env: &mut Env<'a>,
2099) -> Document<'a> {
2100 let mut documents = Vec::with_capacity((assignments.len() + 1) * 3);
2101
2102 let all_assignments = std::iter::once(first_value)
2103 .chain(assignments.iter().map(|(assignment, _kind)| assignment));
2104
2105 let echo_doc = |var_name: &Option<Document<'a>>, location: &SrcSpan, env: &mut Env<'a>| {
2106 let name = var_name
2107 .to_owned()
2108 .expect("echo with no previous step in a pipe");
2109 echo(name, location, env)
2110 };
2111
2112 let mut prev_local_var_name = None;
2113 for a in all_assignments {
2114 match a.value.as_ref() {
2115 // An echo in a pipeline won't result in an assignment, instead it
2116 // just prints the previous variable assigned in the pipeline.
2117 TypedExpr::Echo {
2118 expression: None,
2119 location,
2120 ..
2121 } => documents.push(echo_doc(&prev_local_var_name, location, env)),
2122
2123 // Otherwise we assign the intermediate pipe value to a variable.
2124 _ => {
2125 let body = maybe_block_expr(&a.value, env).group();
2126 let name = env.next_local_var_name(&a.name);
2127 prev_local_var_name = Some(name.clone());
2128 documents.push(docvec![name, " = ", body]);
2129 }
2130 };
2131 documents.push(",".to_doc());
2132 documents.push(line());
2133 }
2134
2135 match finally {
2136 TypedExpr::Echo {
2137 expression: None,
2138 location,
2139 ..
2140 } => documents.push(echo_doc(&prev_local_var_name, location, env)),
2141 _ => documents.push(expr(finally, env)),
2142 }
2143
2144 documents.to_doc()
2145}
2146
2147fn assignment<'a>(assignment: &'a TypedAssignment, env: &mut Env<'a>) -> Document<'a> {
2148 match &assignment.kind {
2149 AssignmentKind::Let | AssignmentKind::Generated => {
2150 let_(&assignment.value, &assignment.pattern, env)
2151 }
2152 AssignmentKind::Assert { message, .. } => let_assert(
2153 &assignment.value,
2154 &assignment.pattern,
2155 env,
2156 message.as_deref(),
2157 ),
2158 }
2159}
2160
2161fn negate_with<'a>(op: &'static str, value: &'a TypedExpr, env: &mut Env<'a>) -> Document<'a> {
2162 docvec![op, maybe_block_expr(value, env)]
2163}
2164
2165fn tuple_index<'a>(tuple: &'a TypedExpr, index: u64, env: &mut Env<'a>) -> Document<'a> {
2166 let index_doc = eco_format!("{}", (index + 1)).to_doc();
2167 let tuple_doc = maybe_block_expr(tuple, env);
2168 "erlang:element"
2169 .to_doc()
2170 .append(wrap_args([index_doc, tuple_doc]))
2171}
2172
2173fn module_select_fn<'a>(type_: Arc<Type>, module_name: &'a str, label: &'a str) -> Document<'a> {
2174 match crate::type_::collapse_links(type_).as_ref() {
2175 Type::Fn { args, .. } => "fun "
2176 .to_doc()
2177 .append(module_name_to_erlang(module_name))
2178 .append(":")
2179 .append(atom(label))
2180 .append("/")
2181 .append(args.len()),
2182
2183 _ => module_name_to_erlang(module_name)
2184 .append(":")
2185 .append(atom(label))
2186 .append("()"),
2187 }
2188}
2189
2190fn fun<'a>(args: &'a [TypedArg], body: &'a [TypedStatement], env: &mut Env<'a>) -> Document<'a> {
2191 let current_scope_vars = env.current_scope_vars.clone();
2192 let doc = "fun"
2193 .to_doc()
2194 .append(fun_args(args, env).append(" ->"))
2195 .append(
2196 break_("", " ")
2197 .append(statement_sequence(body, env))
2198 .nest(INDENT),
2199 )
2200 .append(break_("", " "))
2201 .append("end")
2202 .group();
2203 env.current_scope_vars = current_scope_vars;
2204 doc
2205}
2206
2207fn incrementing_args_list(arity: usize) -> EcoString {
2208 let arguments = (0..arity).map(|c| format!("Field@{c}"));
2209 Itertools::intersperse(arguments, ", ".into())
2210 .collect::<String>()
2211 .into()
2212}
2213
2214fn variable_name(name: &str) -> EcoString {
2215 let mut chars = name.chars();
2216 let first_char = chars.next();
2217 let first_uppercased = first_char.into_iter().flat_map(char::to_uppercase);
2218
2219 first_uppercased.chain(chars).collect::<EcoString>()
2220}
2221
2222/// When rendering a type variable to an erlang type spec we need all type variables with the
2223/// same id to end up with the same name in the generated erlang.
2224/// This function converts a usize into base 26 A-Z for this purpose.
2225fn id_to_type_var(id: u64) -> Document<'static> {
2226 if id < 26 {
2227 let mut name = EcoString::from("");
2228 name.push(char::from_u32((id % 26 + 65) as u32).expect("id_to_type_var 0"));
2229 return name.to_doc();
2230 }
2231 let mut name = vec![];
2232 let mut last_char = id;
2233 while last_char >= 26 {
2234 name.push(char::from_u32((last_char % 26 + 65) as u32).expect("id_to_type_var 1"));
2235 last_char /= 26;
2236 }
2237 name.push(char::from_u32((last_char % 26 + 64) as u32).expect("id_to_type_var 2"));
2238 name.reverse();
2239 name.into_iter().collect::<EcoString>().to_doc()
2240}
2241
2242pub fn is_erlang_reserved_word(name: &str) -> bool {
2243 matches!(
2244 name,
2245 "!" | "receive"
2246 | "bnot"
2247 | "div"
2248 | "rem"
2249 | "band"
2250 | "bor"
2251 | "bxor"
2252 | "bsl"
2253 | "bsr"
2254 | "not"
2255 | "and"
2256 | "or"
2257 | "xor"
2258 | "orelse"
2259 | "andalso"
2260 | "when"
2261 | "end"
2262 | "fun"
2263 | "try"
2264 | "catch"
2265 | "after"
2266 | "begin"
2267 | "let"
2268 | "query"
2269 | "cond"
2270 | "if"
2271 | "of"
2272 | "case"
2273 | "maybe"
2274 | "else"
2275 )
2276}
2277
2278// Includes shell_default & user_default which are looked for by the erlang shell
2279pub fn is_erlang_standard_library_module(name: &str) -> bool {
2280 matches!(
2281 name,
2282 "array"
2283 | "base64"
2284 | "beam_lib"
2285 | "binary"
2286 | "c"
2287 | "calendar"
2288 | "dets"
2289 | "dict"
2290 | "digraph"
2291 | "digraph_utils"
2292 | "epp"
2293 | "erl_anno"
2294 | "erl_eval"
2295 | "erl_expand_records"
2296 | "erl_id_trans"
2297 | "erl_internal"
2298 | "erl_lint"
2299 | "erl_parse"
2300 | "erl_pp"
2301 | "erl_scan"
2302 | "erl_tar"
2303 | "ets"
2304 | "file_sorter"
2305 | "filelib"
2306 | "filename"
2307 | "gb_sets"
2308 | "gb_trees"
2309 | "gen_event"
2310 | "gen_fsm"
2311 | "gen_server"
2312 | "gen_statem"
2313 | "io"
2314 | "io_lib"
2315 | "lists"
2316 | "log_mf_h"
2317 | "maps"
2318 | "math"
2319 | "ms_transform"
2320 | "orddict"
2321 | "ordsets"
2322 | "pool"
2323 | "proc_lib"
2324 | "proplists"
2325 | "qlc"
2326 | "queue"
2327 | "rand"
2328 | "random"
2329 | "re"
2330 | "sets"
2331 | "shell"
2332 | "shell_default"
2333 | "shell_docs"
2334 | "slave"
2335 | "sofs"
2336 | "string"
2337 | "supervisor"
2338 | "supervisor_bridge"
2339 | "sys"
2340 | "timer"
2341 | "unicode"
2342 | "uri_string"
2343 | "user_default"
2344 | "win32reg"
2345 | "zip"
2346 )
2347}
2348
2349// Includes the functions that are autogenerated by erlang itself
2350pub fn escape_erlang_existing_name(name: &str) -> &str {
2351 match name {
2352 "module_info" => "moduleInfo",
2353 _ => name,
2354 }
2355}
2356
2357// A TypeVar can either be rendered as an actual type variable such as `A` or `B`,
2358// or it can be rendered as `any()` depending on how many usages it has. If it
2359// has only 1 usage it is an `any()` type. If it has more than 1 usage it is a
2360// type variable. This function gathers usages for this determination.
2361//
2362// Examples:
2363// fn(a) -> String // `a` is `any()`
2364// fn() -> Result(a, b) // `a` and `b` are `any()`
2365// fn(a) -> a // `a` is a type var
2366fn collect_type_var_usages<'a>(
2367 mut ids: HashMap<u64, u64>,
2368 types: impl IntoIterator<Item = &'a Arc<Type>>,
2369) -> HashMap<u64, u64> {
2370 for type_ in types {
2371 type_var_ids(type_, &mut ids);
2372 }
2373 ids
2374}
2375
2376fn result_type_var_ids(ids: &mut HashMap<u64, u64>, arg_ok: &Type, arg_err: &Type) {
2377 let mut ok_ids = HashMap::new();
2378 type_var_ids(arg_ok, &mut ok_ids);
2379
2380 let mut err_ids = HashMap::new();
2381 type_var_ids(arg_err, &mut err_ids);
2382
2383 let mut result_counts = ok_ids;
2384 for (id, count) in err_ids {
2385 let _ = result_counts
2386 .entry(id)
2387 .and_modify(|current_count| {
2388 if *current_count < count {
2389 *current_count = count;
2390 }
2391 })
2392 .or_insert(count);
2393 }
2394 for (id, count) in result_counts {
2395 let _ = ids
2396 .entry(id)
2397 .and_modify(|current_count| {
2398 *current_count += count;
2399 })
2400 .or_insert(count);
2401 }
2402}
2403
2404fn type_var_ids(type_: &Type, ids: &mut HashMap<u64, u64>) {
2405 match type_ {
2406 Type::Var { type_ } => match type_.borrow().deref() {
2407 TypeVar::Generic { id, .. } | TypeVar::Unbound { id, .. } => {
2408 let count = ids.entry(*id).or_insert(0);
2409 *count += 1;
2410 }
2411 TypeVar::Link { type_ } => type_var_ids(type_, ids),
2412 },
2413 Type::Named {
2414 args, module, name, ..
2415 } => match args[..] {
2416 [ref arg_ok, ref arg_err] if is_prelude_module(module) && name == "Result" => {
2417 result_type_var_ids(ids, arg_ok, arg_err)
2418 }
2419 _ => {
2420 for arg in args {
2421 type_var_ids(arg, ids)
2422 }
2423 }
2424 },
2425 Type::Fn { args, return_ } => {
2426 for arg in args {
2427 type_var_ids(arg, ids)
2428 }
2429 type_var_ids(return_, ids);
2430 }
2431 Type::Tuple { elements } => {
2432 for element in elements {
2433 type_var_ids(element, ids)
2434 }
2435 }
2436 }
2437}
2438
2439fn erl_safe_type_name(mut name: String) -> EcoString {
2440 if matches!(
2441 name.as_str(),
2442 "any"
2443 | "arity"
2444 | "atom"
2445 | "binary"
2446 | "bitstring"
2447 | "boolean"
2448 | "byte"
2449 | "char"
2450 | "dynamic"
2451 | "float"
2452 | "function"
2453 | "identifier"
2454 | "integer"
2455 | "iodata"
2456 | "iolist"
2457 | "list"
2458 | "map"
2459 | "maybe_improper_list"
2460 | "mfa"
2461 | "module"
2462 | "neg_integer"
2463 | "nil"
2464 | "no_return"
2465 | "node"
2466 | "non_neg_integer"
2467 | "none"
2468 | "nonempty_improper_list"
2469 | "nonempty_list"
2470 | "nonempty_string"
2471 | "number"
2472 | "pid"
2473 | "port"
2474 | "pos_integer"
2475 | "reference"
2476 | "string"
2477 | "term"
2478 | "timeout"
2479 | "tuple"
2480 ) {
2481 name.push('_');
2482 EcoString::from(name)
2483 } else {
2484 escape_atom_string(name)
2485 }
2486}
2487
2488#[derive(Debug)]
2489struct TypePrinter<'a> {
2490 var_as_any: bool,
2491 current_module: &'a str,
2492 var_usages: Option<&'a HashMap<u64, u64>>,
2493}
2494
2495impl<'a> TypePrinter<'a> {
2496 fn new(current_module: &'a str) -> Self {
2497 Self {
2498 current_module,
2499 var_usages: None,
2500 var_as_any: false,
2501 }
2502 }
2503
2504 pub fn with_var_usages(mut self, var_usages: &'a HashMap<u64, u64>) -> Self {
2505 self.var_usages = Some(var_usages);
2506 self
2507 }
2508
2509 pub fn print(&self, type_: &Type) -> Document<'static> {
2510 match type_ {
2511 Type::Var { type_ } => self.print_var(&type_.borrow()),
2512
2513 Type::Named {
2514 name, module, args, ..
2515 } if is_prelude_module(module) => self.print_prelude_type(name, args),
2516
2517 Type::Named {
2518 name, module, args, ..
2519 } => self.print_type_app(module, name, args),
2520
2521 Type::Fn { args, return_ } => self.print_fn(args, return_),
2522
2523 Type::Tuple { elements } => tuple(elements.iter().map(|element| self.print(element))),
2524 }
2525 }
2526
2527 fn print_var(&self, type_: &TypeVar) -> Document<'static> {
2528 match type_ {
2529 TypeVar::Generic { .. } | TypeVar::Unbound { .. } if self.var_as_any => {
2530 "any()".to_doc()
2531 }
2532 TypeVar::Generic { id, .. } | TypeVar::Unbound { id, .. } => match &self.var_usages {
2533 Some(usages) => match usages.get(id) {
2534 Some(&0) => nil(),
2535 Some(&1) => "any()".to_doc(),
2536 _ => id_to_type_var(*id),
2537 },
2538 None => id_to_type_var(*id),
2539 },
2540 TypeVar::Link { type_ } => self.print(type_),
2541 }
2542 }
2543
2544 fn print_prelude_type(&self, name: &str, args: &[Arc<Type>]) -> Document<'static> {
2545 match name {
2546 "Nil" => "nil".to_doc(),
2547 "Int" | "UtfCodepoint" => "integer()".to_doc(),
2548 "String" => "binary()".to_doc(),
2549 "Bool" => "boolean()".to_doc(),
2550 "Float" => "float()".to_doc(),
2551 "BitArray" => "bitstring()".to_doc(),
2552 "List" => {
2553 let arg0 = self.print(args.first().expect("print_prelude_type list"));
2554 "list(".to_doc().append(arg0).append(")")
2555 }
2556 "Result" => match args {
2557 [arg_ok, arg_err] => {
2558 let ok = tuple(["ok".to_doc(), self.print(arg_ok)]);
2559 let error = tuple(["error".to_doc(), self.print(arg_err)]);
2560 docvec![ok, break_(" |", " | "), error].nest(INDENT).group()
2561 }
2562 _ => panic!("print_prelude_type result expects ok and err"),
2563 },
2564 // Getting here should mean we either forgot a built-in type or there is a
2565 // compiler error
2566 name => panic!("{name} is not a built-in type."),
2567 }
2568 }
2569
2570 fn print_type_app(&self, module: &str, name: &str, args: &[Arc<Type>]) -> Document<'static> {
2571 let args = join(args.iter().map(|a| self.print(a)), ", ".to_doc());
2572 let name = erl_safe_type_name(name.to_snake_case()).to_doc();
2573 if self.current_module == module {
2574 docvec![name, "(", args, ")"]
2575 } else {
2576 docvec![module_name_atom(module), ":", name, "(", args, ")"]
2577 }
2578 }
2579
2580 fn print_fn(&self, args: &[Arc<Type>], return_: &Type) -> Document<'static> {
2581 let args = join(args.iter().map(|a| self.print(a)), ", ".to_doc());
2582 let return_ = self.print(return_);
2583 "fun(("
2584 .to_doc()
2585 .append(args)
2586 .append(") -> ")
2587 .append(return_)
2588 .append(")")
2589 }
2590
2591 /// Print type vars as `any()`.
2592 fn var_as_any(mut self) -> Self {
2593 self.var_as_any = true;
2594 self
2595 }
2596}
2597
2598fn find_private_functions_referenced_in_importable_constants(
2599 module: &TypedModule,
2600) -> HashSet<EcoString> {
2601 let mut overridden_publicity = HashSet::new();
2602
2603 for def in module.definitions.iter() {
2604 if let Definition::ModuleConstant(c) = def {
2605 if c.publicity.is_importable() {
2606 find_referenced_private_functions(&c.value, &mut overridden_publicity)
2607 }
2608 }
2609 }
2610 overridden_publicity
2611}
2612
2613fn find_referenced_private_functions(
2614 constant: &TypedConstant,
2615 already_found: &mut HashSet<EcoString>,
2616) {
2617 match constant {
2618 Constant::Invalid { .. } => panic!("invalid constants should not reach code generation"),
2619
2620 Constant::Int { .. }
2621 | Constant::Float { .. }
2622 | Constant::String { .. }
2623 | Constant::BitArray { .. } => (),
2624
2625 TypedConstant::Var {
2626 name, constructor, ..
2627 } => {
2628 if let Some(ValueConstructor { type_, .. }) = constructor.as_deref() {
2629 if let Type::Fn { .. } = **type_ {
2630 let _ = already_found.insert(name.clone());
2631 }
2632 }
2633 }
2634
2635 TypedConstant::Record { args, .. } => args
2636 .iter()
2637 .for_each(|arg| find_referenced_private_functions(&arg.value, already_found)),
2638
2639 TypedConstant::StringConcatenation { left, right, .. } => {
2640 find_referenced_private_functions(left, already_found);
2641 find_referenced_private_functions(right, already_found);
2642 }
2643
2644 Constant::Tuple { elements, .. } | Constant::List { elements, .. } => elements
2645 .iter()
2646 .for_each(|element| find_referenced_private_functions(element, already_found)),
2647 }
2648}