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

Configure Feed

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

use a struct for the erlang source code printer

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