Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

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