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