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