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
108 kB 3400 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::erlang::pattern::{PatternPrinter, StringPatternAssignment}; 11use crate::strings::{convert_string_escape_chars, to_snake_case}; 12use crate::type_::is_prelude_module; 13use crate::{ 14 Result, 15 ast::{Function, *}, 16 docvec, 17 line_numbers::LineNumbers, 18 pretty::*, 19 type_::{ 20 ModuleValueConstructor, PatternConstructor, Type, TypeVar, TypedCallArg, ValueConstructor, 21 ValueConstructorVariant, 22 }, 23}; 24use camino::Utf8Path; 25use ecow::{EcoString, eco_format}; 26use itertools::Itertools; 27use regex::{Captures, Regex}; 28use std::collections::HashSet; 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_atom(module: &str) -> Document<'static> { 37 atom_string(module.replace('/', "@").into()) 38} 39 40#[derive(Debug, Clone)] 41struct Env<'a> { 42 module: &'a str, 43 function: &'a str, 44 line_numbers: &'a LineNumbers, 45 needs_function_docs: bool, 46 echo_used: bool, 47 current_scope_vars: im::HashMap<String, usize>, 48 erl_function_scope_vars: im::HashMap<String, usize>, 49} 50 51impl<'env> Env<'env> { 52 pub fn new(module: &'env str, function: &'env str, line_numbers: &'env LineNumbers) -> Self { 53 let vars: im::HashMap<_, _> = std::iter::once(("_".into(), 0)).collect(); 54 Self { 55 current_scope_vars: vars.clone(), 56 erl_function_scope_vars: vars, 57 needs_function_docs: false, 58 echo_used: false, 59 line_numbers, 60 function, 61 module, 62 } 63 } 64 65 pub fn local_var_name<'a>(&mut self, name: &str) -> Document<'a> { 66 match self.current_scope_vars.get(name) { 67 None => { 68 let _ = self.current_scope_vars.insert(name.to_string(), 0); 69 let _ = self.erl_function_scope_vars.insert(name.to_string(), 0); 70 variable_name(name).to_doc() 71 } 72 Some(0) => variable_name(name).to_doc(), 73 Some(n) => { 74 use std::fmt::Write; 75 let mut name = variable_name(name); 76 write!(name, "@{n}").expect("pushing number suffix to name"); 77 name.to_doc() 78 } 79 } 80 } 81 82 pub fn next_local_var_name<'a>(&mut self, name: &str) -> Document<'a> { 83 let next = self.erl_function_scope_vars.get(name).map_or(0, |i| i + 1); 84 let _ = self.erl_function_scope_vars.insert(name.to_string(), next); 85 let _ = self.current_scope_vars.insert(name.to_string(), next); 86 self.local_var_name(name) 87 } 88} 89 90pub fn records(module: &TypedModule) -> Vec<(&str, String)> { 91 module 92 .definitions 93 .custom_types 94 .iter() 95 .filter(|custom_type| { 96 custom_type.publicity.is_public() 97 && !module 98 .unused_definition_positions 99 .contains(&custom_type.location.start) 100 }) 101 .flat_map(|custom_type| &custom_type.constructors) 102 .filter(|constructor| !constructor.arguments.is_empty()) 103 .filter_map(|constructor| { 104 constructor 105 .arguments 106 .iter() 107 .map( 108 |RecordConstructorArg { 109 label, 110 ast: _, 111 location: _, 112 type_, 113 .. 114 }| { 115 label 116 .as_ref() 117 .map(|(_, label)| (label.as_str(), type_.clone())) 118 }, 119 ) 120 .collect::<Option<Vec<_>>>() 121 .map(|fields| (constructor.name.as_str(), fields)) 122 }) 123 .map(|(name, fields)| (name, record_definition(name, &fields))) 124 .collect() 125} 126 127pub fn record_definition(name: &str, fields: &[(&str, Arc<Type>)]) -> String { 128 let name = to_snake_case(name); 129 let type_printer = TypePrinter::new("").var_as_any(); 130 let fields = fields.iter().map(move |(name, type_)| { 131 let type_ = type_printer.print(type_); 132 docvec![atom_string((*name).into()), " :: ", type_.group()] 133 }); 134 let fields = break_("", "") 135 .append(join(fields, break_(",", ", "))) 136 .nest(INDENT) 137 .append(break_("", "")) 138 .group(); 139 docvec!["-record(", atom_string(name), ", {", fields, "}).", line()] 140 .to_pretty_string(MAX_COLUMNS) 141} 142 143pub fn module<'a>( 144 module: &'a TypedModule, 145 line_numbers: &'a LineNumbers, 146 root: &'a Utf8Path, 147) -> Result<String> { 148 Ok(module_document(module, line_numbers, root)?.to_pretty_string(MAX_COLUMNS)) 149} 150 151fn module_document<'a>( 152 module: &'a TypedModule, 153 line_numbers: &'a LineNumbers, 154 root: &'a Utf8Path, 155) -> Result<Document<'a>> { 156 let mut exports = vec![]; 157 let mut type_defs = vec![]; 158 let mut type_exports = vec![]; 159 160 let header = "-module(" 161 .to_doc() 162 .append(module.erlang_name()) 163 .append(").") 164 .append(line()); 165 166 // We need to know which private functions are referenced in importable 167 // constants so that we can export them anyway in the generated Erlang. 168 // This is because otherwise when the constant is used in another module it 169 // would result in an error as it tries to reference this private function. 170 let overridden_publicity = find_private_functions_referenced_in_importable_constants(module); 171 172 for function in &module.definitions.functions { 173 register_function_exports(function, &mut exports, &overridden_publicity); 174 } 175 176 for custom_type in &module.definitions.custom_types { 177 register_custom_type_exports(custom_type, &mut type_exports, &mut type_defs, &module.name); 178 } 179 180 let exports = match (!exports.is_empty(), !type_exports.is_empty()) { 181 (false, false) => return Ok(header), 182 (true, false) => "-export([" 183 .to_doc() 184 .append(join(exports, ", ".to_doc())) 185 .append("]).") 186 .append(lines(2)), 187 188 (true, true) => "-export([" 189 .to_doc() 190 .append(join(exports, ", ".to_doc())) 191 .append("]).") 192 .append(line()) 193 .append("-export_type([") 194 .to_doc() 195 .append(join(type_exports, ", ".to_doc())) 196 .append("]).") 197 .append(lines(2)), 198 199 (false, true) => "-export_type([" 200 .to_doc() 201 .append(join(type_exports, ", ".to_doc())) 202 .append("]).") 203 .append(lines(2)), 204 }; 205 206 let type_defs = if type_defs.is_empty() { 207 nil() 208 } else { 209 join(type_defs, lines(2)).append(lines(2)) 210 }; 211 212 let src_path_full = &module.type_info.src_path; 213 let src_path_relative = EcoString::from( 214 src_path_full 215 .strip_prefix(root) 216 .unwrap_or(src_path_full) 217 .as_str(), 218 ) 219 .replace("\\", "\\\\"); 220 221 let mut needs_function_docs = false; 222 let mut echo_used = false; 223 let mut statements = vec![]; 224 for function in &module.definitions.functions { 225 if let Some((statement_document, env)) = module_function( 226 function, 227 &module.name, 228 module.type_info.is_internal, 229 line_numbers, 230 src_path_relative.clone(), 231 &module.unused_definition_positions, 232 ) { 233 needs_function_docs = needs_function_docs || env.needs_function_docs; 234 echo_used = echo_used || env.echo_used; 235 statements.push(statement_document); 236 } 237 } 238 239 let module_doc = if module.type_info.is_internal { 240 Some(hidden_module_doc().append(lines(2))) 241 } else if module.documentation.is_empty() { 242 None 243 } else { 244 Some(module_doc(&module.documentation).append(lines(2))) 245 }; 246 247 // We're going to need the documentation directives if any of the module's 248 // functions need it, or if the module has a module comment that we want to 249 // include in the generated Erlang source, or if the module is internal. 250 let needs_doc_directive = needs_function_docs || module_doc.is_some(); 251 let documentation_directive = if needs_doc_directive { 252 "-if(?OTP_RELEASE >= 27). 253-define(MODULEDOC(Str), -moduledoc(Str)). 254-define(DOC(Str), -doc(Str)). 255-else. 256-define(MODULEDOC(Str), -compile([])). 257-define(DOC(Str), -compile([])). 258-endif." 259 .to_doc() 260 .append(lines(2)) 261 } else { 262 nil() 263 }; 264 265 let module = docvec![ 266 header, 267 "-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).", 268 line(), 269 "-define(FILEPATH, \"", 270 src_path_relative, 271 "\").", 272 line(), 273 exports, 274 documentation_directive, 275 module_doc, 276 type_defs, 277 join(statements, lines(2)), 278 ]; 279 280 let module = if echo_used { 281 module 282 .append(lines(2)) 283 .append(std::include_str!("../templates/echo.erl").to_doc()) 284 } else { 285 module 286 }; 287 288 Ok(module.append(line())) 289} 290 291fn register_function_exports( 292 function: &TypedFunction, 293 exports: &mut Vec<Document<'_>>, 294 overridden_publicity: &im::HashSet<EcoString>, 295) { 296 let Function { 297 publicity, 298 name: Some((_, name)), 299 arguments, 300 implementations, 301 .. 302 } = function 303 else { 304 return; 305 }; 306 307 // If the function isn't for this target then don't attempt to export it 308 if implementations.supports(Target::Erlang) 309 && (publicity.is_importable() || overridden_publicity.contains(name)) 310 { 311 let function_name = escape_erlang_existing_name(name); 312 exports.push( 313 atom_string(function_name.into()) 314 .append("/") 315 .append(arguments.len()), 316 ) 317 } 318} 319 320fn register_custom_type_exports( 321 custom_type: &TypedCustomType, 322 type_exports: &mut Vec<Document<'_>>, 323 type_defs: &mut Vec<Document<'_>>, 324 module_name: &str, 325) { 326 let TypedCustomType { 327 name, 328 constructors, 329 opaque, 330 typed_parameters, 331 external_erlang, 332 .. 333 } = custom_type; 334 335 // Erlang doesn't allow phantom type variables in type definitions but gleam does 336 // so we check the type declaratinon against its constroctors and generate a phantom 337 // value that uses the unused type variables. 338 let type_var_usages = collect_type_var_usages(HashMap::new(), typed_parameters); 339 let mut constructor_var_usages = HashMap::new(); 340 for c in constructors { 341 constructor_var_usages = 342 collect_type_var_usages(constructor_var_usages, c.arguments.iter().map(|a| &a.type_)); 343 } 344 let phantom_vars: Vec<_> = type_var_usages 345 .keys() 346 .filter(|&id| !constructor_var_usages.contains_key(id)) 347 .sorted() 348 .map(|&id| Type::Var { 349 type_: Arc::new(std::cell::RefCell::new(TypeVar::Generic { id })), 350 }) 351 .collect(); 352 let phantom_vars_constructor = if !phantom_vars.is_empty() { 353 let type_printer = TypePrinter::new(module_name); 354 Some(tuple( 355 std::iter::once("gleam_phantom".to_doc()) 356 .chain(phantom_vars.iter().map(|pv| type_printer.print(pv))), 357 )) 358 } else { 359 None 360 }; 361 // Type Exports 362 type_exports.push( 363 erl_safe_type_name(to_snake_case(name)) 364 .to_doc() 365 .append("/") 366 .append(typed_parameters.len()), 367 ); 368 // Type definitions 369 let definition = if constructors.is_empty() { 370 if let Some((module, external_type, _location)) = external_erlang { 371 let printer = TypePrinter::new(module_name); 372 docvec![ 373 module, 374 ":", 375 external_type, 376 "(", 377 join( 378 typed_parameters 379 .iter() 380 .map(|parameter| printer.print(parameter)), 381 ", ".to_doc() 382 ), 383 ")" 384 ] 385 } else { 386 let constructors = std::iter::once("any()".to_doc()).chain(phantom_vars_constructor); 387 join(constructors, break_(" |", " | ")) 388 } 389 } else { 390 let constructors = constructors 391 .iter() 392 .map(|constructor| { 393 let name = atom_string(to_snake_case(&constructor.name)); 394 if constructor.arguments.is_empty() { 395 name 396 } else { 397 let type_printer = TypePrinter::new(module_name); 398 let arguments = constructor 399 .arguments 400 .iter() 401 .map(|argument| type_printer.print(&argument.type_)); 402 tuple(std::iter::once(name).chain(arguments)) 403 } 404 }) 405 .chain(phantom_vars_constructor); 406 join(constructors, break_(" |", " | ")) 407 } 408 .nest(INDENT); 409 let type_printer = TypePrinter::new(module_name); 410 let params = join( 411 typed_parameters 412 .iter() 413 .map(|type_| type_printer.print(type_)), 414 ", ".to_doc(), 415 ); 416 let doc = if *opaque { "-opaque " } else { "-type " } 417 .to_doc() 418 .append(erl_safe_type_name(to_snake_case(name))) 419 .append("(") 420 .append(params) 421 .append(") :: ") 422 .append(definition) 423 .group() 424 .append("."); 425 type_defs.push(doc); 426} 427 428fn module_function<'a>( 429 function: &'a TypedFunction, 430 module: &'a str, 431 is_internal_module: bool, 432 line_numbers: &'a LineNumbers, 433 src_path: EcoString, 434 unused_definition_positions: &HashSet<u32>, 435) -> Option<(Document<'a>, Env<'a>)> { 436 // We don't generate any code for unused functions. 437 if unused_definition_positions.contains(&function.location.start) { 438 return None; 439 } 440 441 // Private external functions don't need to render anything, the underlying 442 // Erlang implementation is used directly at the call site. 443 if function.external_erlang.is_some() && function.publicity.is_private() { 444 return None; 445 } 446 447 // If the function has no suitable Erlang implementation then there is nothing 448 // to generate for it. 449 if !function.implementations.supports(Target::Erlang) { 450 return None; 451 } 452 453 let (_, function_name) = function 454 .name 455 .as_ref() 456 .expect("A module's function must be named"); 457 let function_name = escape_erlang_existing_name(function_name); 458 let file_attribute = file_attribute(src_path, function, line_numbers); 459 460 let mut env = Env::new(module, function_name, line_numbers); 461 let var_usages = collect_type_var_usages( 462 HashMap::new(), 463 std::iter::once(&function.return_type).chain(function.arguments.iter().map(|a| &a.type_)), 464 ); 465 let type_printer = TypePrinter::new(module).with_var_usages(&var_usages); 466 let arguments_spec = function 467 .arguments 468 .iter() 469 .map(|a| type_printer.print(&a.type_)); 470 let return_spec = type_printer.print(&function.return_type); 471 472 let spec = fun_spec(function_name, arguments_spec, return_spec); 473 let arguments = if function.external_erlang.is_some() { 474 external_fun_arguments(&function.arguments, &mut env) 475 } else { 476 fun_arguments(&function.arguments, &mut env) 477 }; 478 479 let body = function 480 .external_erlang 481 .as_ref() 482 .map(|(module, function, _location)| { 483 docvec![ 484 atom(module), 485 ":", 486 atom(escape_erlang_existing_name(function)), 487 arguments.clone() 488 ] 489 }) 490 .unwrap_or_else(|| statement_sequence(&function.body, &mut env)); 491 492 let attributes = file_attribute; 493 let attributes = if is_internal_module || function.publicity.is_internal() { 494 // If a function is marked as internal or comes from an internal module 495 // we want to hide its documentation in the Erlang shell! 496 // So the doc directive will look like this: `-doc(false).` 497 env.needs_function_docs = true; 498 docvec![attributes, line(), hidden_function_doc()] 499 } else { 500 match &function.documentation { 501 Some((_, documentation)) => { 502 env.needs_function_docs = true; 503 let doc_lines = documentation 504 .trim_end() 505 .split('\n') 506 .map(EcoString::from) 507 .collect_vec(); 508 docvec![attributes, line(), function_doc(&doc_lines)] 509 } 510 _ => attributes, 511 } 512 }; 513 514 Some(( 515 docvec![ 516 attributes, 517 line(), 518 spec, 519 atom_string(escape_erlang_existing_name(function_name).into()), 520 arguments, 521 " ->", 522 line().append(body).nest(INDENT).group(), 523 ".", 524 ], 525 env, 526 )) 527} 528 529fn file_attribute<'a>( 530 path: EcoString, 531 function: &'a TypedFunction, 532 line_numbers: &'a LineNumbers, 533) -> Document<'a> { 534 let line = line_numbers.line_number(function.location.start); 535 docvec!["-file(\"", path, "\", ", line, ")."] 536} 537 538enum DocCommentKind { 539 Module, 540 Function, 541} 542 543enum DocCommentContent<'a> { 544 String(&'a Vec<EcoString>), 545 False, 546} 547 548fn hidden_module_doc<'a>() -> Document<'a> { 549 doc_attribute(DocCommentKind::Module, DocCommentContent::False) 550} 551 552fn module_doc<'a>(content: &Vec<EcoString>) -> Document<'a> { 553 doc_attribute(DocCommentKind::Module, DocCommentContent::String(content)) 554} 555 556fn hidden_function_doc<'a>() -> Document<'a> { 557 doc_attribute(DocCommentKind::Function, DocCommentContent::False) 558} 559 560fn function_doc<'a>(content: &Vec<EcoString>) -> Document<'a> { 561 doc_attribute(DocCommentKind::Function, DocCommentContent::String(content)) 562} 563 564fn doc_attribute<'a>(kind: DocCommentKind, content: DocCommentContent<'_>) -> Document<'a> { 565 let prefix = match kind { 566 DocCommentKind::Module => "?MODULEDOC", 567 DocCommentKind::Function => "?DOC", 568 }; 569 570 match content { 571 DocCommentContent::False => prefix.to_doc().append("(false)."), 572 DocCommentContent::String(doc_lines) => { 573 let is_multiline_doc_comment = doc_lines.len() > 1; 574 let doc_lines = join( 575 doc_lines.iter().map(|line| { 576 let line = line.replace("\\", "\\\\").replace("\"", "\\\""); 577 docvec!["\"", line, "\\n\""] 578 }), 579 line(), 580 ); 581 if is_multiline_doc_comment { 582 let nested_documentation = docvec![line(), doc_lines].nest(INDENT); 583 docvec![prefix, "(", nested_documentation, line(), ")."] 584 } else { 585 docvec![prefix, "(", doc_lines, ")."] 586 } 587 } 588 } 589} 590 591fn external_fun_arguments<'a>(arguments: &'a [TypedArg], env: &mut Env<'a>) -> Document<'a> { 592 wrap_arguments(arguments.iter().map(|argument| { 593 let name = match &argument.names { 594 ArgNames::Discard { name, .. } 595 | ArgNames::LabelledDiscard { name, .. } 596 | ArgNames::Named { name, .. } 597 | ArgNames::NamedLabelled { name, .. } => name, 598 }; 599 if name.chars().all(|c| c == '_') { 600 env.next_local_var_name("argument") 601 } else { 602 env.next_local_var_name(name) 603 } 604 })) 605} 606 607fn fun_arguments<'a>(arguments: &'a [TypedArg], env: &mut Env<'a>) -> Document<'a> { 608 wrap_arguments(arguments.iter().map(|argument| match &argument.names { 609 ArgNames::Discard { .. } | ArgNames::LabelledDiscard { .. } => "_".to_doc(), 610 ArgNames::Named { name, .. } | ArgNames::NamedLabelled { name, .. } => { 611 env.next_local_var_name(name) 612 } 613 })) 614} 615 616fn wrap_arguments<'a, I>(arguments: I) -> Document<'a> 617where 618 I: IntoIterator<Item = Document<'a>>, 619{ 620 break_("", "") 621 .append(join(arguments, break_(",", ", "))) 622 .nest(INDENT) 623 .append(break_("", "")) 624 .surround("(", ")") 625 .group() 626} 627 628fn fun_spec<'a>( 629 name: &'a str, 630 arguments: impl IntoIterator<Item = Document<'a>>, 631 return_: Document<'a>, 632) -> Document<'a> { 633 "-spec " 634 .to_doc() 635 .append(atom(name)) 636 .append(wrap_arguments(arguments)) 637 .append(" -> ") 638 .append(return_) 639 .append(".") 640 .append(line()) 641 .group() 642} 643 644fn atom_string(value: EcoString) -> Document<'static> { 645 escape_atom_string(value).to_doc() 646} 647 648fn atom_pattern() -> &'static Regex { 649 static ATOM_PATTERN: OnceLock<Regex> = OnceLock::new(); 650 ATOM_PATTERN.get_or_init(|| Regex::new(r"^[a-z][a-z0-9_@]*$").expect("atom RE regex")) 651} 652 653fn atom(value: &str) -> Document<'_> { 654 if is_erlang_reserved_word(value) { 655 // Escape because of keyword collision 656 eco_format!("'{value}'").to_doc() 657 } else if atom_pattern().is_match(value) { 658 // No need to escape 659 EcoString::from(value).to_doc() 660 } else { 661 // Escape because of characters contained 662 eco_format!("'{value}'").to_doc() 663 } 664} 665 666pub fn escape_atom_string(value: EcoString) -> EcoString { 667 if is_erlang_reserved_word(&value) { 668 // Escape because of keyword collision 669 eco_format!("'{value}'") 670 } else if atom_pattern().is_match(&value) { 671 value 672 } else { 673 // Escape because of characters contained 674 eco_format!("'{value}'") 675 } 676} 677 678fn unicode_escape_sequence_pattern() -> &'static Regex { 679 static PATTERN: OnceLock<Regex> = OnceLock::new(); 680 PATTERN.get_or_init(|| { 681 Regex::new(r#"(\\+)(u)"#).expect("Unicode escape sequence regex cannot be constructed") 682 }) 683} 684 685fn string_inner(value: &str) -> Document<'_> { 686 let content = unicode_escape_sequence_pattern() 687 // `\\u`-s should not be affected, so that "\\u..." is not converted to 688 // "\\x...". That's why capturing groups is used to exclude cases that 689 // shouldn't be replaced. 690 .replace_all(value, |caps: &Captures<'_>| { 691 let slashes = caps.get(1).map_or("", |m| m.as_str()); 692 693 if slashes.len().is_multiple_of(2) { 694 format!("{slashes}u") 695 } else { 696 format!("{slashes}x") 697 } 698 }); 699 EcoString::from(content).to_doc() 700} 701 702fn string(value: &str) -> Document<'_> { 703 string_inner(value).surround("<<\"", "\"/utf8>>") 704} 705 706fn string_length_utf8_bytes(str: &EcoString) -> usize { 707 convert_string_escape_chars(str).len() 708} 709 710fn tuple<'a>(elements: impl IntoIterator<Item = Document<'a>>) -> Document<'a> { 711 join(elements, break_(",", ", ")) 712 .nest(INDENT) 713 .surround("{", "}") 714 .group() 715} 716 717fn const_string_concatenate_bit_array<'a>( 718 elements: impl IntoIterator<Item = Document<'a>>, 719) -> Document<'a> { 720 join(elements, break_(",", ", ")) 721 .nest(INDENT) 722 .surround("<<", ">>") 723 .group() 724} 725 726fn const_string_concatenate<'a>( 727 left: &'a TypedConstant, 728 right: &'a TypedConstant, 729 env: &mut Env<'a>, 730) -> Document<'a> { 731 let left = const_string_concatenate_argument(left, env); 732 let right = const_string_concatenate_argument(right, env); 733 const_string_concatenate_bit_array([left, right]) 734} 735 736fn const_string_concatenate_inner<'a>( 737 left: &'a TypedConstant, 738 right: &'a TypedConstant, 739 env: &mut Env<'a>, 740) -> Document<'a> { 741 let left = const_string_concatenate_argument(left, env); 742 let right = const_string_concatenate_argument(right, env); 743 join([left, right], break_(",", ", ")) 744} 745 746fn const_string_concatenate_argument<'a>( 747 value: &'a TypedConstant, 748 env: &mut Env<'a>, 749) -> Document<'a> { 750 match value { 751 Constant::String { value, .. } => docvec!['"', string_inner(value), "\"/utf8"], 752 753 Constant::Var { 754 constructor: Some(constructor), 755 .. 756 } => match &constructor.variant { 757 ValueConstructorVariant::ModuleConstant { 758 literal: Constant::String { value, .. }, 759 .. 760 } => docvec!['"', string_inner(value), "\"/utf8"], 761 ValueConstructorVariant::ModuleConstant { 762 literal: Constant::StringConcatenation { left, right, .. }, 763 .. 764 } => const_string_concatenate_inner(left, right, env), 765 ValueConstructorVariant::LocalVariable { .. } 766 | ValueConstructorVariant::ModuleConstant { .. } 767 | ValueConstructorVariant::ModuleFn { .. } 768 | ValueConstructorVariant::Record { .. } => const_inline(value, env), 769 }, 770 771 Constant::StringConcatenation { left, right, .. } => { 772 const_string_concatenate_inner(left, right, env) 773 } 774 775 Constant::Int { .. } 776 | Constant::Float { .. } 777 | Constant::Tuple { .. } 778 | Constant::List { .. } 779 | Constant::Record { .. } 780 | Constant::RecordUpdate { .. } 781 | Constant::BitArray { .. } 782 | Constant::Var { .. } 783 | Constant::Invalid { .. } => const_inline(value, env), 784 } 785} 786 787fn string_concatenate<'a>( 788 left: &'a TypedExpr, 789 right: &'a TypedExpr, 790 env: &mut Env<'a>, 791) -> Document<'a> { 792 let left = string_concatenate_argument(left, env); 793 let right = string_concatenate_argument(right, env); 794 bit_array([left, right]) 795} 796 797fn string_concatenate_argument<'a>(value: &'a TypedExpr, env: &mut Env<'a>) -> Document<'a> { 798 match value { 799 TypedExpr::Var { 800 constructor: 801 ValueConstructor { 802 variant: 803 ValueConstructorVariant::ModuleConstant { 804 literal: Constant::String { value, .. }, 805 .. 806 }, 807 .. 808 }, 809 .. 810 } 811 | TypedExpr::String { value, .. } => docvec!['"', string_inner(value), "\"/utf8"], 812 813 TypedExpr::Var { 814 name, 815 constructor: 816 ValueConstructor { 817 variant: ValueConstructorVariant::LocalVariable { .. }, 818 .. 819 }, 820 .. 821 } => docvec![env.local_var_name(name), "/binary"], 822 823 TypedExpr::BinOp { 824 name: BinOp::Concatenate, 825 .. 826 } => docvec![expr(value, env), "/binary"], 827 828 TypedExpr::Int { .. } 829 | TypedExpr::Float { .. } 830 | TypedExpr::Block { .. } 831 | TypedExpr::Pipeline { .. } 832 | TypedExpr::Var { .. } 833 | TypedExpr::Fn { .. } 834 | TypedExpr::List { .. } 835 | TypedExpr::Call { .. } 836 | TypedExpr::BinOp { .. } 837 | TypedExpr::Case { .. } 838 | TypedExpr::RecordAccess { .. } 839 | TypedExpr::PositionalAccess { .. } 840 | TypedExpr::ModuleSelect { .. } 841 | TypedExpr::Tuple { .. } 842 | TypedExpr::TupleIndex { .. } 843 | TypedExpr::Todo { .. } 844 | TypedExpr::Panic { .. } 845 | TypedExpr::Echo { .. } 846 | TypedExpr::BitArray { .. } 847 | TypedExpr::RecordUpdate { .. } 848 | TypedExpr::NegateBool { .. } 849 | TypedExpr::NegateInt { .. } 850 | TypedExpr::Invalid { .. } => docvec!["(", maybe_block_expr(value, env), ")/binary"], 851 } 852} 853 854fn bit_array<'a>(elements: impl IntoIterator<Item = Document<'a>>) -> Document<'a> { 855 join(elements, break_(",", ", ")) 856 .nest(INDENT) 857 .surround("<<", ">>") 858 .group() 859} 860 861fn const_segment<'a>( 862 value: &'a TypedConstant, 863 options: &'a [TypedConstantBitArraySegmentOption], 864 env: &mut Env<'a>, 865) -> Document<'a> { 866 let value_is_a_string_literal = matches!(value, Constant::String { .. }); 867 868 let create_document = |env: &mut Env<'a>| { 869 match value { 870 // Skip the normal <<value/utf8>> surrounds 871 Constant::String { value, .. } => value.to_doc().surround("\"", "\""), 872 873 // As normal 874 Constant::Int { .. } | Constant::Float { .. } | Constant::BitArray { .. } => { 875 const_inline(value, env) 876 } 877 878 // Wrap anything else in parentheses 879 Constant::Tuple { .. } 880 | Constant::List { .. } 881 | Constant::Record { .. } 882 | Constant::RecordUpdate { .. } 883 | Constant::Var { .. } 884 | Constant::StringConcatenation { .. } 885 | Constant::Invalid { .. } => const_inline(value, env).surround("(", ")"), 886 } 887 }; 888 889 let size = |value: &'a TypedConstant, env: &mut Env<'a>| { 890 if let Constant::Int { .. } = value { 891 Some(":".to_doc().append(const_inline(value, env))) 892 } else { 893 Some( 894 ":".to_doc() 895 .append(const_inline(value, env).surround("(", ")")), 896 ) 897 } 898 }; 899 900 let unit = |value: &'a u8| Some(eco_format!("unit:{value}").to_doc()); 901 902 bit_array_segment( 903 create_document, 904 options, 905 size, 906 unit, 907 value_is_a_string_literal, 908 false, 909 env, 910 ) 911} 912 913enum Position { 914 Tail, 915 NotTail, 916} 917 918fn statement<'a>( 919 statement: &'a TypedStatement, 920 env: &mut Env<'a>, 921 position: Position, 922) -> Document<'a> { 923 match statement { 924 Statement::Expression(e) => expr(e, env), 925 Statement::Assignment(a) => assignment(a, env, position), 926 Statement::Use(use_) => expr(&use_.call, env), 927 Statement::Assert(a) => assert(a, env), 928 } 929} 930 931fn expr_segment<'a>( 932 value: &'a TypedExpr, 933 options: &'a [BitArrayOption<TypedExpr>], 934 env: &mut Env<'a>, 935) -> Document<'a> { 936 let value_is_a_string_literal = matches!(value, TypedExpr::String { .. }); 937 938 let create_document = |env: &mut Env<'a>| { 939 match value { 940 // Skip the normal <<value/utf8>> surrounds and set the string literal flag 941 TypedExpr::String { value, .. } => string_inner(value).surround("\"", "\""), 942 943 // As normal 944 TypedExpr::Int { .. } 945 | TypedExpr::Float { .. } 946 | TypedExpr::Var { .. } 947 | TypedExpr::BitArray { .. } => expr(value, env), 948 949 // Wrap anything else in parentheses 950 TypedExpr::Block { .. } 951 | TypedExpr::Pipeline { .. } 952 | TypedExpr::Fn { .. } 953 | TypedExpr::List { .. } 954 | TypedExpr::Call { .. } 955 | TypedExpr::BinOp { .. } 956 | TypedExpr::Case { .. } 957 | TypedExpr::RecordAccess { .. } 958 | TypedExpr::PositionalAccess { .. } 959 | TypedExpr::ModuleSelect { .. } 960 | TypedExpr::Tuple { .. } 961 | TypedExpr::TupleIndex { .. } 962 | TypedExpr::Todo { .. } 963 | TypedExpr::Panic { .. } 964 | TypedExpr::Echo { .. } 965 | TypedExpr::RecordUpdate { .. } 966 | TypedExpr::NegateBool { .. } 967 | TypedExpr::NegateInt { .. } 968 | TypedExpr::Invalid { .. } => expr(value, env).surround("(", ")"), 969 } 970 }; 971 972 let size = |expression: &'a TypedExpr, env: &mut Env<'a>| { 973 if let TypedExpr::Int { value, .. } = expression { 974 let v = value.replace("_", ""); 975 let v = u64::from_str(&v).unwrap_or(0); 976 Some(eco_format!(":{v}").to_doc()) 977 } else { 978 let inner_expr = maybe_block_expr(expression, env).surround("(", ")"); 979 // The value of size must be a non-negative integer, we use lists:max here to ensure 980 // it is at least 0; 981 let value_guard = ":(lists:max([" 982 .to_doc() 983 .append(inner_expr) 984 .append(", 0]))") 985 .group(); 986 Some(value_guard) 987 } 988 }; 989 990 let unit = |value: &'a u8| Some(eco_format!("unit:{value}").to_doc()); 991 992 bit_array_segment( 993 create_document, 994 options, 995 size, 996 unit, 997 value_is_a_string_literal, 998 false, 999 env, 1000 ) 1001} 1002 1003fn bit_array_segment<'a, Value: 'a, CreateDoc, SizeToDoc, UnitToDoc, State>( 1004 mut create_document: CreateDoc, 1005 options: &'a [BitArrayOption<Value>], 1006 mut size_to_doc: SizeToDoc, 1007 mut unit_to_doc: UnitToDoc, 1008 value_is_a_string_literal: bool, 1009 value_is_a_discard: bool, 1010 state: &mut State, 1011) -> Document<'a> 1012where 1013 CreateDoc: FnMut(&mut State) -> Document<'a>, 1014 SizeToDoc: FnMut(&'a Value, &mut State) -> Option<Document<'a>>, 1015 UnitToDoc: FnMut(&'a u8) -> Option<Document<'a>>, 1016{ 1017 let mut size: Option<Document<'a>> = None; 1018 let mut unit: Option<Document<'a>> = None; 1019 let mut others = Vec::new(); 1020 1021 // Erlang only allows valid codepoint integers to be used as values for utf segments 1022 // We want to support <<string_var:utf8>> for all string variables, but <<StringVar/utf8>> is invalid 1023 // To work around this we use the binary type specifier for these segments instead 1024 let override_type = if !value_is_a_string_literal && !value_is_a_discard { 1025 Some("binary") 1026 } else { 1027 None 1028 }; 1029 1030 for option in options { 1031 use BitArrayOption as Opt; 1032 if !others.is_empty() && !matches!(option, Opt::Size { .. } | Opt::Unit { .. }) { 1033 others.push("-".to_doc()); 1034 } 1035 match option { 1036 Opt::Utf8 { .. } => others.push(override_type.unwrap_or("utf8").to_doc()), 1037 Opt::Utf16 { .. } => others.push(override_type.unwrap_or("utf16").to_doc()), 1038 Opt::Utf32 { .. } => others.push(override_type.unwrap_or("utf32").to_doc()), 1039 Opt::Int { .. } => others.push("integer".to_doc()), 1040 Opt::Float { .. } => others.push("float".to_doc()), 1041 Opt::Bytes { .. } => others.push("binary".to_doc()), 1042 Opt::Bits { .. } => others.push("bitstring".to_doc()), 1043 Opt::Utf8Codepoint { .. } => others.push("utf8".to_doc()), 1044 Opt::Utf16Codepoint { .. } => others.push("utf16".to_doc()), 1045 Opt::Utf32Codepoint { .. } => others.push("utf32".to_doc()), 1046 Opt::Signed { .. } => others.push("signed".to_doc()), 1047 Opt::Unsigned { .. } => others.push("unsigned".to_doc()), 1048 Opt::Big { .. } => others.push("big".to_doc()), 1049 Opt::Little { .. } => others.push("little".to_doc()), 1050 Opt::Native { .. } => others.push("native".to_doc()), 1051 Opt::Size { value, .. } => size = size_to_doc(value, state), 1052 Opt::Unit { value, .. } => unit = unit_to_doc(value), 1053 } 1054 } 1055 1056 let mut document = create_document(state); 1057 1058 document = document.append(size); 1059 let others_is_empty = others.is_empty(); 1060 1061 if !others_is_empty { 1062 document = document.append("/").append(others); 1063 } 1064 1065 if unit.is_some() { 1066 if !others_is_empty { 1067 document = document.append("-").append(unit) 1068 } else { 1069 document = document.append("/").append(unit) 1070 } 1071 } 1072 1073 document 1074} 1075 1076fn block<'a>(statements: &'a Vec1<TypedStatement>, env: &mut Env<'a>) -> Document<'a> { 1077 if statements.len() == 1 1078 && let Statement::Expression(expression) = statements.first() 1079 && !needs_begin_end_wrapping(expression) 1080 { 1081 return docvec!['(', expr(expression, env), ')']; 1082 } 1083 1084 let vars = env.current_scope_vars.clone(); 1085 let document = statement_sequence(statements, env); 1086 env.current_scope_vars = vars; 1087 1088 begin_end(document) 1089} 1090 1091fn statement_sequence<'a>(statements: &'a [TypedStatement], env: &mut Env<'a>) -> Document<'a> { 1092 let count = statements.len(); 1093 let mut documents = Vec::with_capacity(count * 3); 1094 for (i, expression) in statements.iter().enumerate() { 1095 let position = if i + 1 == count { 1096 Position::Tail 1097 } else { 1098 Position::NotTail 1099 }; 1100 documents.push(statement(expression, env, position).group()); 1101 1102 if i + 1 < count { 1103 // This isn't the final expression so add the delimeters 1104 documents.push(",".to_doc()); 1105 documents.push(line()); 1106 } 1107 } 1108 if count == 1 { 1109 documents.to_doc() 1110 } else { 1111 documents.to_doc().force_break() 1112 } 1113} 1114 1115fn float_div<'a>(left: &'a TypedExpr, right: &'a TypedExpr, env: &mut Env<'a>) -> Document<'a> { 1116 if right.is_non_zero_compile_time_number() { 1117 return binop_exprs(left, "/", right, env); 1118 } else if right.is_zero_compile_time_number() { 1119 return "+0.0".to_doc(); 1120 } 1121 1122 let left = expr(left, env); 1123 let right = expr(right, env); 1124 let denominator = env.next_local_var_name("gleam@denominator"); 1125 let clauses = docvec![ 1126 line(), 1127 "+0.0 -> +0.0;", 1128 line(), 1129 "-0.0 -> -0.0;", 1130 line(), 1131 denominator.clone(), 1132 " -> ", 1133 binop_documents(left, "/", denominator) 1134 ]; 1135 docvec!["case ", right, " of", clauses.nest(INDENT), line(), "end"] 1136} 1137 1138fn int_div<'a>( 1139 left: &'a TypedExpr, 1140 right: &'a TypedExpr, 1141 op: &'static str, 1142 env: &mut Env<'a>, 1143) -> Document<'a> { 1144 if right.is_non_zero_compile_time_number() { 1145 return binop_exprs(left, op, right, env); 1146 } 1147 1148 // If we have a constant value divided by zero then it's safe to replace it 1149 // directly with 0. 1150 if left.is_literal() && right.is_zero_compile_time_number() { 1151 return "0".to_doc(); 1152 } 1153 1154 let left = expr(left, env); 1155 let right = expr(right, env); 1156 let denominator = env.next_local_var_name("gleam@denominator"); 1157 let clauses = docvec![ 1158 line(), 1159 "0 -> 0;", 1160 line(), 1161 denominator.clone(), 1162 " -> ", 1163 binop_documents(left, op, denominator) 1164 ]; 1165 docvec!["case ", right, " of", clauses.nest(INDENT), line(), "end"] 1166} 1167 1168fn bin_op<'a>( 1169 name: &'a BinOp, 1170 left: &'a TypedExpr, 1171 right: &'a TypedExpr, 1172 env: &mut Env<'a>, 1173) -> Document<'a> { 1174 let op = match name { 1175 BinOp::And => "andalso", 1176 BinOp::Or => "orelse", 1177 BinOp::LtInt | BinOp::LtFloat => "<", 1178 BinOp::LtEqInt | BinOp::LtEqFloat => "=<", 1179 BinOp::Eq => "=:=", 1180 BinOp::NotEq => "/=", 1181 BinOp::GtInt | BinOp::GtFloat => ">", 1182 BinOp::GtEqInt | BinOp::GtEqFloat => ">=", 1183 BinOp::AddInt => "+", 1184 BinOp::AddFloat => "+", 1185 BinOp::SubInt => "-", 1186 BinOp::SubFloat => "-", 1187 BinOp::MultInt => "*", 1188 BinOp::MultFloat => "*", 1189 BinOp::DivFloat => return float_div(left, right, env), 1190 BinOp::DivInt => return int_div(left, right, "div", env), 1191 BinOp::RemainderInt => return int_div(left, right, "rem", env), 1192 BinOp::Concatenate => return string_concatenate(left, right, env), 1193 }; 1194 1195 binop_exprs(left, op, right, env) 1196} 1197 1198fn binop_exprs<'a>( 1199 left: &'a TypedExpr, 1200 op: &'static str, 1201 right: &'a TypedExpr, 1202 env: &mut Env<'a>, 1203) -> Document<'a> { 1204 let left = if let TypedExpr::BinOp { .. } = left { 1205 expr(left, env).surround("(", ")") 1206 } else { 1207 maybe_block_expr(left, env) 1208 }; 1209 let right = if let TypedExpr::BinOp { .. } = right { 1210 expr(right, env).surround("(", ")") 1211 } else { 1212 maybe_block_expr(right, env) 1213 }; 1214 binop_documents(left, op, right) 1215} 1216 1217fn binop_documents<'a>(left: Document<'a>, op: &'static str, right: Document<'a>) -> Document<'a> { 1218 left.append(break_("", " ")) 1219 .append(op) 1220 .group() 1221 .append(" ") 1222 .append(right) 1223} 1224 1225fn let_assert<'a>( 1226 value: &'a TypedExpr, 1227 pattern: &'a TypedPattern, 1228 environment: &mut Env<'a>, 1229 message: Option<&'a TypedExpr>, 1230 position: Position, 1231 location: SrcSpan, 1232) -> Document<'a> { 1233 // If the pattern will never fail, like a tuple or a simple variable, we 1234 // simply treat it as if it were a `let` assignment. 1235 if pattern.always_matches() { 1236 return let_(value, pattern, environment); 1237 } 1238 1239 let message = match message { 1240 Some(message) => expr(message, environment), 1241 None => string("Pattern match failed, no pattern matched the value."), 1242 }; 1243 1244 let subject = maybe_block_expr(value, environment); 1245 1246 // The code we generated for a `let assert` assignment looks something like 1247 // this. For this Gleam code: 1248 // 1249 // ```gleam 1250 // let assert [a, b, c] = [1, 2, 3] 1251 // ``` 1252 // 1253 // We generate (roughly) the following Erlang: 1254 // 1255 // ```erlang 1256 // {A, B, C} = case [1, 2, 3] of 1257 // [A, B, C] -> {A, B, C}; 1258 // _ -> erlang:error(...) 1259 // end. 1260 // ``` 1261 // This is the most efficient way to properly extract all the required 1262 // variables from the pattern. However, if the `let assert` assignment is 1263 // the last in a block, like this: 1264 // 1265 // ```gleam 1266 // let x = { 1267 // let assert [a, b, c] = [1, 2, 3] 1268 // } 1269 // ``` 1270 // 1271 // The generated Erlang code will end up assigning the value `#(1, 2, 3)` 1272 // to the variable `x`, instead of `[1, 2, 3]`. In this case, we must 1273 // generate slightly different code. Since we know we won't be using the 1274 // bound variables anywhere (there is nothing else in this scope to 1275 // reference them), we can safely remove the assignment from the generated 1276 // code, and generate the following: 1277 // 1278 // ```erlang 1279 // X = begin 1280 // _assert_subject = [1, 2, 3] 1281 // case _assert_subject of 1282 // [A, B, C] -> _assert_subject; 1283 // _ -> erlang:error(...) 1284 // end 1285 // end. 1286 // ``` 1287 // 1288 // That correctly assigns `[1, 2, 3]` to the `x` variable. 1289 // 1290 let is_tail = match position { 1291 Position::Tail => true, 1292 Position::NotTail => false, 1293 }; 1294 1295 let (subject_assignment, subject) = if is_tail && !value.is_var() { 1296 let variable = environment.next_local_var_name(ASSERT_SUBJECT_VARIABLE); 1297 let assignment = docvec![variable.clone(), " = ", subject, ",", line()]; 1298 (assignment, variable) 1299 } else { 1300 (nil(), subject) 1301 }; 1302 1303 let mut pattern_printer = PatternPrinter::new(environment); 1304 let pattern_document = pattern_printer.print(pattern); 1305 let PatternPrinter { 1306 environment, 1307 variables, 1308 guards, 1309 assignments, 1310 } = pattern_printer; 1311 1312 let assignments_map = assignments 1313 .iter() 1314 .map(|assignment| (assignment.gleam_name.clone(), assignment)) 1315 .collect(); 1316 let clause_guard = optional_clause_guard(None, guards, environment, &assignments_map); 1317 1318 let value_document = match variables.as_slice() { 1319 _ if is_tail => subject.clone(), 1320 [] => "nil".to_doc(), 1321 [variable] => environment.local_var_name(variable), 1322 variables => { 1323 let variables = variables 1324 .iter() 1325 .map(|variable| environment.local_var_name(variable)); 1326 docvec![ 1327 break_("{", "{"), 1328 join(variables, break_(",", ", ")).nest(INDENT), 1329 "}" 1330 ] 1331 .group() 1332 } 1333 }; 1334 1335 let assignment = match variables.as_slice() { 1336 _ if is_tail => nil(), 1337 [] => nil(), 1338 [variable] => environment.next_local_var_name(variable).append(" = "), 1339 variables => { 1340 let variables = variables 1341 .iter() 1342 .map(|variable| environment.next_local_var_name(variable)); 1343 docvec![ 1344 break_("{", "{"), 1345 join(variables, break_(",", ", ")).nest(INDENT), 1346 "} = " 1347 ] 1348 .group() 1349 } 1350 }; 1351 1352 let clauses = docvec![ 1353 pattern_document, 1354 clause_guard, 1355 " -> ", 1356 value_document, 1357 ";", 1358 line(), 1359 environment.next_local_var_name(ASSERT_FAIL_VARIABLE), 1360 " ->", 1361 docvec![ 1362 line(), 1363 erlang_error( 1364 "let_assert", 1365 &message, 1366 location, 1367 vec![ 1368 ("value", environment.local_var_name(ASSERT_FAIL_VARIABLE)), 1369 ("start", location.start.to_doc()), 1370 ("'end'", value.location().end.to_doc()), 1371 ("pattern_start", pattern.location().start.to_doc()), 1372 ("pattern_end", pattern.location().end.to_doc()), 1373 ], 1374 environment, 1375 ) 1376 .nest(INDENT) 1377 ] 1378 .nest(INDENT) 1379 ]; 1380 1381 let assignments = if assignments.is_empty() { 1382 nil() 1383 } else { 1384 docvec![ 1385 ",", 1386 line(), 1387 join( 1388 assignments 1389 .iter() 1390 .map(|assignment| assignment.to_assignment_doc()), 1391 ",".to_doc().append(line()) 1392 ) 1393 ] 1394 }; 1395 1396 docvec![ 1397 subject_assignment, 1398 assignment, 1399 "case ", 1400 subject, 1401 " of", 1402 docvec![line(), clauses].nest(INDENT), 1403 line(), 1404 "end", 1405 assignments, 1406 ] 1407} 1408 1409fn let_<'a>( 1410 value: &'a TypedExpr, 1411 pattern: &'a TypedPattern, 1412 environment: &mut Env<'a>, 1413) -> Document<'a> { 1414 let body = maybe_block_expr(value, environment).group(); 1415 PatternPrinter::new(environment) 1416 .print(pattern) 1417 .append(" = ") 1418 .append(body) 1419} 1420 1421fn float<'a>(value: &str) -> Document<'a> { 1422 let mut value = value.replace('_', ""); 1423 if value.ends_with('.') { 1424 value.push('0') 1425 } 1426 1427 match value.split('.').collect_vec().as_slice() { 1428 ["0", "0"] => "+0.0".to_doc(), 1429 [before_dot, after_dot] if after_dot.starts_with('e') => { 1430 eco_format!("{before_dot}.0{after_dot}").to_doc() 1431 } 1432 _ => EcoString::from(value).to_doc(), 1433 } 1434} 1435 1436fn expr_list<'a>( 1437 elements: &'a [TypedExpr], 1438 tail: &'a Option<Box<TypedExpr>>, 1439 env: &mut Env<'a>, 1440) -> Document<'a> { 1441 let elements = join( 1442 elements 1443 .iter() 1444 .map(|element| maybe_block_expr(element, env)), 1445 break_(",", ", "), 1446 ); 1447 list( 1448 elements, 1449 tail.as_ref().map(|element| maybe_block_expr(element, env)), 1450 ) 1451} 1452 1453fn list<'a>(elements: Document<'a>, tail: Option<Document<'a>>) -> Document<'a> { 1454 let elements = match tail { 1455 Some(tail) if elements.is_empty() => return tail.to_doc(), 1456 1457 Some(tail) => elements.append(break_(" |", " | ")).append(tail), 1458 1459 None => elements, 1460 }; 1461 1462 elements.to_doc().nest(INDENT).surround("[", "]").group() 1463} 1464 1465fn var<'a>(name: &'a str, constructor: &'a ValueConstructor, env: &mut Env<'a>) -> Document<'a> { 1466 match &constructor.variant { 1467 ValueConstructorVariant::Record { 1468 name: record_name, .. 1469 } => match constructor.type_.deref() { 1470 Type::Fn { arguments, .. } => { 1471 let chars = incrementing_arguments_list(arguments.len()); 1472 "fun(" 1473 .to_doc() 1474 .append(chars.clone()) 1475 .append(") -> {") 1476 .append(atom_string(to_snake_case(record_name))) 1477 .append(", ") 1478 .append(chars) 1479 .append("} end") 1480 } 1481 Type::Named { .. } | Type::Var { .. } | Type::Tuple { .. } => { 1482 atom_string(to_snake_case(record_name)) 1483 } 1484 }, 1485 1486 ValueConstructorVariant::LocalVariable { .. } => env.local_var_name(name), 1487 1488 ValueConstructorVariant::ModuleConstant { literal, .. } => const_inline(literal, env), 1489 1490 ValueConstructorVariant::ModuleFn { 1491 arity, 1492 external_erlang: Some((module, name)), 1493 .. 1494 } if module == env.module => function_reference(None, name, *arity), 1495 1496 ValueConstructorVariant::ModuleFn { 1497 arity, 1498 external_erlang: Some((module, name)), 1499 .. 1500 } => function_reference(Some(module), name, *arity), 1501 1502 ValueConstructorVariant::ModuleFn { arity, module, .. } if module == env.module => { 1503 function_reference(None, name, *arity) 1504 } 1505 1506 ValueConstructorVariant::ModuleFn { 1507 arity, 1508 module, 1509 name, 1510 .. 1511 } => function_reference(Some(module), name, *arity), 1512 } 1513} 1514 1515fn function_reference<'a>(module: Option<&'a str>, name: &'a str, arity: usize) -> Document<'a> { 1516 match module { 1517 None => "fun ".to_doc(), 1518 Some(module) => "fun ".to_doc().append(module_name_atom(module)).append(":"), 1519 } 1520 .append(atom(escape_erlang_existing_name(name))) 1521 .append("/") 1522 .append(arity) 1523} 1524 1525fn int<'a>(value: &str) -> Document<'a> { 1526 let mut value = value.replace('_', ""); 1527 if value.starts_with("0x") { 1528 value.replace_range(..2, "16#"); 1529 } else if value.starts_with("0o") { 1530 value.replace_range(..2, "8#"); 1531 } else if value.starts_with("0b") { 1532 value.replace_range(..2, "2#"); 1533 } 1534 1535 EcoString::from(value).to_doc() 1536} 1537 1538fn const_inline<'a>(literal: &'a TypedConstant, env: &mut Env<'a>) -> Document<'a> { 1539 match literal { 1540 Constant::Int { value, .. } => int(value), 1541 Constant::Float { value, .. } => float(value), 1542 Constant::String { value, .. } => string(value), 1543 Constant::Tuple { elements, .. } => { 1544 tuple(elements.iter().map(|element| const_inline(element, env))) 1545 } 1546 1547 Constant::List { elements, tail, .. } => { 1548 let tail_elements = tail 1549 .as_deref() 1550 .and_then(|tail| tail.list_elements()) 1551 .unwrap_or_default(); 1552 1553 join( 1554 elements 1555 .iter() 1556 .chain(tail_elements.into_iter()) 1557 .map(|element| const_inline(element, env)), 1558 break_(",", ", "), 1559 ) 1560 .nest(INDENT) 1561 .surround("[", "]") 1562 .group() 1563 } 1564 1565 Constant::BitArray { segments, .. } => bit_array( 1566 segments 1567 .iter() 1568 .map(|s| const_segment(&s.value, &s.options, env)), 1569 ), 1570 1571 Constant::Record { 1572 tag, 1573 type_, 1574 arguments, 1575 .. 1576 } if arguments.is_empty() => match type_.deref() { 1577 Type::Fn { arguments, .. } => record_constructor_function(tag, arguments.len()), 1578 Type::Named { .. } | Type::Var { .. } | Type::Tuple { .. } => { 1579 atom_string(to_snake_case(tag)) 1580 } 1581 }, 1582 1583 Constant::Record { tag, arguments, .. } => { 1584 // Record updates are fully expanded during type checking, so we just handle arguments 1585 let arguments_doc = arguments 1586 .iter() 1587 .map(|argument| const_inline(&argument.value, env)); 1588 let tag = atom_string(to_snake_case(tag)); 1589 tuple(std::iter::once(tag).chain(arguments_doc)) 1590 } 1591 1592 Constant::Var { 1593 name, constructor, .. 1594 } => var( 1595 name, 1596 constructor 1597 .as_ref() 1598 .expect("This is guaranteed to hold a value."), 1599 env, 1600 ), 1601 1602 Constant::StringConcatenation { left, right, .. } => { 1603 const_string_concatenate(left, right, env) 1604 } 1605 1606 Constant::RecordUpdate { .. } => panic!("record updates should not reach code generation"), 1607 Constant::Invalid { .. } => panic!("invalid constants should not reach code generation"), 1608 } 1609} 1610 1611fn record_constructor_function(tag: &EcoString, arity: usize) -> Document<'_> { 1612 let chars = incrementing_arguments_list(arity); 1613 "fun(" 1614 .to_doc() 1615 .append(chars.clone()) 1616 .append(") -> {") 1617 .append(atom_string(to_snake_case(tag))) 1618 .append(", ") 1619 .append(chars) 1620 .append("} end") 1621} 1622 1623fn clause<'a>(clause: &'a TypedClause, environment: &mut Env<'a>) -> Document<'a> { 1624 let Clause { 1625 guard, 1626 pattern, 1627 alternative_patterns, 1628 then, 1629 .. 1630 } = clause; 1631 1632 // These are required to get the alternative patterns working properly. 1633 // Simply rendering the duplicate erlang clauses breaks the variable 1634 // rewriting because each pattern would define different (rewritten) 1635 // variables names. 1636 let initial_erlang_vars = environment.erl_function_scope_vars.clone(); 1637 let initial_scope_vars = environment.current_scope_vars.clone(); 1638 1639 let mut branches_docs = Vec::with_capacity(alternative_patterns.len() + 1); 1640 for patterns in std::iter::once(pattern).chain(alternative_patterns) { 1641 // Erlang doesn't support alternative patterns, so we turn each 1642 // alternative into a branch of its own. 1643 // For each alternative, before generating the body, we need to reset 1644 // the variables in scope to what they are before the case expression, 1645 // so that a branch will not interfere with the other ones! 1646 environment.erl_function_scope_vars = initial_erlang_vars.clone(); 1647 environment.current_scope_vars = initial_scope_vars.clone(); 1648 let mut pattern_printer = PatternPrinter::new(environment); 1649 1650 let pattern = match patterns.as_slice() { 1651 [pattern] => pattern_printer.print(pattern), 1652 _ => tuple(patterns.iter().map(|pattern| { 1653 pattern_printer.reset_variables(); 1654 pattern_printer.print(pattern) 1655 })), 1656 }; 1657 1658 let PatternPrinter { 1659 environment, 1660 guards, 1661 variables: _, 1662 assignments, 1663 } = pattern_printer; 1664 1665 let assignments_map = assignments 1666 .iter() 1667 .map(|assignment| (assignment.gleam_name.clone(), assignment)) 1668 .collect(); 1669 1670 let guard = optional_clause_guard(guard.as_ref(), guards, environment, &assignments_map); 1671 let then = clause_consequence(then, assignments, environment).group(); 1672 branches_docs.push(docvec![ 1673 pattern, 1674 guard, 1675 " ->", 1676 docvec![line(), then].nest(INDENT), 1677 ]); 1678 } 1679 1680 join(branches_docs, ";".to_doc().append(lines(2))) 1681} 1682 1683fn clause_consequence<'a>( 1684 consequence: &'a TypedExpr, 1685 // Further assignments that the pattern might need to introduce at the start 1686 // of the new block. 1687 assignments: Vec<StringPatternAssignment<'a>>, 1688 env: &mut Env<'a>, 1689) -> Document<'a> { 1690 let assignment_doc = if assignments.is_empty() { 1691 nil() 1692 } else { 1693 let separator = ",".to_doc().append(line()); 1694 join( 1695 assignments 1696 .iter() 1697 .map(|assignment| assignment.to_assignment_doc()), 1698 separator.clone(), 1699 ) 1700 .append(separator) 1701 }; 1702 1703 let consequence = if let TypedExpr::Block { statements, .. } = consequence { 1704 statement_sequence(statements, env) 1705 } else { 1706 expr(consequence, env) 1707 }; 1708 assignment_doc.append(consequence) 1709} 1710 1711fn optional_clause_guard<'a>( 1712 guard: Option<&'a TypedClauseGuard>, 1713 additional_guards: Vec<Document<'a>>, 1714 env: &mut Env<'a>, 1715 assignments: &HashMap<EcoString, &StringPatternAssignment<'a>>, 1716) -> Document<'a> { 1717 let guard_doc = guard.map(|guard| bare_clause_guard(guard, env, assignments)); 1718 1719 let guards_count = guard_doc.iter().len() + additional_guards.len(); 1720 let guards_docs = additional_guards.into_iter().chain(guard_doc).map(|guard| { 1721 if guards_count > 1 { 1722 guard.surround("(", ")") 1723 } else { 1724 guard 1725 } 1726 }); 1727 let doc = join(guards_docs, " andalso ".to_doc()); 1728 if doc.is_empty() { 1729 doc 1730 } else { 1731 " when ".to_doc().append(doc) 1732 } 1733} 1734 1735fn bare_clause_guard<'a>( 1736 guard: &'a TypedClauseGuard, 1737 env: &mut Env<'a>, 1738 assignments: &HashMap<EcoString, &StringPatternAssignment<'a>>, 1739) -> Document<'a> { 1740 match guard { 1741 ClauseGuard::Block { value, .. } => { 1742 bare_clause_guard(value, env, assignments).surround("(", ")") 1743 } 1744 1745 ClauseGuard::Not { expression, .. } => { 1746 docvec!["not ", bare_clause_guard(expression, env, assignments)] 1747 } 1748 1749 ClauseGuard::BinaryOperator { 1750 operator, 1751 left, 1752 right, 1753 .. 1754 } => { 1755 let left_document = clause_guard(left, env, assignments); 1756 let right_document = clause_guard(right, env, assignments); 1757 1758 let operator = match operator { 1759 BinOp::Or => "orelse", 1760 BinOp::And => "andalso", 1761 BinOp::Eq => "=:=", 1762 BinOp::NotEq => "=/=", 1763 BinOp::GtInt | BinOp::GtFloat => ">", 1764 BinOp::GtEqInt | BinOp::GtEqFloat => ">=", 1765 BinOp::LtInt | BinOp::LtFloat => "<", 1766 BinOp::LtEqInt | BinOp::LtEqFloat => "=<", 1767 BinOp::AddInt | BinOp::AddFloat => "+", 1768 BinOp::SubInt | BinOp::SubFloat => "-", 1769 BinOp::MultInt | BinOp::MultFloat => "*", 1770 BinOp::DivFloat => "/", 1771 BinOp::DivInt => "div", 1772 BinOp::RemainderInt => "rem", 1773 BinOp::Concatenate => { 1774 return clause_guard_string_concatenate(left, right, env, assignments); 1775 } 1776 }; 1777 1778 docvec![left_document, " ", operator, " ", right_document] 1779 } 1780 1781 // Only local variables are supported and the typer ensures that all 1782 // ClauseGuard::Vars are local variables 1783 ClauseGuard::Var { name, .. } => { 1784 // If we're referencing a variable introduced by a string pattern 1785 // assignment we need to replace it with its actual literal value: 1786 // in the generated code the variable is only defined later, so 1787 // just referencing its name would result in an error. 1788 assignments 1789 .get(name) 1790 .map(|assignment| assignment.literal_value.clone()) 1791 .unwrap_or_else(|| env.local_var_name(name)) 1792 } 1793 1794 ClauseGuard::TupleIndex { tuple, index, .. } => tuple_index_inline(tuple, *index, env), 1795 1796 ClauseGuard::FieldAccess { 1797 container, index, .. 1798 } => tuple_index_inline(container, index.expect("Unable to find index") + 1, env), 1799 1800 ClauseGuard::ModuleSelect { literal, .. } => const_inline(literal, env), 1801 1802 ClauseGuard::Constant(constant) => const_inline(constant, env), 1803 } 1804} 1805 1806fn clause_guard_string_concatenate<'a>( 1807 left: &'a TypedClauseGuard, 1808 right: &'a TypedClauseGuard, 1809 env: &mut Env<'a>, 1810 assignments: &HashMap<EcoString, &StringPatternAssignment<'a>>, 1811) -> Document<'a> { 1812 let left = clause_guard_string_concatenate_argument(left, env, assignments); 1813 let right = clause_guard_string_concatenate_argument(right, env, assignments); 1814 bit_array([left, right]) 1815} 1816 1817fn clause_guard_string_concatenate_argument<'a>( 1818 guard: &'a TypedClauseGuard, 1819 env: &mut Env<'a>, 1820 assignments: &HashMap<EcoString, &StringPatternAssignment<'a>>, 1821) -> Document<'a> { 1822 match guard { 1823 ClauseGuard::Constant(Constant::String { value, .. }) => { 1824 docvec!['"', string_inner(value), "\"/utf8"] 1825 } 1826 1827 ClauseGuard::Constant(Constant::StringConcatenation { left, right, .. }) => { 1828 const_string_concatenate_inner(left, right, env) 1829 } 1830 1831 ClauseGuard::ModuleSelect { literal, .. } => match literal { 1832 Constant::String { value, .. } => docvec!['"', string_inner(value), "\"/utf8"], 1833 Constant::StringConcatenation { left, right, .. } => { 1834 const_string_concatenate_inner(left, right, env) 1835 } 1836 Constant::Int { .. } 1837 | Constant::Float { .. } 1838 | Constant::Tuple { .. } 1839 | Constant::List { .. } 1840 | Constant::Record { .. } 1841 | Constant::RecordUpdate { .. } 1842 | Constant::BitArray { .. } 1843 | Constant::Var { .. } 1844 | Constant::Invalid { .. } => docvec!["(", const_inline(literal, env), ")/binary"], 1845 }, 1846 1847 ClauseGuard::Var { name, .. } => assignments 1848 .get(name) 1849 .map(|assignment| docvec![assignment.literal_value.clone(), "/binary"]) 1850 .unwrap_or_else(|| docvec![env.local_var_name(name), "/binary"]), 1851 1852 ClauseGuard::BinaryOperator { 1853 operator: BinOp::Concatenate, 1854 left, 1855 right, 1856 .. 1857 } => docvec![ 1858 clause_guard_string_concatenate(left, right, env, assignments), 1859 "/binary" 1860 ], 1861 1862 ClauseGuard::Block { .. } 1863 | ClauseGuard::BinaryOperator { .. } 1864 | ClauseGuard::Not { .. } 1865 | ClauseGuard::TupleIndex { .. } 1866 | ClauseGuard::FieldAccess { .. } 1867 | ClauseGuard::Constant(_) => docvec![ 1868 clause_guard(guard, env, assignments).surround("(", ")"), 1869 "/binary" 1870 ], 1871 } 1872} 1873 1874fn tuple_index_inline<'a>( 1875 tuple: &'a TypedClauseGuard, 1876 index: u64, 1877 env: &mut Env<'a>, 1878) -> Document<'a> { 1879 let index_doc = eco_format!("{}", (index + 1)).to_doc(); 1880 let tuple_doc = bare_clause_guard(tuple, env, &HashMap::new()); 1881 "erlang:element" 1882 .to_doc() 1883 .append(wrap_arguments([index_doc, tuple_doc])) 1884} 1885 1886fn clause_guard<'a>( 1887 guard: &'a TypedClauseGuard, 1888 env: &mut Env<'a>, 1889 assignments: &HashMap<EcoString, &StringPatternAssignment<'a>>, 1890) -> Document<'a> { 1891 match guard { 1892 // Binary operators are wrapped in parens 1893 ClauseGuard::BinaryOperator { .. } => "(" 1894 .to_doc() 1895 .append(bare_clause_guard(guard, env, assignments)) 1896 .append(")"), 1897 1898 // Other expressions are not 1899 ClauseGuard::Constant(_) 1900 | ClauseGuard::Not { .. } 1901 | ClauseGuard::Var { .. } 1902 | ClauseGuard::TupleIndex { .. } 1903 | ClauseGuard::FieldAccess { .. } 1904 | ClauseGuard::ModuleSelect { .. } 1905 | ClauseGuard::Block { .. } => bare_clause_guard(guard, env, assignments), 1906 } 1907} 1908 1909fn clauses<'a>(cs: &'a [TypedClause], env: &mut Env<'a>) -> Document<'a> { 1910 join( 1911 cs.iter().map(|c| { 1912 let vars = env.current_scope_vars.clone(); 1913 let erl = clause(c, env); 1914 env.current_scope_vars = vars; // Reset the known variables now the clauses' scope has ended 1915 erl 1916 }), 1917 ";".to_doc().append(lines(2)), 1918 ) 1919} 1920 1921fn case<'a>(subjects: &'a [TypedExpr], cs: &'a [TypedClause], env: &mut Env<'a>) -> Document<'a> { 1922 let subjects_doc = if subjects.len() == 1 { 1923 let subject = subjects 1924 .first() 1925 .expect("erl case printing of single subject"); 1926 maybe_block_expr(subject, env).group() 1927 } else { 1928 tuple( 1929 subjects 1930 .iter() 1931 .map(|element| maybe_block_expr(element, env)), 1932 ) 1933 }; 1934 "case " 1935 .to_doc() 1936 .append(subjects_doc) 1937 .append(" of") 1938 .append(line().append(clauses(cs, env)).nest(INDENT)) 1939 .append(line()) 1940 .append("end") 1941 .group() 1942} 1943 1944fn call<'a>(fun: &'a TypedExpr, arguments: &'a [TypedCallArg], env: &mut Env<'a>) -> Document<'a> { 1945 docs_arguments_call( 1946 fun, 1947 arguments 1948 .iter() 1949 .map(|argument| maybe_block_expr(&argument.value, env)) 1950 .collect(), 1951 env, 1952 ) 1953} 1954 1955fn module_fn_with_arguments<'a>( 1956 module: &'a str, 1957 name: &'a str, 1958 arguments: Vec<Document<'a>>, 1959 env: &Env<'a>, 1960) -> Document<'a> { 1961 let name = escape_erlang_existing_name(name); 1962 let arguments = wrap_arguments(arguments); 1963 if module == env.module { 1964 atom(name).append(arguments) 1965 } else { 1966 atom_string(module.replace('/', "@").into()) 1967 .append(":") 1968 .append(atom(name)) 1969 .append(arguments) 1970 } 1971} 1972 1973fn docs_arguments_call<'a>( 1974 fun: &'a TypedExpr, 1975 mut arguments: Vec<Document<'a>>, 1976 env: &mut Env<'a>, 1977) -> Document<'a> { 1978 match fun { 1979 TypedExpr::ModuleSelect { 1980 constructor: ModuleValueConstructor::Record { name, .. }, 1981 .. 1982 } 1983 | TypedExpr::Var { 1984 constructor: 1985 ValueConstructor { 1986 variant: ValueConstructorVariant::Record { name, .. }, 1987 .. 1988 }, 1989 .. 1990 } => tuple(std::iter::once(atom_string(to_snake_case(name))).chain(arguments)), 1991 1992 TypedExpr::Var { 1993 constructor: 1994 ValueConstructor { 1995 variant: 1996 ValueConstructorVariant::ModuleFn { 1997 external_erlang: Some((module, name)), 1998 .. 1999 } 2000 | ValueConstructorVariant::ModuleFn { module, name, .. }, 2001 .. 2002 }, 2003 .. 2004 } => module_fn_with_arguments(module, name, arguments, env), 2005 2006 // Match against a Constant::Var that contains a function. 2007 // We want this to be emitted like a normal function call, not a function variable 2008 // substitution. 2009 TypedExpr::Var { 2010 constructor: 2011 ValueConstructor { 2012 variant: 2013 ValueConstructorVariant::ModuleConstant { 2014 literal: 2015 Constant::Var { 2016 constructor: Some(constructor), 2017 .. 2018 }, 2019 .. 2020 }, 2021 .. 2022 }, 2023 .. 2024 } if constructor.variant.is_module_fn() => match &constructor.variant { 2025 ValueConstructorVariant::ModuleFn { 2026 external_erlang: Some((module, name)), 2027 .. 2028 } 2029 | ValueConstructorVariant::ModuleFn { module, name, .. } => { 2030 module_fn_with_arguments(module, name, arguments, env) 2031 } 2032 ValueConstructorVariant::LocalVariable { .. } 2033 | ValueConstructorVariant::ModuleConstant { .. } 2034 | ValueConstructorVariant::Record { .. } => { 2035 unreachable!("The above clause guard ensures that this is a module fn") 2036 } 2037 }, 2038 2039 TypedExpr::ModuleSelect { 2040 constructor: 2041 ModuleValueConstructor::Fn { 2042 external_erlang: Some((module, name)), 2043 .. 2044 } 2045 | ModuleValueConstructor::Fn { module, name, .. }, 2046 .. 2047 } => { 2048 let arguments = wrap_arguments(arguments); 2049 let name = escape_erlang_existing_name(name); 2050 // We use the constructor Fn variant's `module` and function `name`. 2051 // It would also be valid to use the module and label as in the 2052 // Gleam code, but using the variant can result in an optimisation 2053 // in which the target function is used for `external fn`s, removing 2054 // one layer of wrapping. 2055 // This also enables an optimisation in the Erlang compiler in which 2056 // some Erlang BIFs can be replaced with literals if their arguments 2057 // are literals, such as `binary_to_atom`. 2058 atom_string(module_erlang_name(module)) 2059 .append(":") 2060 .append(atom_string(name.into())) 2061 .append(arguments) 2062 } 2063 2064 TypedExpr::Fn { kind, body, .. } if kind.is_capture() => { 2065 if let Statement::Expression(TypedExpr::Call { 2066 fun, 2067 arguments: inner_arguments, 2068 .. 2069 }) = body.first() 2070 { 2071 let mut merged_arguments = Vec::with_capacity(inner_arguments.len()); 2072 for arg in inner_arguments { 2073 if let TypedExpr::Var { name, .. } = &arg.value 2074 && name == CAPTURE_VARIABLE 2075 { 2076 merged_arguments.push(arguments.swap_remove(0)) 2077 } else { 2078 merged_arguments.push(maybe_block_expr(&arg.value, env)) 2079 } 2080 } 2081 docs_arguments_call(fun, merged_arguments, env) 2082 } else { 2083 panic!("Erl printing: Capture was not a call") 2084 } 2085 } 2086 2087 TypedExpr::Fn { .. } 2088 | TypedExpr::Call { .. } 2089 | TypedExpr::Todo { .. } 2090 | TypedExpr::Panic { .. } 2091 | TypedExpr::RecordAccess { .. } 2092 | TypedExpr::TupleIndex { .. } => { 2093 let arguments = wrap_arguments(arguments); 2094 expr(fun, env).surround("(", ")").append(arguments) 2095 } 2096 2097 TypedExpr::Int { .. } 2098 | TypedExpr::Float { .. } 2099 | TypedExpr::String { .. } 2100 | TypedExpr::Block { .. } 2101 | TypedExpr::Pipeline { .. } 2102 | TypedExpr::Var { .. } 2103 | TypedExpr::List { .. } 2104 | TypedExpr::BinOp { .. } 2105 | TypedExpr::Case { .. } 2106 | TypedExpr::PositionalAccess { .. } 2107 | TypedExpr::ModuleSelect { .. } 2108 | TypedExpr::Tuple { .. } 2109 | TypedExpr::Echo { .. } 2110 | TypedExpr::BitArray { .. } 2111 | TypedExpr::RecordUpdate { .. } 2112 | TypedExpr::NegateBool { .. } 2113 | TypedExpr::NegateInt { .. } 2114 | TypedExpr::Invalid { .. } => { 2115 let arguments = wrap_arguments(arguments); 2116 maybe_block_expr(fun, env).append(arguments) 2117 } 2118 } 2119} 2120 2121fn record_update<'a>( 2122 record: &'a Option<Box<TypedAssignment>>, 2123 constructor: &'a TypedExpr, 2124 arguments: &'a [TypedCallArg], 2125 env: &mut Env<'a>, 2126) -> Document<'a> { 2127 let vars = env.current_scope_vars.clone(); 2128 2129 let document = match record.as_ref() { 2130 Some(record) => docvec![ 2131 assignment(record, env, Position::NotTail), 2132 ",", 2133 line(), 2134 call(constructor, arguments, env) 2135 ], 2136 None => call(constructor, arguments, env), 2137 }; 2138 2139 env.current_scope_vars = vars; 2140 2141 document 2142} 2143 2144/// Wrap a document in begin end 2145/// 2146fn begin_end(document: Document<'_>) -> Document<'_> { 2147 docvec!["begin", line().append(document).nest(INDENT), line(), "end"].force_break() 2148} 2149 2150fn maybe_block_expr<'a>(expression: &'a TypedExpr, env: &mut Env<'a>) -> Document<'a> { 2151 if needs_begin_end_wrapping(expression) { 2152 begin_end(expr(expression, env)) 2153 } else { 2154 expr(expression, env) 2155 } 2156} 2157 2158fn needs_begin_end_wrapping(expression: &TypedExpr) -> bool { 2159 match expression { 2160 // Record updates are 1 expression if there's no assignment, multiple otherwise. 2161 TypedExpr::RecordUpdate { 2162 record_assignment, .. 2163 } => record_assignment.is_some(), 2164 2165 TypedExpr::Pipeline { .. } => true, 2166 2167 TypedExpr::Int { .. } 2168 | TypedExpr::Float { .. } 2169 | TypedExpr::String { .. } 2170 | TypedExpr::Var { .. } 2171 | TypedExpr::Fn { .. } 2172 | TypedExpr::List { .. } 2173 | TypedExpr::Call { .. } 2174 | TypedExpr::BinOp { .. } 2175 | TypedExpr::Case { .. } 2176 | TypedExpr::RecordAccess { .. } 2177 | TypedExpr::PositionalAccess { .. } 2178 | TypedExpr::Block { .. } 2179 | TypedExpr::ModuleSelect { .. } 2180 | TypedExpr::Tuple { .. } 2181 | TypedExpr::TupleIndex { .. } 2182 | TypedExpr::Todo { .. } 2183 | TypedExpr::Echo { .. } 2184 | TypedExpr::Panic { .. } 2185 | TypedExpr::BitArray { .. } 2186 | TypedExpr::NegateBool { .. } 2187 | TypedExpr::NegateInt { .. } 2188 | TypedExpr::Invalid { .. } => false, 2189 } 2190} 2191 2192fn todo<'a>(message: Option<&'a TypedExpr>, location: SrcSpan, env: &mut Env<'a>) -> Document<'a> { 2193 let message = match message { 2194 Some(m) => expr(m, env), 2195 None => string("`todo` expression evaluated. This code has not yet been implemented."), 2196 }; 2197 erlang_error("todo", &message, location, vec![], env) 2198} 2199 2200fn panic<'a>(location: SrcSpan, message: Option<&'a TypedExpr>, env: &mut Env<'a>) -> Document<'a> { 2201 let message = match message { 2202 Some(m) => expr(m, env), 2203 None => string("`panic` expression evaluated."), 2204 }; 2205 erlang_error("panic", &message, location, vec![], env) 2206} 2207 2208fn echo<'a>( 2209 body: Document<'a>, 2210 message: Option<&'a TypedExpr>, 2211 location: &SrcSpan, 2212 env: &mut Env<'a>, 2213) -> Document<'a> { 2214 env.echo_used = true; 2215 2216 let message = message 2217 .as_ref() 2218 .map(|message| maybe_block_expr(message, env)) 2219 .unwrap_or("nil".to_doc()); 2220 2221 "echo".to_doc().append(wrap_arguments(vec![ 2222 body, 2223 message, 2224 env.line_numbers.line_number(location.start).to_doc(), 2225 ])) 2226} 2227 2228fn erlang_error<'a>( 2229 name: &'a str, 2230 message: &Document<'a>, 2231 location: SrcSpan, 2232 fields: Vec<(&'a str, Document<'a>)>, 2233 env: &Env<'a>, 2234) -> Document<'a> { 2235 let mut fields_doc = docvec![ 2236 "gleam_error => ", 2237 name, 2238 ",", 2239 line(), 2240 "message => ", 2241 message.clone(), 2242 ",", 2243 line(), 2244 "file => <<?FILEPATH/utf8>>,", 2245 line(), 2246 "module => ", 2247 env.module.to_doc().surround("<<\"", "\"/utf8>>"), 2248 ",", 2249 line(), 2250 "function => ", 2251 string(env.function), 2252 ",", 2253 line(), 2254 "line => ", 2255 env.line_numbers.line_number(location.start), 2256 ]; 2257 for (key, value) in fields { 2258 fields_doc = fields_doc 2259 .append(",") 2260 .append(line()) 2261 .append(key) 2262 .append(" => ") 2263 .append(value); 2264 } 2265 let error = docvec!["#{", fields_doc.group().nest(INDENT), "}"]; 2266 docvec!["erlang:error", wrap_arguments([error.group()])] 2267} 2268 2269fn expr<'a>(expression: &'a TypedExpr, env: &mut Env<'a>) -> Document<'a> { 2270 match expression { 2271 TypedExpr::Todo { 2272 message: label, 2273 location, 2274 .. 2275 } => todo(label.as_deref(), *location, env), 2276 2277 TypedExpr::Panic { 2278 location, message, .. 2279 } => panic(*location, message.as_deref(), env), 2280 2281 TypedExpr::Echo { 2282 expression, 2283 location, 2284 message, 2285 .. 2286 } => { 2287 let expression = expression 2288 .as_ref() 2289 .expect("echo with no expression outside of pipe"); 2290 let expression = maybe_block_expr(expression, env); 2291 echo(expression, message.as_deref(), location, env) 2292 } 2293 2294 TypedExpr::Int { value, .. } => int(value), 2295 TypedExpr::Float { value, .. } => float(value), 2296 TypedExpr::String { value, .. } => string(value), 2297 2298 TypedExpr::Pipeline { 2299 first_value, 2300 assignments, 2301 finally, 2302 .. 2303 } => pipeline(first_value, assignments, finally, env), 2304 2305 TypedExpr::Block { statements, .. } => block(statements, env), 2306 2307 TypedExpr::TupleIndex { tuple, index, .. } => tuple_index(tuple, *index, env), 2308 2309 TypedExpr::Var { 2310 name, constructor, .. 2311 } => var(name, constructor, env), 2312 2313 TypedExpr::Fn { 2314 arguments, body, .. 2315 } => fun(arguments, body, env), 2316 2317 TypedExpr::NegateBool { value, .. } => negate_with("not ", value, env), 2318 2319 TypedExpr::NegateInt { value, .. } => negate_with("- ", value, env), 2320 2321 TypedExpr::List { elements, tail, .. } => expr_list(elements, tail, env), 2322 2323 TypedExpr::Call { fun, arguments, .. } => call(fun, arguments, env), 2324 2325 TypedExpr::ModuleSelect { 2326 constructor: ModuleValueConstructor::Record { name, arity: 0, .. }, 2327 .. 2328 } => atom_string(to_snake_case(name)), 2329 2330 TypedExpr::ModuleSelect { 2331 constructor: ModuleValueConstructor::Constant { literal, .. }, 2332 .. 2333 } => const_inline(literal, env), 2334 2335 TypedExpr::ModuleSelect { 2336 constructor: ModuleValueConstructor::Record { name, arity, .. }, 2337 .. 2338 } => record_constructor_function(name, *arity as usize), 2339 2340 TypedExpr::ModuleSelect { 2341 type_, 2342 constructor: 2343 ModuleValueConstructor::Fn { 2344 external_erlang: Some((module, name)), 2345 .. 2346 } 2347 | ModuleValueConstructor::Fn { module, name, .. }, 2348 .. 2349 } => module_select_fn(type_.clone(), module, name), 2350 2351 TypedExpr::RecordAccess { record, index, .. } => tuple_index(record, index + 1, env), 2352 TypedExpr::PositionalAccess { record, index, .. } => tuple_index(record, index + 1, env), 2353 2354 TypedExpr::RecordUpdate { 2355 record_assignment, 2356 constructor, 2357 arguments, 2358 .. 2359 } => record_update(record_assignment, constructor, arguments, env), 2360 2361 TypedExpr::Case { 2362 subjects, clauses, .. 2363 } => case(subjects, clauses, env), 2364 2365 TypedExpr::BinOp { 2366 name, left, right, .. 2367 } => bin_op(name, left, right, env), 2368 2369 TypedExpr::Tuple { elements, .. } => tuple( 2370 elements 2371 .iter() 2372 .map(|element| maybe_block_expr(element, env)), 2373 ), 2374 2375 TypedExpr::BitArray { segments, .. } => bit_array( 2376 segments 2377 .iter() 2378 .map(|s| expr_segment(&s.value, &s.options, env)), 2379 ), 2380 2381 TypedExpr::Invalid { .. } => panic!("invalid expressions should not reach code generation"), 2382 } 2383} 2384 2385fn pipeline<'a>( 2386 first_value: &'a TypedPipelineAssignment, 2387 assignments: &'a [(TypedPipelineAssignment, PipelineAssignmentKind)], 2388 finally: &'a TypedExpr, 2389 env: &mut Env<'a>, 2390) -> Document<'a> { 2391 let mut documents = Vec::with_capacity((assignments.len() + 1) * 3); 2392 2393 let all_assignments = std::iter::once(first_value) 2394 .chain(assignments.iter().map(|(assignment, _kind)| assignment)); 2395 2396 let echo_doc = |var_name: &Option<Document<'a>>, 2397 message: Option<&'a TypedExpr>, 2398 location: &SrcSpan, 2399 env: &mut Env<'a>| { 2400 let name = var_name 2401 .to_owned() 2402 .expect("echo with no previous step in a pipe"); 2403 echo(name, message, location, env) 2404 }; 2405 2406 let vars = env.current_scope_vars.clone(); 2407 2408 let mut prev_local_var_name = None; 2409 for a in all_assignments { 2410 // An echo in a pipeline won't result in an assignment, instead it 2411 // just prints the previous variable assigned in the pipeline. 2412 if let TypedExpr::Echo { 2413 expression: None, 2414 message, 2415 location, 2416 .. 2417 } = a.value.as_ref() 2418 { 2419 documents.push(echo_doc( 2420 &prev_local_var_name, 2421 message.as_deref(), 2422 location, 2423 env, 2424 )) 2425 } else { 2426 // Otherwise we assign the intermediate pipe value to a variable. 2427 let body = maybe_block_expr(&a.value, env).group(); 2428 let name = env.next_local_var_name(&a.name); 2429 prev_local_var_name = Some(name.clone()); 2430 documents.push(docvec![name, " = ", body]); 2431 }; 2432 documents.push(",".to_doc()); 2433 documents.push(line()); 2434 } 2435 2436 if let TypedExpr::Echo { 2437 expression: None, 2438 message, 2439 location, 2440 .. 2441 } = finally 2442 { 2443 documents.push(echo_doc( 2444 &prev_local_var_name, 2445 message.as_deref(), 2446 location, 2447 env, 2448 )) 2449 } else { 2450 documents.push(expr(finally, env)) 2451 } 2452 2453 env.current_scope_vars = vars; 2454 2455 documents.to_doc() 2456} 2457 2458fn assignment<'a>( 2459 assignment: &'a TypedAssignment, 2460 env: &mut Env<'a>, 2461 position: Position, 2462) -> Document<'a> { 2463 match &assignment.kind { 2464 AssignmentKind::Let | AssignmentKind::Generated => { 2465 let_(&assignment.value, &assignment.pattern, env) 2466 } 2467 AssignmentKind::Assert { 2468 message, location, .. 2469 } => let_assert( 2470 &assignment.value, 2471 &assignment.pattern, 2472 env, 2473 message.as_ref(), 2474 position, 2475 *location, 2476 ), 2477 } 2478} 2479 2480fn assert<'a>(assert: &'a TypedAssert, env: &mut Env<'a>) -> Document<'a> { 2481 let Assert { 2482 value, 2483 location, 2484 message, 2485 } = assert; 2486 2487 let message = match message { 2488 Some(message) => expr(message, env), 2489 None => string("Assertion failed."), 2490 }; 2491 2492 let mut assignments = Vec::new(); 2493 2494 let (subject, mut fields) = match value { 2495 TypedExpr::Call { fun, arguments, .. } => { 2496 assert_call(fun, arguments, &mut assignments, env) 2497 } 2498 TypedExpr::BinOp { 2499 name, left, right, .. 2500 } => { 2501 let operator = match name { 2502 BinOp::And => { 2503 return assert_and(left, right, message, *location, env); 2504 } 2505 BinOp::Or => { 2506 return assert_or(left, right, message, *location, env); 2507 } 2508 BinOp::Eq => "=:=", 2509 BinOp::NotEq => "/=", 2510 BinOp::LtInt | BinOp::LtFloat => "<", 2511 BinOp::LtEqInt | BinOp::LtEqFloat => "=<", 2512 BinOp::GtInt | BinOp::GtFloat => ">", 2513 BinOp::GtEqInt | BinOp::GtEqFloat => ">=", 2514 BinOp::AddInt 2515 | BinOp::AddFloat 2516 | BinOp::SubInt 2517 | BinOp::SubFloat 2518 | BinOp::MultInt 2519 | BinOp::MultFloat 2520 | BinOp::DivInt 2521 | BinOp::DivFloat 2522 | BinOp::RemainderInt 2523 | BinOp::Concatenate => { 2524 panic!("Non-boolean operators cannot appear here in well-typed code") 2525 } 2526 }; 2527 2528 let left_document = assign_to_variable(left, &mut assignments, env); 2529 let right_document = assign_to_variable(right, &mut assignments, env); 2530 ( 2531 binop_documents(left_document.clone(), operator, right_document.clone()), 2532 vec![ 2533 ("kind", atom("binary_operator")), 2534 ("operator", atom(name.name())), 2535 ( 2536 "left", 2537 asserted_expression( 2538 AssertExpression::from_expression(left), 2539 Some(left_document), 2540 left.location(), 2541 ), 2542 ), 2543 ( 2544 "right", 2545 asserted_expression( 2546 AssertExpression::from_expression(right), 2547 Some(right_document), 2548 right.location(), 2549 ), 2550 ), 2551 ], 2552 ) 2553 } 2554 2555 TypedExpr::Int { .. } 2556 | TypedExpr::Float { .. } 2557 | TypedExpr::String { .. } 2558 | TypedExpr::Block { .. } 2559 | TypedExpr::Pipeline { .. } 2560 | TypedExpr::Var { .. } 2561 | TypedExpr::Fn { .. } 2562 | TypedExpr::List { .. } 2563 | TypedExpr::Case { .. } 2564 | TypedExpr::RecordAccess { .. } 2565 | TypedExpr::PositionalAccess { .. } 2566 | TypedExpr::ModuleSelect { .. } 2567 | TypedExpr::Tuple { .. } 2568 | TypedExpr::TupleIndex { .. } 2569 | TypedExpr::Todo { .. } 2570 | TypedExpr::Panic { .. } 2571 | TypedExpr::Echo { .. } 2572 | TypedExpr::BitArray { .. } 2573 | TypedExpr::RecordUpdate { .. } 2574 | TypedExpr::NegateBool { .. } 2575 | TypedExpr::NegateInt { .. } 2576 | TypedExpr::Invalid { .. } => ( 2577 maybe_block_expr(value, env), 2578 vec![ 2579 ("kind", atom("expression")), 2580 ( 2581 "expression", 2582 asserted_expression( 2583 AssertExpression::from_expression(value), 2584 Some("false".to_doc()), 2585 value.location(), 2586 ), 2587 ), 2588 ], 2589 ), 2590 }; 2591 2592 fields.push(("start", location.start.to_doc())); 2593 fields.push(("'end'", value.location().end.to_doc())); 2594 fields.push(("expression_start", value.location().start.to_doc())); 2595 2596 let clauses = docvec![ 2597 line(), 2598 "true -> nil;", 2599 line(), 2600 "false -> ", 2601 erlang_error("assert", &message, *location, fields, env), 2602 ]; 2603 2604 docvec![ 2605 assignments, 2606 "case ", 2607 subject, 2608 " of", 2609 clauses.nest(INDENT), 2610 line(), 2611 "end" 2612 ] 2613} 2614 2615fn assert_call<'a>( 2616 function: &'a TypedExpr, 2617 arguments: &'a Vec<CallArg<TypedExpr>>, 2618 assignments: &mut Vec<Document<'a>>, 2619 env: &mut Env<'a>, 2620) -> (Document<'a>, Vec<(&'static str, Document<'a>)>) { 2621 let argument_variables = arguments 2622 .iter() 2623 .map(|argument| assign_to_variable(&argument.value, assignments, env)) 2624 .collect_vec(); 2625 2626 let arguments = join( 2627 argument_variables 2628 .iter() 2629 .zip(arguments) 2630 .map(|(variable, argument)| { 2631 asserted_expression( 2632 AssertExpression::from_expression(&argument.value), 2633 Some(variable.clone()), 2634 argument.location(), 2635 ) 2636 }), 2637 break_(",", ", "), 2638 ) 2639 .nest(INDENT) 2640 .surround("[", "]"); 2641 2642 ( 2643 docs_arguments_call(function, argument_variables, env), 2644 vec![("kind", atom("function_call")), ("arguments", arguments)], 2645 ) 2646} 2647 2648/// In Gleam, the `&&` operator is short-circuiting, meaning that we can't 2649/// pre-evaluate both sides of it, and use them in the exception that is 2650/// thrown. 2651/// Instead, we need to implement this short-circuiting logic ourself. 2652/// 2653/// If we short-circuit, we must leave the second expression unevaluated, 2654/// and signal that using the `unevaluated` variant, as detailed in the 2655/// exception format. For the first expression, we know it must be `false`, 2656/// otherwise we would have continued by evaluating the second expression. 2657/// 2658/// Similarly, if we do evaluate the second expression and fail, we know 2659/// that the first expression must have evaluated to `true`, and the second 2660/// to `false`. This way, we avoid needing to evaluate either expression 2661/// twice. 2662/// 2663/// The generated code then looks something like this: 2664/// ```erlang 2665/// case expr1 of 2666/// true -> case expr2 of 2667/// true -> true; 2668/// false -> <throw exception> 2669/// end; 2670/// false -> <throw exception> 2671/// end 2672/// ``` 2673/// 2674fn assert_and<'a>( 2675 left: &'a TypedExpr, 2676 right: &'a TypedExpr, 2677 message: Document<'a>, 2678 location: SrcSpan, 2679 env: &mut Env<'a>, 2680) -> Document<'a> { 2681 let left_kind = AssertExpression::from_expression(left); 2682 let right_kind = AssertExpression::from_expression(right); 2683 2684 let fields_if_short_circuiting = vec![ 2685 ("kind", atom("binary_operator")), 2686 ("operator", atom("&&")), 2687 ( 2688 "left", 2689 asserted_expression(left_kind, Some("false".to_doc()), left.location()), 2690 ), 2691 ( 2692 "right", 2693 asserted_expression(AssertExpression::Unevaluated, None, right.location()), 2694 ), 2695 ("start", location.start.to_doc()), 2696 ("'end'", right.location().end.to_doc()), 2697 ("expression_start", left.location().start.to_doc()), 2698 ]; 2699 2700 let fields = vec![ 2701 ("kind", atom("binary_operator")), 2702 ("operator", atom("&&")), 2703 ( 2704 "left", 2705 asserted_expression(left_kind, Some("true".to_doc()), left.location()), 2706 ), 2707 ( 2708 "right", 2709 asserted_expression(right_kind, Some("false".to_doc()), right.location()), 2710 ), 2711 ("start", location.start.to_doc()), 2712 ("'end'", right.location().end.to_doc()), 2713 ("expression_start", left.location().start.to_doc()), 2714 ]; 2715 2716 let right_clauses = docvec![ 2717 line(), 2718 "true -> nil;", 2719 line(), 2720 "false -> ", 2721 erlang_error("assert", &message, location, fields, env), 2722 ]; 2723 2724 let left_clauses = docvec![ 2725 line(), 2726 "true -> ", 2727 docvec![ 2728 "case ", 2729 maybe_block_expr(right, env), 2730 " of", 2731 right_clauses.nest(INDENT), 2732 line(), 2733 "end" 2734 ] 2735 .nest(INDENT), 2736 ";", 2737 line(), 2738 "false -> ", 2739 erlang_error( 2740 "assert", 2741 &message, 2742 location, 2743 fields_if_short_circuiting, 2744 env 2745 ), 2746 ]; 2747 2748 docvec![ 2749 "case ", 2750 maybe_block_expr(left, env), 2751 " of", 2752 left_clauses.nest(INDENT), 2753 line(), 2754 "end" 2755 ] 2756} 2757 2758/// Similar to `&&`, `||` is also short-circuiting in Gleam. However, if `||` 2759/// short-circuits, that's because the first expression evaluated to `true`, 2760/// meaning the whole assertion succeeds. This allows us to directly use Erlang's 2761/// `orelse` operator as the subject of the `case` expression. 2762/// 2763/// The only difference is that due to the nature of `||`, if the assertion fails, 2764/// we know that both sides must have evaluated to `false`, so we don't 2765/// need to store the values of them in variables beforehand. 2766fn assert_or<'a>( 2767 left: &'a TypedExpr, 2768 right: &'a TypedExpr, 2769 message: Document<'a>, 2770 location: SrcSpan, 2771 env: &mut Env<'a>, 2772) -> Document<'a> { 2773 let fields = vec![ 2774 ("kind", atom("binary_operator")), 2775 ("operator", atom("||")), 2776 ( 2777 "left", 2778 asserted_expression( 2779 AssertExpression::from_expression(left), 2780 Some("false".to_doc()), 2781 left.location(), 2782 ), 2783 ), 2784 ( 2785 "right", 2786 asserted_expression( 2787 AssertExpression::from_expression(right), 2788 Some("false".to_doc()), 2789 right.location(), 2790 ), 2791 ), 2792 ("start", location.start.to_doc()), 2793 ("'end'", right.location().end.to_doc()), 2794 ("expression_start", left.location().start.to_doc()), 2795 ]; 2796 2797 let clauses = docvec![ 2798 line(), 2799 "true -> nil;", 2800 line(), 2801 "false -> ", 2802 erlang_error("assert", &message, location, fields, env), 2803 ]; 2804 2805 docvec![ 2806 "case ", 2807 docvec![ 2808 maybe_block_expr(left, env), 2809 " orelse ", 2810 maybe_block_expr(right, env) 2811 ] 2812 .nest(INDENT), 2813 " of", 2814 clauses.nest(INDENT), 2815 line(), 2816 "end" 2817 ] 2818} 2819 2820fn assign_to_variable<'a>( 2821 value: &'a TypedExpr, 2822 assignments: &mut Vec<Document<'a>>, 2823 env: &mut Env<'a>, 2824) -> Document<'a> { 2825 if value.is_var() { 2826 expr(value, env) 2827 } else { 2828 let value = maybe_block_expr(value, env); 2829 let variable = env.next_local_var_name(ASSERT_SUBJECT_VARIABLE); 2830 let definition = docvec![variable.clone(), " = ", value, ",", line()]; 2831 assignments.push(definition); 2832 variable 2833 } 2834} 2835 2836#[derive(Debug, Clone, Copy)] 2837enum AssertExpression { 2838 Literal, 2839 Expression, 2840 Unevaluated, 2841} 2842 2843impl AssertExpression { 2844 fn from_expression(expression: &TypedExpr) -> Self { 2845 if expression.is_literal() { 2846 Self::Literal 2847 } else { 2848 Self::Expression 2849 } 2850 } 2851} 2852 2853fn asserted_expression( 2854 kind: AssertExpression, 2855 value: Option<Document<'_>>, 2856 location: SrcSpan, 2857) -> Document<'_> { 2858 let kind = match kind { 2859 AssertExpression::Literal => atom("literal"), 2860 AssertExpression::Expression => atom("expression"), 2861 AssertExpression::Unevaluated => atom("unevaluated"), 2862 }; 2863 2864 let start = location.start.to_doc(); 2865 let end = location.end.to_doc(); 2866 2867 let value_field = if let Some(value) = value { 2868 docvec!["value => ", value, ",", line()] 2869 } else { 2870 nil() 2871 }; 2872 2873 let fields_doc = docvec![ 2874 "kind => ", 2875 kind, 2876 ",", 2877 line(), 2878 value_field, 2879 "start => ", 2880 start, 2881 ",", 2882 line(), 2883 // `end` is a keyword in Erlang, so we have to quote it 2884 "'end' => ", 2885 end, 2886 line(), 2887 ]; 2888 2889 "#{".to_doc() 2890 .append(fields_doc.group().nest(INDENT)) 2891 .append("}") 2892} 2893 2894fn negate_with<'a>(op: &'static str, value: &'a TypedExpr, env: &mut Env<'a>) -> Document<'a> { 2895 docvec![op, maybe_block_expr(value, env)] 2896} 2897 2898fn tuple_index<'a>(tuple: &'a TypedExpr, index: u64, env: &mut Env<'a>) -> Document<'a> { 2899 let index_doc = eco_format!("{}", (index + 1)).to_doc(); 2900 let tuple_doc = maybe_block_expr(tuple, env); 2901 "erlang:element" 2902 .to_doc() 2903 .append(wrap_arguments([index_doc, tuple_doc])) 2904} 2905 2906fn module_select_fn<'a>(type_: Arc<Type>, module_name: &'a str, label: &'a str) -> Document<'a> { 2907 match crate::type_::collapse_links(type_).as_ref() { 2908 Type::Fn { arguments, .. } => function_reference(Some(module_name), label, arguments.len()), 2909 2910 Type::Named { .. } | Type::Var { .. } | Type::Tuple { .. } => module_name_atom(module_name) 2911 .append(":") 2912 .append(atom(label)) 2913 .append("()"), 2914 } 2915} 2916 2917fn fun<'a>( 2918 arguments: &'a [TypedArg], 2919 body: &'a [TypedStatement], 2920 env: &mut Env<'a>, 2921) -> Document<'a> { 2922 let current_scope_vars = env.current_scope_vars.clone(); 2923 let doc = "fun" 2924 .to_doc() 2925 .append(fun_arguments(arguments, env).append(" ->")) 2926 .append( 2927 break_("", " ") 2928 .append(statement_sequence(body, env)) 2929 .nest(INDENT), 2930 ) 2931 .append(break_("", " ")) 2932 .append("end") 2933 .group(); 2934 env.current_scope_vars = current_scope_vars; 2935 doc 2936} 2937 2938fn incrementing_arguments_list(arity: usize) -> EcoString { 2939 let arguments = (0..arity).map(|c| format!("Field@{c}")); 2940 Itertools::intersperse(arguments, ", ".into()) 2941 .collect::<String>() 2942 .into() 2943} 2944 2945fn variable_name(name: &str) -> EcoString { 2946 let mut chars = name.chars(); 2947 let first_char = chars.next(); 2948 let first_uppercased = first_char.into_iter().flat_map(char::to_uppercase); 2949 2950 first_uppercased.chain(chars).collect::<EcoString>() 2951} 2952 2953/// When rendering a type variable to an erlang type spec we need all type variables with the 2954/// same id to end up with the same name in the generated erlang. 2955/// This function converts a usize into base 26 A-Z for this purpose. 2956fn id_to_type_var(id: u64) -> Document<'static> { 2957 if id < 26 { 2958 let mut name = EcoString::from(""); 2959 name.push(char::from_u32((id % 26 + 65) as u32).expect("id_to_type_var 0")); 2960 return name.to_doc(); 2961 } 2962 let mut name = vec![]; 2963 let mut last_char = id; 2964 while last_char >= 26 { 2965 name.push(char::from_u32((last_char % 26 + 65) as u32).expect("id_to_type_var 1")); 2966 last_char /= 26; 2967 } 2968 name.push(char::from_u32((last_char % 26 + 64) as u32).expect("id_to_type_var 2")); 2969 name.reverse(); 2970 name.into_iter().collect::<EcoString>().to_doc() 2971} 2972 2973pub fn is_erlang_reserved_word(name: &str) -> bool { 2974 matches!( 2975 name, 2976 "!" | "receive" 2977 | "bnot" 2978 | "div" 2979 | "rem" 2980 | "band" 2981 | "bor" 2982 | "bxor" 2983 | "bsl" 2984 | "bsr" 2985 | "not" 2986 | "and" 2987 | "or" 2988 | "xor" 2989 | "orelse" 2990 | "andalso" 2991 | "when" 2992 | "end" 2993 | "fun" 2994 | "try" 2995 | "catch" 2996 | "after" 2997 | "begin" 2998 | "let" 2999 | "query" 3000 | "cond" 3001 | "if" 3002 | "of" 3003 | "case" 3004 | "maybe" 3005 | "else" 3006 ) 3007} 3008 3009// Includes shell_default & user_default which are looked for by the erlang shell 3010pub fn is_erlang_standard_library_module(name: &str) -> bool { 3011 matches!( 3012 name, 3013 "array" 3014 | "base64" 3015 | "beam_lib" 3016 | "binary" 3017 | "c" 3018 | "calendar" 3019 | "dets" 3020 | "dict" 3021 | "digraph" 3022 | "digraph_utils" 3023 | "epp" 3024 | "erl_anno" 3025 | "erl_eval" 3026 | "erl_expand_records" 3027 | "erl_id_trans" 3028 | "erl_internal" 3029 | "erl_lint" 3030 | "erl_parse" 3031 | "erl_pp" 3032 | "erl_scan" 3033 | "erl_tar" 3034 | "ets" 3035 | "file_sorter" 3036 | "filelib" 3037 | "filename" 3038 | "gb_sets" 3039 | "gb_trees" 3040 | "gen_event" 3041 | "gen_fsm" 3042 | "gen_server" 3043 | "gen_statem" 3044 | "io" 3045 | "io_lib" 3046 | "lists" 3047 | "log_mf_h" 3048 | "maps" 3049 | "math" 3050 | "ms_transform" 3051 | "orddict" 3052 | "ordsets" 3053 | "pool" 3054 | "proc_lib" 3055 | "proplists" 3056 | "qlc" 3057 | "queue" 3058 | "rand" 3059 | "random" 3060 | "re" 3061 | "sets" 3062 | "shell" 3063 | "shell_default" 3064 | "shell_docs" 3065 | "slave" 3066 | "sofs" 3067 | "string" 3068 | "supervisor" 3069 | "supervisor_bridge" 3070 | "sys" 3071 | "timer" 3072 | "unicode" 3073 | "uri_string" 3074 | "user_default" 3075 | "win32reg" 3076 | "zip" 3077 ) 3078} 3079 3080// Includes the functions that are autogenerated by erlang itself 3081pub fn escape_erlang_existing_name(name: &str) -> &str { 3082 match name { 3083 "module_info" => "moduleInfo", 3084 _ => name, 3085 } 3086} 3087 3088// A TypeVar can either be rendered as an actual type variable such as `A` or `B`, 3089// or it can be rendered as `any()` depending on how many usages it has. If it 3090// has only 1 usage it is an `any()` type. If it has more than 1 usage it is a 3091// type variable. This function gathers usages for this determination. 3092// 3093// Examples: 3094// fn(a) -> String // `a` is `any()` 3095// fn() -> Result(a, b) // `a` and `b` are `any()` 3096// fn(a) -> a // `a` is a type var 3097fn collect_type_var_usages<'a>( 3098 mut ids: HashMap<u64, u64>, 3099 types: impl IntoIterator<Item = &'a Arc<Type>>, 3100) -> HashMap<u64, u64> { 3101 for type_ in types { 3102 type_var_ids(type_, &mut ids); 3103 } 3104 ids 3105} 3106 3107fn result_type_var_ids(ids: &mut HashMap<u64, u64>, arg_ok: &Type, arg_err: &Type) { 3108 let mut ok_ids = HashMap::new(); 3109 type_var_ids(arg_ok, &mut ok_ids); 3110 3111 let mut err_ids = HashMap::new(); 3112 type_var_ids(arg_err, &mut err_ids); 3113 3114 let mut result_counts = ok_ids; 3115 for (id, count) in err_ids { 3116 let _ = result_counts 3117 .entry(id) 3118 .and_modify(|current_count| { 3119 if *current_count < count { 3120 *current_count = count; 3121 } 3122 }) 3123 .or_insert(count); 3124 } 3125 for (id, count) in result_counts { 3126 let _ = ids 3127 .entry(id) 3128 .and_modify(|current_count| { 3129 *current_count += count; 3130 }) 3131 .or_insert(count); 3132 } 3133} 3134 3135fn type_var_ids(type_: &Type, ids: &mut HashMap<u64, u64>) { 3136 match type_ { 3137 Type::Var { type_ } => match type_.borrow().deref() { 3138 TypeVar::Generic { id, .. } | TypeVar::Unbound { id, .. } => { 3139 let count = ids.entry(*id).or_insert(0); 3140 *count += 1; 3141 } 3142 TypeVar::Link { type_ } => type_var_ids(type_, ids), 3143 }, 3144 Type::Named { 3145 arguments, 3146 module, 3147 name, 3148 .. 3149 } => match arguments[..] { 3150 [ref arg_ok, ref arg_err] if is_prelude_module(module) && name == "Result" => { 3151 result_type_var_ids(ids, arg_ok, arg_err) 3152 } 3153 _ => { 3154 for argument in arguments { 3155 type_var_ids(argument, ids) 3156 } 3157 } 3158 }, 3159 Type::Fn { arguments, return_ } => { 3160 for argument in arguments { 3161 type_var_ids(argument, ids) 3162 } 3163 type_var_ids(return_, ids); 3164 } 3165 Type::Tuple { elements } => { 3166 for element in elements { 3167 type_var_ids(element, ids) 3168 } 3169 } 3170 } 3171} 3172 3173fn erl_safe_type_name(mut name: EcoString) -> EcoString { 3174 if matches!( 3175 name.as_str(), 3176 "any" 3177 | "arity" 3178 | "atom" 3179 | "binary" 3180 | "bitstring" 3181 | "boolean" 3182 | "byte" 3183 | "char" 3184 | "dynamic" 3185 | "float" 3186 | "function" 3187 | "identifier" 3188 | "integer" 3189 | "iodata" 3190 | "iolist" 3191 | "list" 3192 | "map" 3193 | "maybe_improper_list" 3194 | "mfa" 3195 | "module" 3196 | "neg_integer" 3197 | "nil" 3198 | "no_return" 3199 | "node" 3200 | "non_neg_integer" 3201 | "none" 3202 | "nonempty_improper_list" 3203 | "nonempty_list" 3204 | "nonempty_string" 3205 | "number" 3206 | "pid" 3207 | "port" 3208 | "pos_integer" 3209 | "reference" 3210 | "string" 3211 | "term" 3212 | "timeout" 3213 | "tuple" 3214 ) { 3215 name.push('_'); 3216 name 3217 } else { 3218 escape_atom_string(name) 3219 } 3220} 3221 3222#[derive(Debug)] 3223struct TypePrinter<'a> { 3224 var_as_any: bool, 3225 current_module: &'a str, 3226 var_usages: Option<&'a HashMap<u64, u64>>, 3227} 3228 3229impl<'a> TypePrinter<'a> { 3230 fn new(current_module: &'a str) -> Self { 3231 Self { 3232 current_module, 3233 var_usages: None, 3234 var_as_any: false, 3235 } 3236 } 3237 3238 pub fn with_var_usages(mut self, var_usages: &'a HashMap<u64, u64>) -> Self { 3239 self.var_usages = Some(var_usages); 3240 self 3241 } 3242 3243 pub fn print(&self, type_: &Type) -> Document<'static> { 3244 match type_ { 3245 Type::Var { type_ } => self.print_var(&type_.borrow()), 3246 3247 Type::Named { 3248 name, 3249 module, 3250 arguments, 3251 .. 3252 } if is_prelude_module(module) => self.print_prelude_type(name, arguments), 3253 3254 Type::Named { 3255 name, 3256 module, 3257 arguments, 3258 .. 3259 } => self.print_type_app(module, name, arguments), 3260 3261 Type::Fn { arguments, return_ } => self.print_fn(arguments, return_), 3262 3263 Type::Tuple { elements } => tuple(elements.iter().map(|element| self.print(element))), 3264 } 3265 } 3266 3267 fn print_var(&self, type_: &TypeVar) -> Document<'static> { 3268 match type_ { 3269 TypeVar::Generic { .. } | TypeVar::Unbound { .. } if self.var_as_any => { 3270 "any()".to_doc() 3271 } 3272 TypeVar::Generic { id, .. } | TypeVar::Unbound { id, .. } => match &self.var_usages { 3273 Some(usages) => match usages.get(id) { 3274 Some(&0) => nil(), 3275 Some(&1) => "any()".to_doc(), 3276 _ => id_to_type_var(*id), 3277 }, 3278 None => id_to_type_var(*id), 3279 }, 3280 TypeVar::Link { type_ } => self.print(type_), 3281 } 3282 } 3283 3284 fn print_prelude_type(&self, name: &str, arguments: &[Arc<Type>]) -> Document<'static> { 3285 match name { 3286 "Nil" => "nil".to_doc(), 3287 "Int" | "UtfCodepoint" => "integer()".to_doc(), 3288 "String" => "binary()".to_doc(), 3289 "Bool" => "boolean()".to_doc(), 3290 "Float" => "float()".to_doc(), 3291 "BitArray" => "bitstring()".to_doc(), 3292 "List" => { 3293 let arg0 = self.print(arguments.first().expect("print_prelude_type list")); 3294 "list(".to_doc().append(arg0).append(")") 3295 } 3296 "Result" => match arguments { 3297 [arg_ok, arg_err] => { 3298 let ok = tuple(["ok".to_doc(), self.print(arg_ok)]); 3299 let error = tuple(["error".to_doc(), self.print(arg_err)]); 3300 docvec![ok, break_(" |", " | "), error].nest(INDENT).group() 3301 } 3302 _ => panic!("print_prelude_type result expects ok and err"), 3303 }, 3304 // Getting here should mean we either forgot a built-in type or there is a 3305 // compiler error 3306 name => panic!("{name} is not a built-in type."), 3307 } 3308 } 3309 3310 fn print_type_app( 3311 &self, 3312 module: &str, 3313 name: &str, 3314 arguments: &[Arc<Type>], 3315 ) -> Document<'static> { 3316 let arguments = join( 3317 arguments.iter().map(|argument| self.print(argument)), 3318 ", ".to_doc(), 3319 ); 3320 let name = erl_safe_type_name(to_snake_case(name)).to_doc(); 3321 if self.current_module == module { 3322 docvec![name, "(", arguments, ")"] 3323 } else { 3324 docvec![module_name_atom(module), ":", name, "(", arguments, ")"] 3325 } 3326 } 3327 3328 fn print_fn(&self, arguments: &[Arc<Type>], return_: &Type) -> Document<'static> { 3329 let arguments = join( 3330 arguments.iter().map(|argument| self.print(argument)), 3331 ", ".to_doc(), 3332 ); 3333 let return_ = self.print(return_); 3334 "fun((" 3335 .to_doc() 3336 .append(arguments) 3337 .append(") -> ") 3338 .append(return_) 3339 .append(")") 3340 } 3341 3342 /// Print type vars as `any()`. 3343 fn var_as_any(mut self) -> Self { 3344 self.var_as_any = true; 3345 self 3346 } 3347} 3348 3349fn find_private_functions_referenced_in_importable_constants( 3350 module: &TypedModule, 3351) -> im::HashSet<EcoString> { 3352 let mut overridden_publicity = im::HashSet::new(); 3353 3354 for constant in &module.definitions.constants { 3355 if constant.publicity.is_importable() { 3356 find_referenced_private_functions(&constant.value, &mut overridden_publicity) 3357 } 3358 } 3359 overridden_publicity 3360} 3361 3362fn find_referenced_private_functions( 3363 constant: &TypedConstant, 3364 already_found: &mut im::HashSet<EcoString>, 3365) { 3366 match constant { 3367 Constant::Invalid { .. } => panic!("invalid constants should not reach code generation"), 3368 Constant::RecordUpdate { .. } => { 3369 panic!("record updates should not reach code generation") 3370 } 3371 3372 Constant::Int { .. } 3373 | Constant::Float { .. } 3374 | Constant::String { .. } 3375 | Constant::BitArray { .. } => (), 3376 3377 TypedConstant::Var { 3378 name, constructor, .. 3379 } => { 3380 if let Some(ValueConstructor { type_, .. }) = constructor.as_deref() 3381 && let Type::Fn { .. } = **type_ 3382 { 3383 let _ = already_found.insert(name.clone()); 3384 } 3385 } 3386 3387 TypedConstant::Record { arguments, .. } => arguments 3388 .iter() 3389 .for_each(|argument| find_referenced_private_functions(&argument.value, already_found)), 3390 3391 TypedConstant::StringConcatenation { left, right, .. } => { 3392 find_referenced_private_functions(left, already_found); 3393 find_referenced_private_functions(right, already_found); 3394 } 3395 3396 Constant::Tuple { elements, .. } | Constant::List { elements, .. } => elements 3397 .iter() 3398 .for_each(|element| find_referenced_private_functions(element, already_found)), 3399 } 3400}