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