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