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

Configure Feed

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

gleam / compiler-core / src / format.rs
114 kB 3164 lines
1#[cfg(test)] 2mod tests; 3 4use crate::{ 5 Error, Result, 6 ast::{ 7 CustomType, Import, ModuleConstant, TypeAlias, TypeAstConstructor, TypeAstFn, TypeAstHole, 8 TypeAstTuple, TypeAstVar, *, 9 }, 10 build::Target, 11 docvec, 12 io::Utf8Writer, 13 parse::SpannedString, 14 parse::extra::{Comment, ModuleExtra}, 15 pretty::{self, *}, 16 type_::{self, Type}, 17 warning::WarningEmitter, 18}; 19use ecow::{EcoString, eco_format}; 20use itertools::Itertools; 21use std::{cmp::Ordering, sync::Arc}; 22use vec1::Vec1; 23 24use crate::type_::Deprecation; 25use camino::Utf8Path; 26 27const INDENT: isize = 2; 28 29pub fn pretty(writer: &mut impl Utf8Writer, src: &EcoString, path: &Utf8Path) -> Result<()> { 30 let parsed = crate::parse::parse_module(path.to_owned(), src, &WarningEmitter::null()) 31 .map_err(|error| Error::Parse { 32 path: path.to_path_buf(), 33 src: src.clone(), 34 error, 35 })?; 36 let intermediate = Intermediate::from_extra(&parsed.extra, src); 37 Formatter::with_comments(&intermediate) 38 .module(&parsed.module) 39 .pretty_print(80, writer) 40} 41 42pub(crate) struct Intermediate<'a> { 43 comments: Vec<Comment<'a>>, 44 doc_comments: Vec<Comment<'a>>, 45 module_comments: Vec<Comment<'a>>, 46 empty_lines: &'a [u32], 47 new_lines: &'a [u32], 48} 49 50impl<'a> Intermediate<'a> { 51 pub fn from_extra(extra: &'a ModuleExtra, src: &'a EcoString) -> Intermediate<'a> { 52 Intermediate { 53 comments: extra 54 .comments 55 .iter() 56 .map(|span| Comment::from((span, src))) 57 .collect(), 58 doc_comments: extra 59 .doc_comments 60 .iter() 61 .map(|span| Comment::from((span, src))) 62 .collect(), 63 empty_lines: &extra.empty_lines, 64 module_comments: extra 65 .module_comments 66 .iter() 67 .map(|span| Comment::from((span, src))) 68 .collect(), 69 new_lines: &extra.new_lines, 70 } 71 } 72} 73 74#[derive(Debug)] 75enum FnCapturePosition { 76 RightHandSideOfPipe, 77 EverywhereElse, 78} 79 80#[derive(Debug)] 81/// One of the pieces making a record update arg list: it could be the starting 82/// record being updated, or one of the subsequent arguments. 83/// 84enum RecordUpdatePiece<'a> { 85 Record(&'a RecordBeingUpdated), 86 Argument(&'a UntypedRecordUpdateArg), 87} 88 89impl HasLocation for RecordUpdatePiece<'_> { 90 fn location(&self) -> SrcSpan { 91 match self { 92 RecordUpdatePiece::Record(record) => record.location, 93 RecordUpdatePiece::Argument(arg) => arg.location, 94 } 95 } 96} 97 98/// Hayleigh's bane 99#[derive(Debug, Clone, Default)] 100pub struct Formatter<'a> { 101 comments: &'a [Comment<'a>], 102 doc_comments: &'a [Comment<'a>], 103 module_comments: &'a [Comment<'a>], 104 empty_lines: &'a [u32], 105 new_lines: &'a [u32], 106} 107 108impl<'comments> Formatter<'comments> { 109 pub fn new() -> Self { 110 Default::default() 111 } 112 113 pub(crate) fn with_comments(extra: &'comments Intermediate<'comments>) -> Self { 114 Self { 115 comments: &extra.comments, 116 doc_comments: &extra.doc_comments, 117 module_comments: &extra.module_comments, 118 empty_lines: extra.empty_lines, 119 new_lines: extra.new_lines, 120 } 121 } 122 123 fn any_comments(&self, limit: u32) -> bool { 124 self.comments 125 .first() 126 .is_some_and(|comment| comment.start < limit) 127 } 128 129 fn any_empty_lines(&self, limit: u32) -> bool { 130 self.empty_lines.first().is_some_and(|line| *line < limit) 131 } 132 133 /// Pop comments that occur before a byte-index in the source, consuming 134 /// and retaining any empty lines contained within. 135 /// Returns an iterator of comments with their start position. 136 fn pop_comments_with_position( 137 &mut self, 138 limit: u32, 139 ) -> impl Iterator<Item = (u32, Option<&'comments str>)> + use<'comments> { 140 let (popped, rest, empty_lines) = 141 comments_before(self.comments, self.empty_lines, limit, true); 142 self.comments = rest; 143 self.empty_lines = empty_lines; 144 popped 145 } 146 147 /// Pop comments that occur before a byte-index in the source, consuming 148 /// and retaining any empty lines contained within. 149 fn pop_comments( 150 &mut self, 151 limit: u32, 152 ) -> impl Iterator<Item = Option<&'comments str>> + use<'comments> { 153 self.pop_comments_with_position(limit) 154 .map(|(_position, comment)| comment) 155 } 156 157 /// Pop doc comments that occur before a byte-index in the source, consuming 158 /// and dropping any empty lines contained within. 159 fn pop_doc_comments( 160 &mut self, 161 limit: u32, 162 ) -> impl Iterator<Item = Option<&'comments str>> + use<'comments> { 163 let (popped, rest, empty_lines) = 164 comments_before(self.doc_comments, self.empty_lines, limit, false); 165 self.doc_comments = rest; 166 self.empty_lines = empty_lines; 167 popped.map(|(_position, comment)| comment) 168 } 169 170 /// Remove between 0 and `limit` empty lines following the current position, 171 /// returning true if any empty lines were removed. 172 fn pop_empty_lines(&mut self, limit: u32) -> bool { 173 let mut end = 0; 174 for (i, &position) in self.empty_lines.iter().enumerate() { 175 if position > limit { 176 break; 177 } 178 end = i + 1; 179 } 180 181 self.empty_lines = self 182 .empty_lines 183 .get(end..) 184 .expect("Pop empty lines slicing"); 185 end != 0 186 } 187 188 fn targeted_definition<'a>(&mut self, definition: &'a TargetedDefinition) -> Document<'a> { 189 let target = definition.target; 190 let definition = &definition.definition; 191 let start = definition.location().start; 192 193 let comments = self.pop_comments_with_position(start); 194 let comments = self.printed_documented_comments(comments); 195 let document = self.documented_definition(definition); 196 let document = match target { 197 None => document, 198 Some(Target::Erlang) => docvec!["@target(erlang)", line(), document], 199 Some(Target::JavaScript) => docvec!["@target(javascript)", line(), document], 200 }; 201 202 comments.to_doc().append(document.group()) 203 } 204 205 pub(crate) fn module<'a>(&mut self, module: &'a UntypedModule) -> Document<'a> { 206 let mut documents = vec![]; 207 let mut previous_was_a_definition = false; 208 209 // Here we take consecutive groups of imports so that they can be sorted 210 // alphabetically. 211 for (is_import_group, definitions) in &module 212 .definitions 213 .iter() 214 .chunk_by(|definition| definition.definition.is_import()) 215 { 216 if is_import_group { 217 if previous_was_a_definition { 218 documents.push(lines(2)); 219 } 220 documents.append(&mut self.imports(definitions.collect_vec())); 221 previous_was_a_definition = false; 222 } else { 223 for definition in definitions { 224 if !documents.is_empty() { 225 documents.push(lines(2)); 226 } 227 documents.push(self.targeted_definition(definition)); 228 } 229 previous_was_a_definition = true; 230 } 231 } 232 233 let definitions = concat(documents); 234 235 // Now that definitions has been collected, only freestanding comments (//) 236 // and doc comments (///) remain. Freestanding comments aren't associated 237 // with any statement, and are moved to the bottom of the module. 238 let doc_comments = join( 239 self.doc_comments 240 .iter() 241 .map(|comment| "///".to_doc().append(EcoString::from(comment.content))), 242 line(), 243 ); 244 245 let comments = match printed_comments(self.pop_comments(u32::MAX), false) { 246 Some(comments) => comments, 247 None => nil(), 248 }; 249 250 let module_comments = if !self.module_comments.is_empty() { 251 let comments = self 252 .module_comments 253 .iter() 254 .map(|s| "////".to_doc().append(EcoString::from(s.content))); 255 join(comments, line()).append(line()) 256 } else { 257 nil() 258 }; 259 260 let non_empty = vec![module_comments, definitions, doc_comments, comments] 261 .into_iter() 262 .filter(|doc| !doc.is_empty()); 263 264 join(non_empty, line()).append(line()) 265 } 266 267 /// Separates the imports in groups delimited by comments or empty lines and 268 /// sorts each group alphabetically. 269 /// 270 /// The formatter needs to play nicely with import groups defined by the 271 /// programmer. If one puts a comment before an import then that's a clue 272 /// for the formatter that it has run into a gorup of related imports. 273 /// 274 /// So we can't just sort `imports` and format each one, we have to be a 275 /// bit smarter and see if each import is preceded by a comment. 276 /// Once we find a comment we know we're done with the current import 277 /// group and a new one has started. 278 /// 279 /// ```gleam 280 /// // This is an import group. 281 /// import gleam/int 282 /// import gleam/string 283 /// 284 /// // This marks the beginning of a new import group that can't 285 /// // be mushed together with the previous one! 286 /// import wibble 287 /// import wobble 288 /// ``` 289 fn imports<'a>(&mut self, imports: Vec<&'a TargetedDefinition>) -> Vec<Document<'a>> { 290 let mut import_groups_docs = vec![]; 291 let mut current_group = vec![]; 292 let mut current_group_delimiter = nil(); 293 294 for import in imports { 295 let start = import.definition.location().start; 296 297 // We need to start a new group if the `import` is preceded by one or 298 // more empty lines or a `//` comment. 299 let start_new_group = self.any_comments(start) || self.any_empty_lines(start); 300 if start_new_group { 301 // First we print the previous group and clear it out to start a 302 // new empty group containing the import we've just ran into. 303 if !current_group.is_empty() { 304 import_groups_docs.push(docvec![ 305 current_group_delimiter, 306 self.sorted_import_group(&current_group) 307 ]); 308 current_group.clear(); 309 } 310 311 // Now that we've taken care of the previous group we can start 312 // the new one. We know it's preceded either by an empty line or 313 // some comments se we have to be a bit more precise and save the 314 // actual delimiter that we're going to put at the top of this 315 // group. 316 317 let comments = self.pop_comments(start); 318 let _ = self.pop_empty_lines(start); 319 current_group_delimiter = printed_comments(comments, true).unwrap_or(nil()); 320 } 321 // Lastly we add the import to the group. 322 current_group.push(import); 323 } 324 325 // Let's not forget about the last import group! 326 if !current_group.is_empty() { 327 import_groups_docs.push(docvec![ 328 current_group_delimiter, 329 self.sorted_import_group(&current_group) 330 ]); 331 } 332 333 // We want all consecutive import groups to be separated by an empty line. 334 // This should really be `.intersperse(line())` but I can't do that 335 // because of https://github.com/rust-lang/rust/issues/48919. 336 Itertools::intersperse(import_groups_docs.into_iter(), lines(2)).collect_vec() 337 } 338 339 /// Prints the imports as a single sorted group of import statements. 340 /// 341 fn sorted_import_group<'a>(&mut self, imports: &[&'a TargetedDefinition]) -> Document<'a> { 342 let imports = imports 343 .iter() 344 .sorted_by(|one, other| match (&one.definition, &other.definition) { 345 (Definition::Import(one), Definition::Import(other)) => { 346 one.module.cmp(&other.module) 347 } 348 // It shouldn't really be possible for a non import to be here so 349 // we just return a default value. 350 _ => Ordering::Equal, 351 }) 352 .map(|import| self.targeted_definition(import)); 353 354 // This should really be `.intersperse(line())` but I can't do that 355 // because of https://github.com/rust-lang/rust/issues/48919. 356 Itertools::intersperse(imports, line()) 357 .collect_vec() 358 .to_doc() 359 } 360 361 fn definition<'a>(&mut self, statement: &'a UntypedDefinition) -> Document<'a> { 362 match statement { 363 Definition::Function(function) => self.statement_fn(function), 364 365 Definition::TypeAlias(TypeAlias { 366 alias, 367 parameters: args, 368 type_ast: resolved_type, 369 publicity, 370 deprecation, 371 location, 372 .. 373 }) => self.type_alias( 374 *publicity, 375 alias, 376 args, 377 resolved_type, 378 deprecation, 379 location, 380 ), 381 382 Definition::CustomType(ct) => self.custom_type(ct), 383 384 Definition::Import(Import { 385 module, 386 as_name, 387 unqualified_values, 388 unqualified_types, 389 .. 390 }) => { 391 let second = if unqualified_values.is_empty() && unqualified_types.is_empty() { 392 nil() 393 } else { 394 let unqualified_types = unqualified_types 395 .iter() 396 .sorted_by(|a, b| a.name.cmp(&b.name)) 397 .map(|type_| docvec!["type ", type_]); 398 let unqualified_values = unqualified_values 399 .iter() 400 .sorted_by(|a, b| a.name.cmp(&b.name)) 401 .map(|value| value.to_doc()); 402 let unqualified = join( 403 unqualified_types.chain(unqualified_values), 404 flex_break(",", ", "), 405 ); 406 let unqualified = break_("", "") 407 .append(unqualified) 408 .nest(INDENT) 409 .append(break_(",", "")) 410 .group(); 411 ".{".to_doc().append(unqualified).append("}") 412 }; 413 414 let doc = docvec!["import ", module.as_str(), second]; 415 let default_module_access_name = module.split('/').next_back().map(EcoString::from); 416 match (default_module_access_name, as_name) { 417 // If the `as name` is the same as the module name that would be 418 // used anyways we won't render it. For example: 419 // ```gleam 420 // import gleam/int as int 421 // ^^^^^^ this is redundant and removed 422 // ``` 423 (Some(module_name), Some((AssignName::Variable(name), _))) 424 if &module_name == name => 425 { 426 doc 427 } 428 (_, None) => doc, 429 (_, Some((AssignName::Variable(name) | AssignName::Discard(name), _))) => { 430 doc.append(" as ").append(name) 431 } 432 } 433 } 434 435 Definition::ModuleConstant(ModuleConstant { 436 publicity, 437 name, 438 annotation, 439 value, 440 .. 441 }) => { 442 let attributes = AttributesPrinter::new().set_internal(*publicity).to_doc(); 443 let head = attributes 444 .append(pub_(*publicity)) 445 .append("const ") 446 .append(name.as_str()); 447 let head = match annotation { 448 None => head, 449 Some(t) => head.append(": ").append(self.type_ast(t)), 450 }; 451 head.append(" = ").append(self.const_expr(value)) 452 } 453 } 454 } 455 456 fn const_expr<'a, A, B>(&mut self, value: &'a Constant<A, B>) -> Document<'a> { 457 let comments = self.pop_comments(value.location().start); 458 let document = match value { 459 Constant::Int { value, .. } => self.int(value), 460 461 Constant::Float { value, .. } => self.float(value), 462 463 Constant::String { value, .. } => self.string(value), 464 465 Constant::List { 466 elements, location, .. 467 } => self.const_list(elements, location), 468 469 Constant::Tuple { 470 elements, location, .. 471 } => self.const_tuple(elements, location), 472 473 Constant::BitArray { 474 segments, location, .. 475 } => { 476 let segment_docs = segments 477 .iter() 478 .map(|segment| bit_array_segment(segment, |e| self.const_expr(e))) 479 .collect_vec(); 480 481 self.bit_array( 482 segment_docs, 483 segments.iter().all(|s| s.value.is_simple()), 484 location, 485 ) 486 } 487 488 Constant::Record { 489 name, 490 args, 491 module: None, 492 .. 493 } if args.is_empty() => name.to_doc(), 494 495 Constant::Record { 496 name, 497 args, 498 module: Some((m, _)), 499 .. 500 } if args.is_empty() => m.to_doc().append(".").append(name.as_str()), 501 502 Constant::Record { 503 name, 504 args, 505 module: None, 506 location, 507 .. 508 } => { 509 let args = args.iter().map(|a| self.constant_call_arg(a)).collect_vec(); 510 name.to_doc() 511 .append(self.wrap_args(args, location.end)) 512 .group() 513 } 514 515 Constant::Record { 516 name, 517 args, 518 module: Some((m, _)), 519 location, 520 .. 521 } => { 522 let args = args.iter().map(|a| self.constant_call_arg(a)).collect_vec(); 523 m.to_doc() 524 .append(".") 525 .append(name.as_str()) 526 .append(self.wrap_args(args, location.end)) 527 .group() 528 } 529 530 Constant::Var { 531 name, module: None, .. 532 } => name.to_doc(), 533 534 Constant::Var { 535 name, 536 module: Some((module, _)), 537 .. 538 } => docvec![module, ".", name], 539 540 Constant::StringConcatenation { left, right, .. } => self 541 .const_expr(left) 542 .append(break_("", " ").append("<>".to_doc())) 543 .nest(INDENT) 544 .append(" ") 545 .append(self.const_expr(right)), 546 547 Constant::Invalid { .. } => { 548 panic!("invalid constants can not be in an untyped ast") 549 } 550 }; 551 commented(document, comments) 552 } 553 554 fn const_list<'a, A, B>( 555 &mut self, 556 elements: &'a [Constant<A, B>], 557 location: &SrcSpan, 558 ) -> Document<'a> { 559 if elements.is_empty() { 560 // We take all comments that come _before_ the end of the list, 561 // that is all comments that are inside "[" and "]", if there's 562 // any comment we want to put it inside the empty list! 563 return match printed_comments(self.pop_comments(location.end), false) { 564 None => "[]".to_doc(), 565 Some(comments) => "[" 566 .to_doc() 567 .append(break_("", "").nest(INDENT)) 568 .append(comments) 569 .append(break_("", "")) 570 .append("]") 571 // vvv We want to make sure the comments are on a separate 572 // line from the opening and closing brackets so we 573 // force the breaks to be split on newlines. 574 .force_break(), 575 }; 576 } 577 578 let comma = match elements.first() { 579 // If the list is made of non-simple constants and it gets too long we want to 580 // have each record on its own line instead of trying to fit as much 581 // as possible in each line. For example: 582 // 583 // [ 584 // Some("wibble wobble"), 585 // None, 586 // Some("wobble wibble"), 587 // ] 588 // 589 Some(el) if !el.is_simple() => break_(",", ", "), 590 // For simple constants(String, Int, Float), if we have to break the list we still try to 591 // fit as much as possible into a single line instead of putting 592 // each item on its own separate line. For example: 593 // 594 // [ 595 // 1, 2, 3, 4, 596 // 5, 6, 7, 597 // ] 598 // 599 Some(_) | None => flex_break(",", ", "), 600 }; 601 602 let elements = join( 603 elements.iter().map(|element| self.const_expr(element)), 604 comma, 605 ); 606 607 let doc = break_("[", "[").append(elements).nest(INDENT); 608 609 // We get all remaining comments that come before the list's closing 610 // square bracket. 611 // If there's any we add those before the closing square bracket instead 612 // of moving those out of the list. 613 // Otherwise those would be moved out of the list. 614 let comments = self.pop_comments(location.end); 615 match printed_comments(comments, false) { 616 None => doc.append(break_(",", "")).append("]").group(), 617 Some(comment) => doc 618 .append(break_(",", "").nest(INDENT)) 619 // ^ See how here we're adding the missing indentation to the 620 // final break so that the final comment is as indented as the 621 // list's items. 622 .append(comment) 623 .append(line()) 624 .append("]") 625 .force_break(), 626 } 627 } 628 629 pub fn const_tuple<'a, A, B>( 630 &mut self, 631 elements: &'a [Constant<A, B>], 632 location: &SrcSpan, 633 ) -> Document<'a> { 634 if elements.is_empty() { 635 // We take all comments that come _before_ the end of the tuple, 636 // that is all comments that are inside "#(" and ")", if there's 637 // any comment we want to put it inside the empty list! 638 return match printed_comments(self.pop_comments(location.end), false) { 639 None => "#()".to_doc(), 640 Some(comments) => "#(" 641 .to_doc() 642 .append(break_("", "").nest(INDENT)) 643 .append(comments) 644 .append(break_("", "")) 645 .append(")") 646 // vvv We want to make sure the comments are on a separate 647 // line from the opening and closing parentheses so we 648 // force the breaks to be split on newlines. 649 .force_break(), 650 }; 651 } 652 653 let args_docs = elements.iter().map(|element| self.const_expr(element)); 654 let tuple_doc = break_("#(", "#(") 655 .append(join(args_docs, break_(",", ", ")).next_break_fits(NextBreakFitsMode::Disabled)) 656 .nest(INDENT); 657 658 let comments = self.pop_comments(location.end); 659 match printed_comments(comments, false) { 660 None => tuple_doc.append(break_(",", "")).append(")").group(), 661 Some(comments) => tuple_doc 662 .append(break_(",", "").nest(INDENT)) 663 .append(comments) 664 .append(line()) 665 .append(")") 666 .force_break(), 667 } 668 } 669 670 pub fn docs_const_expr<'a>( 671 &mut self, 672 publicity: Publicity, 673 name: &'a str, 674 value: &'a TypedConstant, 675 ) -> Document<'a> { 676 let type_ = type_::pretty::Printer::new().print(&value.type_()); 677 let attributes = AttributesPrinter::new().set_internal(publicity); 678 docvec![attributes, pub_(publicity), "const ", name, ": ", type_] 679 } 680 681 fn documented_definition<'a>(&mut self, s: &'a UntypedDefinition) -> Document<'a> { 682 let comments = self.doc_comments(s.location().start); 683 comments.append(self.definition(s).group()).group() 684 } 685 686 fn doc_comments<'a>(&mut self, limit: u32) -> Document<'a> { 687 let mut comments = self.pop_doc_comments(limit).peekable(); 688 match comments.peek() { 689 None => nil(), 690 Some(_) => join( 691 comments.map(|c| match c { 692 Some(c) => "///".to_doc().append(EcoString::from(c)), 693 None => unreachable!("empty lines dropped by pop_doc_comments"), 694 }), 695 line(), 696 ) 697 .append(line()) 698 .force_break(), 699 } 700 } 701 702 fn type_ast_constructor<'a>( 703 &mut self, 704 module: &'a Option<(EcoString, SrcSpan)>, 705 name: &'a str, 706 args: &'a [TypeAst], 707 location: &SrcSpan, 708 _name_location: &SrcSpan, 709 ) -> Document<'a> { 710 let head = module 711 .as_ref() 712 .map(|(qualifier, _)| qualifier.to_doc().append(".").append(name)) 713 .unwrap_or_else(|| name.to_doc()); 714 715 if args.is_empty() { 716 head 717 } else { 718 head.append(self.type_arguments(args, location)) 719 } 720 } 721 722 fn type_ast<'a>(&mut self, t: &'a TypeAst) -> Document<'a> { 723 match t { 724 TypeAst::Hole(TypeAstHole { name, .. }) => name.to_doc(), 725 726 TypeAst::Constructor(TypeAstConstructor { 727 name, 728 arguments: args, 729 module, 730 location, 731 name_location, 732 }) => self.type_ast_constructor(module, name, args, location, name_location), 733 734 TypeAst::Fn(TypeAstFn { 735 arguments: args, 736 return_, 737 location, 738 }) => "fn" 739 .to_doc() 740 .append(self.type_arguments(args, location)) 741 .group() 742 .append(" ->") 743 .append(break_("", " ").append(self.type_ast(return_)).nest(INDENT)), 744 745 TypeAst::Var(TypeAstVar { name, .. }) => name.to_doc(), 746 747 TypeAst::Tuple(TypeAstTuple { elements, location }) => { 748 "#".to_doc().append(self.type_arguments(elements, location)) 749 } 750 } 751 .group() 752 } 753 754 fn type_arguments<'a>(&mut self, args: &'a [TypeAst], location: &SrcSpan) -> Document<'a> { 755 let args = args.iter().map(|type_| self.type_ast(type_)).collect_vec(); 756 self.wrap_args(args, location.end) 757 } 758 759 pub fn type_alias<'a>( 760 &mut self, 761 publicity: Publicity, 762 name: &'a str, 763 args: &'a [SpannedString], 764 type_: &'a TypeAst, 765 deprecation: &'a Deprecation, 766 location: &SrcSpan, 767 ) -> Document<'a> { 768 let attributes = AttributesPrinter::new() 769 .set_deprecation(deprecation) 770 .set_internal(publicity) 771 .to_doc(); 772 773 let head = docvec![attributes, pub_(publicity), "type ", name]; 774 let head = if args.is_empty() { 775 head 776 } else { 777 let args = args.iter().map(|(_, e)| e.to_doc()).collect_vec(); 778 head.append(self.wrap_args(args, location.end).group()) 779 }; 780 781 head.append(" =") 782 .append(line().append(self.type_ast(type_)).group().nest(INDENT)) 783 } 784 785 fn fn_arg<'a, A>(&mut self, arg: &'a Arg<A>) -> Document<'a> { 786 let comments = self.pop_comments(arg.location.start); 787 let doc = match &arg.annotation { 788 None => arg.names.to_doc(), 789 Some(a) => arg.names.to_doc().append(": ").append(self.type_ast(a)), 790 } 791 .group(); 792 commented(doc, comments) 793 } 794 795 fn statement_fn<'a>(&mut self, function: &'a UntypedFunction) -> Document<'a> { 796 let attributes = AttributesPrinter::new() 797 .set_deprecation(&function.deprecation) 798 .set_internal(function.publicity) 799 .set_external_erlang(&function.external_erlang) 800 .set_external_javascript(&function.external_javascript) 801 .to_doc(); 802 803 // Fn name and args 804 let args = function 805 .arguments 806 .iter() 807 .map(|argument| self.fn_arg(argument)) 808 .collect_vec(); 809 let signature = pub_(function.publicity) 810 .append("fn ") 811 .append( 812 &function 813 .name 814 .as_ref() 815 .expect("Function in a statement must be named") 816 .1, 817 ) 818 .append(self.wrap_args(args, function.location.end)); 819 820 // Add return annotation 821 let signature = match &function.return_annotation { 822 Some(anno) => signature.append(" -> ").append(self.type_ast(anno)), 823 None => signature, 824 } 825 .group(); 826 827 let body = &function.body; 828 if body.len() == 1 && body.first().is_placeholder() { 829 return attributes.append(signature); 830 } 831 832 let head = attributes.append(signature); 833 834 // Format body 835 let body = self.statements(body); 836 837 // Add any trailing comments 838 let body = match printed_comments(self.pop_comments(function.end_position), false) { 839 Some(comments) => body.append(line()).append(comments), 840 None => body, 841 }; 842 843 // Stick it all together 844 head.append(" {") 845 .append(line().append(body).nest(INDENT).group()) 846 .append(line()) 847 .append("}") 848 } 849 850 fn expr_fn<'a>( 851 &mut self, 852 args: &'a [UntypedArg], 853 return_annotation: Option<&'a TypeAst>, 854 body: &'a Vec1<UntypedStatement>, 855 location: &SrcSpan, 856 end_of_head_byte_index: &u32, 857 ) -> Document<'a> { 858 let args_docs = args.iter().map(|arg| self.fn_arg(arg)).collect_vec(); 859 let args = self 860 .wrap_args(args_docs, *end_of_head_byte_index) 861 .group() 862 .next_break_fits(NextBreakFitsMode::Disabled); 863 // ^^^ We add this so that when an expression function is passed as 864 // the last argument of a function and it goes over the line 865 // limit with just its arguments we don't get some strange 866 // splitting. 867 // See https://github.com/gleam-lang/gleam/issues/2571 868 // 869 // There's many ways we could be smarter than this. For example: 870 // - still split the arguments like it did in the example shown in the 871 // issue if the expr_fn has more than one argument 872 // - make sure that an anonymous function whose body is made up of a 873 // single expression doesn't get split (I think that could boil down 874 // to wrapping the body with a `next_break_fits(Disabled)`) 875 // 876 // These are some of the ways we could tweak the look of expression 877 // functions in the future if people are not satisfied with it. 878 879 let header = "fn".to_doc().append(args); 880 881 let header = match return_annotation { 882 None => header, 883 Some(t) => header.append(" -> ").append(self.type_ast(t)), 884 }; 885 886 let statements = self.statements(body); 887 let body = match printed_comments(self.pop_comments(location.end), false) { 888 None => statements, 889 Some(comments) => statements.append(line()).append(comments).force_break(), 890 }; 891 892 header.append(" ").append(wrap_block(body)).group() 893 } 894 895 fn statements<'a>(&mut self, statements: &'a Vec1<UntypedStatement>) -> Document<'a> { 896 let mut previous_position = 0; 897 let count = statements.len(); 898 let mut documents = Vec::with_capacity(count * 2); 899 for (i, statement) in statements.iter().enumerate() { 900 let preceding_newline = self.pop_empty_lines(previous_position + 1); 901 if i != 0 && preceding_newline { 902 documents.push(lines(2)); 903 } else if i != 0 { 904 documents.push(line()); 905 } 906 previous_position = statement.location().end; 907 documents.push(self.statement(statement).group()); 908 909 // If the last statement is a use we make sure it's followed by a 910 // todo to make it explicit it has an unimplemented callback. 911 if statement.is_use() && i == count - 1 { 912 documents.push(line()); 913 documents.push("todo".to_doc()); 914 } 915 } 916 if count == 1 && statements.first().is_expression() { 917 documents.to_doc() 918 } else { 919 documents.to_doc().force_break() 920 } 921 } 922 923 fn assignment<'a>(&mut self, assignment: &'a UntypedAssignment) -> Document<'a> { 924 let comments = self.pop_comments(assignment.location.start); 925 let Assignment { 926 pattern, 927 value, 928 kind, 929 annotation, 930 .. 931 } = assignment; 932 933 let _ = self.pop_empty_lines(pattern.location().end); 934 935 let (keyword, message) = match kind { 936 AssignmentKind::Let | AssignmentKind::Generated => ("let ", None), 937 AssignmentKind::Assert { message, .. } => ( 938 "let assert ", 939 message.as_ref().map(|message| { 940 break_("", " ") 941 .nest(INDENT) 942 .append("as ".to_doc().append(self.expr(message).group())) 943 }), 944 ), 945 }; 946 947 let pattern = self.pattern(pattern); 948 949 let annotation = annotation 950 .as_ref() 951 .map(|a| ": ".to_doc().append(self.type_ast(a))); 952 953 let doc = keyword 954 .to_doc() 955 .append(pattern.append(annotation).group()) 956 .append(" =") 957 .append(self.assigned_value(value)) 958 .append(message); 959 commented(doc, comments) 960 } 961 962 fn expr<'a>(&mut self, expr: &'a UntypedExpr) -> Document<'a> { 963 let comments = self.pop_comments(expr.start_byte_index()); 964 965 let document = match expr { 966 UntypedExpr::Placeholder { .. } => panic!("Placeholders should not be formatted"), 967 968 UntypedExpr::Panic { 969 message: Some(m), .. 970 } => docvec!["panic as ", self.expr(m)], 971 972 UntypedExpr::Panic { .. } => "panic".to_doc(), 973 974 UntypedExpr::Todo { message: None, .. } => "todo".to_doc(), 975 976 UntypedExpr::Todo { 977 message: Some(l), .. 978 } => docvec!["todo as ", self.expr(l)], 979 980 UntypedExpr::Echo { 981 expression, 982 location: _, 983 } => self.echo(expression), 984 985 UntypedExpr::PipeLine { expressions, .. } => self.pipeline(expressions, false), 986 987 UntypedExpr::Int { value, .. } => self.int(value), 988 989 UntypedExpr::Float { value, .. } => self.float(value), 990 991 UntypedExpr::String { value, .. } => self.string(value), 992 993 UntypedExpr::Block { 994 statements, 995 location, 996 .. 997 } => self.block(location, statements, false), 998 999 UntypedExpr::Var { name, .. } if name == CAPTURE_VARIABLE => "_".to_doc(), 1000 1001 UntypedExpr::Var { name, .. } => name.to_doc(), 1002 1003 UntypedExpr::TupleIndex { tuple, index, .. } => self.tuple_index(tuple, *index), 1004 1005 UntypedExpr::NegateInt { value, .. } => self.negate_int(value), 1006 1007 UntypedExpr::NegateBool { value, .. } => self.negate_bool(value), 1008 1009 UntypedExpr::Fn { kind, body, .. } if kind.is_capture() => { 1010 self.fn_capture(body, FnCapturePosition::EverywhereElse) 1011 } 1012 1013 UntypedExpr::Fn { 1014 return_annotation, 1015 arguments: args, 1016 body, 1017 location, 1018 end_of_head_byte_index, 1019 .. 1020 } => self.expr_fn( 1021 args, 1022 return_annotation.as_ref(), 1023 body, 1024 location, 1025 end_of_head_byte_index, 1026 ), 1027 1028 UntypedExpr::List { 1029 elements, 1030 tail, 1031 location, 1032 } => self.list(elements, tail.as_deref(), location), 1033 1034 UntypedExpr::Call { 1035 fun, 1036 arguments: args, 1037 location, 1038 .. 1039 } => self.call(fun, args, location), 1040 1041 UntypedExpr::BinOp { 1042 name, left, right, .. 1043 } => self.bin_op(name, left, right, false), 1044 1045 UntypedExpr::Case { 1046 subjects, 1047 clauses, 1048 location, 1049 } => self.case(subjects, clauses.as_deref().unwrap_or_default(), location), 1050 1051 UntypedExpr::FieldAccess { 1052 label, container, .. 1053 } => if let UntypedExpr::TupleIndex { .. } = container.as_ref() { 1054 self.expr(container).surround("{ ", " }") 1055 } else { 1056 self.expr(container) 1057 } 1058 .append(".") 1059 .append(label.as_str()), 1060 1061 UntypedExpr::Tuple { elements, location } => self.tuple(elements, location), 1062 1063 UntypedExpr::BitArray { 1064 segments, location, .. 1065 } => { 1066 let segment_docs = segments 1067 .iter() 1068 .map(|segment| bit_array_segment(segment, |e| self.bit_array_segment_expr(e))) 1069 .collect_vec(); 1070 1071 self.bit_array( 1072 segment_docs, 1073 segments.iter().all(|s| s.value.is_simple_constant()), 1074 location, 1075 ) 1076 } 1077 UntypedExpr::RecordUpdate { 1078 constructor, 1079 record, 1080 arguments: args, 1081 location, 1082 .. 1083 } => self.record_update(constructor, record, args, location), 1084 }; 1085 commented(document, comments) 1086 } 1087 1088 fn string<'a>(&self, string: &'a EcoString) -> Document<'a> { 1089 let doc = string.to_doc().surround("\"", "\""); 1090 if string.contains('\n') { 1091 doc.force_break() 1092 } else { 1093 doc 1094 } 1095 } 1096 1097 fn bin_op_string<'a>(&self, string: &'a EcoString) -> Document<'a> { 1098 let lines = string.split('\n').collect_vec(); 1099 match lines.as_slice() { 1100 [] | [_] => string.to_doc().surround("\"", "\""), 1101 [first_line, lines @ ..] => { 1102 let mut doc = docvec!["\"", first_line]; 1103 for line in lines { 1104 doc = doc 1105 .append(pretty::line().set_nesting(0)) 1106 .append(line.to_doc()) 1107 } 1108 doc.append("\"".to_doc()).group() 1109 } 1110 } 1111 } 1112 1113 fn float<'a>(&self, value: &'a str) -> Document<'a> { 1114 // Create parts 1115 let mut parts = value.split('.'); 1116 let integer_part = parts.next().unwrap_or_default(); 1117 // floating point part 1118 let fp_part = parts.next().unwrap_or_default(); 1119 let integer_doc = self.underscore_integer_string(integer_part); 1120 let dot_doc = ".".to_doc(); 1121 1122 // Split fp_part into a regular fractional and maybe a scientific part 1123 let (fp_part_fractional, fp_part_scientific) = fp_part.split_at( 1124 fp_part 1125 .chars() 1126 .position(|ch| ch == 'e') 1127 .unwrap_or(fp_part.len()), 1128 ); 1129 1130 // Trim right any consequtive '0's 1131 let mut fp_part_fractional = fp_part_fractional.trim_end_matches('0').to_string(); 1132 // If there is no fractional part left, add a '0', thus that 1. becomes 1.0 etc. 1133 if fp_part_fractional.is_empty() { 1134 fp_part_fractional.push('0'); 1135 } 1136 let fp_doc = fp_part_fractional.chars().collect::<EcoString>(); 1137 1138 integer_doc 1139 .append(dot_doc) 1140 .append(fp_doc) 1141 .append(fp_part_scientific) 1142 } 1143 1144 fn int<'a>(&self, value: &'a str) -> Document<'a> { 1145 if value.starts_with("0x") || value.starts_with("0b") || value.starts_with("0o") { 1146 return value.to_doc(); 1147 } 1148 1149 self.underscore_integer_string(value) 1150 } 1151 1152 fn underscore_integer_string<'a>(&self, value: &'a str) -> Document<'a> { 1153 let underscore_ch = '_'; 1154 let minus_ch = '-'; 1155 1156 let len = value.len(); 1157 let underscore_ch_cnt = value.matches(underscore_ch).count(); 1158 let reformat_watershed = if value.starts_with(minus_ch) { 6 } else { 5 }; 1159 let insert_underscores = (len - underscore_ch_cnt) >= reformat_watershed; 1160 1161 let mut new_value = String::new(); 1162 let mut j = 0; 1163 for (i, ch) in value.chars().rev().enumerate() { 1164 if ch == '_' { 1165 continue; 1166 } 1167 1168 if insert_underscores && i != 0 && ch != minus_ch && i < len && j % 3 == 0 { 1169 new_value.push(underscore_ch); 1170 } 1171 new_value.push(ch); 1172 1173 j += 1; 1174 } 1175 1176 new_value.chars().rev().collect::<EcoString>().to_doc() 1177 } 1178 1179 fn pattern_constructor<'a>( 1180 &mut self, 1181 name: &'a str, 1182 args: &'a [CallArg<UntypedPattern>], 1183 module: &'a Option<(EcoString, SrcSpan)>, 1184 spread: Option<SrcSpan>, 1185 location: &SrcSpan, 1186 ) -> Document<'a> { 1187 fn is_breakable(expr: &UntypedPattern) -> bool { 1188 match expr { 1189 Pattern::Tuple { .. } | Pattern::List { .. } | Pattern::BitArray { .. } => true, 1190 Pattern::Constructor { 1191 arguments: args, .. 1192 } => !args.is_empty(), 1193 _ => false, 1194 } 1195 } 1196 1197 let name = match module { 1198 Some((m, _)) => m.to_doc().append(".").append(name), 1199 None => name.to_doc(), 1200 }; 1201 1202 if args.is_empty() && spread.is_some() { 1203 name.append("(..)") 1204 } else if args.is_empty() { 1205 name 1206 } else if spread.is_some() { 1207 let args = args.iter().map(|a| self.pattern_call_arg(a)).collect_vec(); 1208 name.append(self.wrap_args_with_spread(args, location.end)) 1209 } else { 1210 match args { 1211 [arg] if is_breakable(&arg.value) => name 1212 .append("(") 1213 .append(self.pattern_call_arg(arg)) 1214 .append(")") 1215 .group(), 1216 1217 _ => { 1218 let args = args.iter().map(|a| self.pattern_call_arg(a)).collect_vec(); 1219 name.append(self.wrap_args(args, location.end)).group() 1220 } 1221 } 1222 } 1223 } 1224 1225 fn call<'a>( 1226 &mut self, 1227 fun: &'a UntypedExpr, 1228 args: &'a [CallArg<UntypedExpr>], 1229 location: &SrcSpan, 1230 ) -> Document<'a> { 1231 let expr = match fun { 1232 UntypedExpr::Placeholder { .. } => panic!("Placeholders should not be formatted"), 1233 1234 UntypedExpr::PipeLine { .. } => break_block(self.expr(fun)), 1235 1236 UntypedExpr::BinOp { .. } 1237 | UntypedExpr::Int { .. } 1238 | UntypedExpr::Float { .. } 1239 | UntypedExpr::String { .. } 1240 | UntypedExpr::Block { .. } 1241 | UntypedExpr::Var { .. } 1242 | UntypedExpr::Fn { .. } 1243 | UntypedExpr::List { .. } 1244 | UntypedExpr::Call { .. } 1245 | UntypedExpr::Case { .. } 1246 | UntypedExpr::FieldAccess { .. } 1247 | UntypedExpr::Tuple { .. } 1248 | UntypedExpr::TupleIndex { .. } 1249 | UntypedExpr::Todo { .. } 1250 | UntypedExpr::Echo { .. } 1251 | UntypedExpr::Panic { .. } 1252 | UntypedExpr::BitArray { .. } 1253 | UntypedExpr::RecordUpdate { .. } 1254 | UntypedExpr::NegateBool { .. } 1255 | UntypedExpr::NegateInt { .. } => self.expr(fun), 1256 }; 1257 1258 let arity = args.len(); 1259 self.append_inlinable_wrapped_args( 1260 expr, 1261 args, 1262 location, 1263 |arg| &arg.value, 1264 |self_, arg| self_.call_arg(arg, arity), 1265 ) 1266 } 1267 1268 fn tuple<'a>(&mut self, elements: &'a [UntypedExpr], location: &SrcSpan) -> Document<'a> { 1269 if elements.is_empty() { 1270 // We take all comments that come _before_ the end of the tuple, 1271 // that is all comments that are inside "#(" and ")", if there's 1272 // any comment we want to put it inside the empty tuple! 1273 return match printed_comments(self.pop_comments(location.end), false) { 1274 None => "#()".to_doc(), 1275 Some(comments) => "#(" 1276 .to_doc() 1277 .append(break_("", "").nest(INDENT)) 1278 .append(comments) 1279 .append(break_("", "")) 1280 .append(")") 1281 // vvv We want to make sure the comments are on a separate 1282 // line from the opening and closing parentheses so we 1283 // force the breaks to be split on newlines. 1284 .force_break(), 1285 }; 1286 } 1287 1288 self.append_inlinable_wrapped_args( 1289 "#".to_doc(), 1290 elements, 1291 location, 1292 |e| e, 1293 |self_, e| self_.comma_separated_item(e, elements.len()), 1294 ) 1295 } 1296 1297 // Appends to the given docs a comma-separated list of documents wrapped by 1298 // parentheses. If the last item of the argument list is splittable the 1299 // resulting document will try to first split that before splitting all the 1300 // other arguments. 1301 // This is used for function calls and tuples. 1302 fn append_inlinable_wrapped_args<'a, 'b, T, ToExpr, ToDoc>( 1303 &mut self, 1304 doc: Document<'a>, 1305 values: &'b [T], 1306 location: &SrcSpan, 1307 to_expr: ToExpr, 1308 to_doc: ToDoc, 1309 ) -> Document<'a> 1310 where 1311 T: HasLocation, 1312 ToExpr: Fn(&T) -> &UntypedExpr, 1313 ToDoc: Fn(&mut Self, &'b T) -> Document<'a>, 1314 { 1315 match init_and_last(values) { 1316 Some((initial_values, last_value)) 1317 if is_breakable_argument(to_expr(last_value), values.len()) 1318 && !self.any_comments(last_value.location().start) => 1319 { 1320 let mut docs = initial_values 1321 .iter() 1322 .map(|value| to_doc(self, value)) 1323 .collect_vec(); 1324 1325 let last_value_doc = to_doc(self, last_value) 1326 .group() 1327 .next_break_fits(NextBreakFitsMode::Enabled); 1328 1329 docs.append(&mut vec![last_value_doc]); 1330 1331 doc.append(self.wrap_function_call_args(docs, location)) 1332 .next_break_fits(NextBreakFitsMode::Disabled) 1333 .group() 1334 } 1335 1336 Some(_) | None => { 1337 let docs = values.iter().map(|value| to_doc(self, value)).collect_vec(); 1338 doc.append(self.wrap_function_call_args(docs, location)) 1339 .group() 1340 } 1341 } 1342 } 1343 1344 pub fn case<'a>( 1345 &mut self, 1346 subjects: &'a [UntypedExpr], 1347 clauses: &'a [UntypedClause], 1348 location: &'a SrcSpan, 1349 ) -> Document<'a> { 1350 let subjects_doc = break_("case", "case ") 1351 .append(join( 1352 subjects.iter().map(|s| self.expr(s).group()), 1353 break_(",", ", "), 1354 )) 1355 .nest(INDENT) 1356 .append(break_("", " ")) 1357 .append("{") 1358 .next_break_fits(NextBreakFitsMode::Disabled) 1359 .group(); 1360 1361 let clauses_doc = concat( 1362 clauses 1363 .iter() 1364 .enumerate() 1365 .map(|(i, c)| self.clause(c, i as u32).group()), 1366 ); 1367 1368 // We get all remaining comments that come before the case's closing 1369 // bracket. If there's any we add those before the closing bracket 1370 // instead of moving those out of the case expression. 1371 // Otherwise those would be moved out of the case expression. 1372 let comments = self.pop_comments(location.end); 1373 let closing_bracket = match printed_comments(comments, false) { 1374 None => docvec![line(), "}"], 1375 Some(comment) => docvec![line(), comment] 1376 .nest(INDENT) 1377 .append(line()) 1378 .append("}"), 1379 }; 1380 1381 subjects_doc 1382 .append(line().append(clauses_doc).nest(INDENT)) 1383 .append(closing_bracket) 1384 .force_break() 1385 } 1386 1387 pub fn record_update<'a>( 1388 &mut self, 1389 constructor: &'a UntypedExpr, 1390 record: &'a RecordBeingUpdated, 1391 args: &'a [UntypedRecordUpdateArg], 1392 location: &SrcSpan, 1393 ) -> Document<'a> { 1394 let constructor_doc: Document<'a> = self.expr(constructor); 1395 let pieces = std::iter::once(RecordUpdatePiece::Record(record)) 1396 .chain(args.iter().map(RecordUpdatePiece::Argument)) 1397 .collect_vec(); 1398 1399 self.append_inlinable_wrapped_args( 1400 constructor_doc, 1401 &pieces, 1402 location, 1403 |arg| match arg { 1404 RecordUpdatePiece::Argument(arg) => &arg.value, 1405 RecordUpdatePiece::Record(record) => record.base.as_ref(), 1406 }, 1407 |this, arg| match arg { 1408 RecordUpdatePiece::Argument(arg) => this.record_update_arg(arg), 1409 RecordUpdatePiece::Record(record) => { 1410 let comments = this.pop_comments(record.base.location().start); 1411 commented("..".to_doc().append(this.expr(&record.base)), comments) 1412 } 1413 }, 1414 ) 1415 } 1416 1417 pub fn bin_op<'a>( 1418 &mut self, 1419 name: &'a BinOp, 1420 left: &'a UntypedExpr, 1421 right: &'a UntypedExpr, 1422 nest_steps: bool, 1423 ) -> Document<'a> { 1424 let left_side = self.bin_op_side(name, left, nest_steps); 1425 1426 let comments = self.pop_comments(right.start_byte_index()); 1427 let name_doc = break_("", " ").append(commented(name.to_doc(), comments)); 1428 1429 let right_side = self.bin_op_side(name, right, nest_steps); 1430 1431 left_side 1432 .append(if nest_steps { 1433 name_doc.nest(INDENT) 1434 } else { 1435 name_doc 1436 }) 1437 .append(" ") 1438 .append(right_side) 1439 } 1440 1441 fn bin_op_side<'a>( 1442 &mut self, 1443 operator: &'a BinOp, 1444 side: &'a UntypedExpr, 1445 nest_steps: bool, 1446 ) -> Document<'a> { 1447 let side_doc = match side { 1448 UntypedExpr::String { value, .. } => self.bin_op_string(value), 1449 UntypedExpr::BinOp { 1450 name, left, right, .. 1451 } => self.bin_op(name, left, right, nest_steps), 1452 _ => self.expr(side), 1453 }; 1454 match side.bin_op_name() { 1455 // In case the other side is a binary operation as well and it can 1456 // be grouped together with the current binary operation, the two 1457 // docs are simply concatenated, so that they will end up in the 1458 // same group and the formatter will try to keep those on a single 1459 // line. 1460 Some(side_name) if side_name.can_be_grouped_with(operator) => side_doc, 1461 // In case the binary operations cannot be grouped together the 1462 // other side is treated as a group on its own so that it can be 1463 // broken independently of other pieces of the binary operations 1464 // chain. 1465 _ => self.operator_side( 1466 side_doc.group(), 1467 operator.precedence(), 1468 side.bin_op_precedence(), 1469 ), 1470 } 1471 } 1472 1473 pub fn operator_side<'a>(&self, doc: Document<'a>, op: u8, side: u8) -> Document<'a> { 1474 if op > side { 1475 wrap_block(doc).group() 1476 } else { 1477 doc 1478 } 1479 } 1480 1481 fn spans_multiple_lines(&self, start: u32, end: u32) -> bool { 1482 self.new_lines 1483 .binary_search_by(|newline| { 1484 if *newline <= start { 1485 Ordering::Less 1486 } else if *newline >= end { 1487 Ordering::Greater 1488 } else { 1489 // If the newline is in between the pipe start and end 1490 // then we've found it! 1491 Ordering::Equal 1492 } 1493 }) 1494 // If we couldn't find any newline between the start and end of 1495 // the pipeline then we will try and keep it on a single line. 1496 .is_ok() 1497 } 1498 1499 fn pipeline<'a>( 1500 &mut self, 1501 expressions: &'a Vec1<UntypedExpr>, 1502 nest_pipe: bool, 1503 ) -> Document<'a> { 1504 let mut docs = Vec::with_capacity(expressions.len() * 3); 1505 let first = expressions.first(); 1506 let first_precedence = first.bin_op_precedence(); 1507 let first = self.expr(first).group(); 1508 docs.push(self.operator_side(first, 5, first_precedence)); 1509 1510 let pipeline_start = expressions.first().location().start; 1511 let pipeline_end = expressions.last().location().end; 1512 let try_to_keep_on_one_line = !self.spans_multiple_lines(pipeline_start, pipeline_end); 1513 1514 for expr in expressions.iter().skip(1) { 1515 let comments = self.pop_comments(expr.location().start); 1516 let doc = match expr { 1517 UntypedExpr::Fn { kind, body, .. } if kind.is_capture() => { 1518 self.fn_capture(body, FnCapturePosition::RightHandSideOfPipe) 1519 } 1520 _ => self.expr(expr), 1521 }; 1522 let doc = if nest_pipe { doc.nest(INDENT) } else { doc }; 1523 let space = if try_to_keep_on_one_line { 1524 break_("", " ") 1525 } else { 1526 line() 1527 }; 1528 let pipe = space.append(commented("|> ".to_doc(), comments)); 1529 let pipe = if nest_pipe { pipe.nest(INDENT) } else { pipe }; 1530 docs.push(pipe); 1531 docs.push(self.operator_side(doc, 4, expr.bin_op_precedence())); 1532 } 1533 1534 if try_to_keep_on_one_line { 1535 docs.to_doc() 1536 } else { 1537 docs.to_doc().force_break() 1538 } 1539 } 1540 1541 fn fn_capture<'a>( 1542 &mut self, 1543 call: &'a [UntypedStatement], 1544 position: FnCapturePosition, 1545 ) -> Document<'a> { 1546 // The body of a capture being multiple statements shouldn't be possible... 1547 if call.len() != 1 { 1548 panic!("Function capture found not to have a single statement call"); 1549 } 1550 1551 let Some(Statement::Expression(UntypedExpr::Call { 1552 fun, 1553 arguments, 1554 location, 1555 })) = call.first() 1556 else { 1557 // The body of a capture being not a fn shouldn't be possible... 1558 panic!("Function capture body found not to be a call in the formatter") 1559 }; 1560 1561 match (position, arguments.as_slice()) { 1562 // The capture has a single unlabelled hole: 1563 // 1564 // wibble |> wobble(_) 1565 // list.map([], wobble(_)) 1566 // 1567 // We want these to become: 1568 // 1569 // wibble |> wobble 1570 // list.map([], wobble) 1571 // 1572 (FnCapturePosition::RightHandSideOfPipe | FnCapturePosition::EverywhereElse, [arg]) 1573 if arg.is_capture_hole() && arg.label.is_none() => 1574 { 1575 self.expr(fun) 1576 } 1577 1578 // The capture is on the right hand side of a pipe and its first 1579 // argument it an unlabelled capture hole: 1580 // 1581 // wibble |> wobble(_, woo) 1582 // 1583 // We want it to become: 1584 // 1585 // wibble |> wobble(woo) 1586 // 1587 (FnCapturePosition::RightHandSideOfPipe, [arg, rest @ ..]) 1588 if arg.is_capture_hole() && arg.label.is_none() => 1589 { 1590 let expr = self.expr(fun); 1591 let arity = rest.len(); 1592 self.append_inlinable_wrapped_args( 1593 expr, 1594 rest, 1595 location, 1596 |arg| &arg.value, 1597 |self_, arg| self_.call_arg(arg, arity), 1598 ) 1599 } 1600 1601 // In all other cases we print it like a regular function call 1602 // without changing it. 1603 // 1604 ( 1605 FnCapturePosition::RightHandSideOfPipe | FnCapturePosition::EverywhereElse, 1606 arguments, 1607 ) => { 1608 let expr = self.expr(fun); 1609 let arity = arguments.len(); 1610 self.append_inlinable_wrapped_args( 1611 expr, 1612 arguments, 1613 location, 1614 |arg| &arg.value, 1615 |self_, arg| self_.call_arg(arg, arity), 1616 ) 1617 } 1618 } 1619 } 1620 1621 pub fn record_constructor<'a, A>( 1622 &mut self, 1623 constructor: &'a RecordConstructor<A>, 1624 ) -> Document<'a> { 1625 let comments = self.pop_comments(constructor.location.start); 1626 let doc_comments = self.doc_comments(constructor.location.start); 1627 let attributes = AttributesPrinter::new() 1628 .set_deprecation(&constructor.deprecation) 1629 .to_doc(); 1630 1631 let doc = if constructor.arguments.is_empty() { 1632 if self.any_comments(constructor.location.end) { 1633 attributes 1634 .append(constructor.name.as_str().to_doc()) 1635 .append(self.wrap_args(vec![], constructor.location.end)) 1636 .group() 1637 } else { 1638 attributes.append(constructor.name.as_str().to_doc()) 1639 } 1640 } else { 1641 let args = constructor 1642 .arguments 1643 .iter() 1644 .map( 1645 |RecordConstructorArg { 1646 label, 1647 ast, 1648 location, 1649 .. 1650 }| { 1651 let arg_comments = self.pop_comments(location.start); 1652 let arg = match label { 1653 Some((_, l)) => l.to_doc().append(": ").append(self.type_ast(ast)), 1654 None => self.type_ast(ast), 1655 }; 1656 1657 commented( 1658 self.doc_comments(location.start).append(arg).group(), 1659 arg_comments, 1660 ) 1661 }, 1662 ) 1663 .collect_vec(); 1664 1665 attributes 1666 .append(constructor.name.as_str().to_doc()) 1667 .append(self.wrap_args(args, constructor.location.end).group()) 1668 }; 1669 1670 commented(doc_comments.append(doc).group(), comments) 1671 } 1672 1673 pub fn custom_type<'a, A>(&mut self, ct: &'a CustomType<A>) -> Document<'a> { 1674 let _ = self.pop_empty_lines(ct.location.end); 1675 1676 let attributes = AttributesPrinter::new() 1677 .set_deprecation(&ct.deprecation) 1678 .set_internal(ct.publicity) 1679 .to_doc(); 1680 1681 let doc = attributes 1682 .append(pub_(ct.publicity)) 1683 .append(if ct.opaque { "opaque type " } else { "type " }) 1684 .append(if ct.parameters.is_empty() { 1685 ct.name.clone().to_doc() 1686 } else { 1687 let args = ct.parameters.iter().map(|(_, e)| e.to_doc()).collect_vec(); 1688 ct.name 1689 .clone() 1690 .to_doc() 1691 .append(self.wrap_args(args, ct.location.end)) 1692 .group() 1693 }); 1694 1695 if ct.constructors.is_empty() { 1696 return doc; 1697 } 1698 let doc = doc.append(" {"); 1699 1700 let inner = concat(ct.constructors.iter().map(|c| { 1701 if self.pop_empty_lines(c.location.start) { 1702 lines(2) 1703 } else { 1704 line() 1705 } 1706 .append(self.record_constructor(c)) 1707 })); 1708 1709 // Add any trailing comments 1710 let inner = match printed_comments(self.pop_comments(ct.end_position), false) { 1711 Some(comments) => inner.append(line()).append(comments), 1712 None => inner, 1713 } 1714 .nest(INDENT) 1715 .group(); 1716 1717 doc.append(inner).append(line()).append("}") 1718 } 1719 1720 pub fn docs_opaque_custom_type<'a>( 1721 &mut self, 1722 publicity: Publicity, 1723 name: &'a str, 1724 args: &'a [SpannedString], 1725 location: &'a SrcSpan, 1726 ) -> Document<'a> { 1727 let _ = self.pop_empty_lines(location.start); 1728 let attributes = AttributesPrinter::new().set_internal(publicity).to_doc(); 1729 1730 attributes 1731 .append(pub_(publicity)) 1732 .append("opaque type ") 1733 .append(if args.is_empty() { 1734 name.to_doc() 1735 } else { 1736 let args = args.iter().map(|(_, e)| e.to_doc()).collect_vec(); 1737 name.to_doc().append(self.wrap_args(args, location.end)) 1738 }) 1739 } 1740 1741 pub fn docs_fn_signature<'a>( 1742 &mut self, 1743 publicity: Publicity, 1744 name: &'a str, 1745 args: &'a [TypedArg], 1746 return_type: Arc<Type>, 1747 location: &SrcSpan, 1748 ) -> Document<'a> { 1749 let mut printer = type_::pretty::Printer::new(); 1750 let fn_args = self.docs_fn_args(args, &mut printer, location); 1751 let return_type = printer.print(&return_type); 1752 1753 let attributes = AttributesPrinter::new().set_internal(publicity); 1754 docvec![ 1755 attributes, 1756 pub_(publicity), 1757 "fn ", 1758 name, 1759 fn_args, 1760 " -> ", 1761 return_type 1762 ] 1763 } 1764 1765 // Will always print the types, even if they were implicit in the original source 1766 fn docs_fn_args<'a>( 1767 &mut self, 1768 args: &'a [TypedArg], 1769 printer: &mut type_::pretty::Printer, 1770 location: &SrcSpan, 1771 ) -> Document<'a> { 1772 let args = args 1773 .iter() 1774 .map(|arg| { 1775 self.docs_fn_arg_name(arg) 1776 .append(": ".to_doc().append(printer.print(&arg.type_))) 1777 .group() 1778 }) 1779 .collect_vec(); 1780 self.wrap_args(args, location.end) 1781 } 1782 1783 fn docs_fn_arg_name<'a>(&mut self, arg: &'a TypedArg) -> Document<'a> { 1784 match &arg.names { 1785 ArgNames::Named { name, .. } => name.to_doc(), 1786 ArgNames::NamedLabelled { label, name, .. } => docvec![label, " ", name], 1787 // We remove the underscore from discarded function arguments since we don't want to 1788 // expose this kind of detail: https://github.com/gleam-lang/gleam/issues/2561 1789 ArgNames::Discard { name, .. } => match name.strip_prefix('_').unwrap_or(name) { 1790 "" => "arg".to_doc(), 1791 name => name.to_doc(), 1792 }, 1793 ArgNames::LabelledDiscard { label, name, .. } => { 1794 docvec![label, " ", name.strip_prefix('_').unwrap_or(name).to_doc()] 1795 } 1796 } 1797 } 1798 1799 fn call_arg<'a>(&mut self, arg: &'a CallArg<UntypedExpr>, arity: usize) -> Document<'a> { 1800 self.format_call_arg(arg, expr_call_arg_formatting, |this, value| { 1801 this.comma_separated_item(value, arity) 1802 }) 1803 } 1804 1805 fn format_call_arg<'a, A, F, G>( 1806 &mut self, 1807 arg: &'a CallArg<A>, 1808 figure_formatting: F, 1809 format_value: G, 1810 ) -> Document<'a> 1811 where 1812 F: Fn(&'a CallArg<A>) -> CallArgFormatting<'a, A>, 1813 G: Fn(&mut Self, &'a A) -> Document<'a>, 1814 { 1815 match figure_formatting(arg) { 1816 CallArgFormatting::Unlabelled(value) => format_value(self, value), 1817 CallArgFormatting::ShorthandLabelled(label) => { 1818 let comments = self.pop_comments(arg.location.start); 1819 let label = label.as_ref().to_doc().append(":"); 1820 commented(label, comments) 1821 } 1822 CallArgFormatting::Labelled(label, value) => { 1823 let comments = self.pop_comments(arg.location.start); 1824 let label = label.as_ref().to_doc().append(": "); 1825 let value = format_value(self, value); 1826 commented(label, comments).append(value) 1827 } 1828 } 1829 } 1830 1831 fn record_update_arg<'a>(&mut self, arg: &'a UntypedRecordUpdateArg) -> Document<'a> { 1832 let comments = self.pop_comments(arg.location.start); 1833 match arg { 1834 // Argument supplied with a label shorthand. 1835 _ if arg.uses_label_shorthand() => { 1836 commented(arg.label.as_str().to_doc().append(":"), comments) 1837 } 1838 // Labelled argument. 1839 _ => { 1840 let doc = arg 1841 .label 1842 .as_str() 1843 .to_doc() 1844 .append(": ") 1845 .append(self.expr(&arg.value)) 1846 .group(); 1847 1848 if arg.value.is_binop() || arg.value.is_pipeline() { 1849 commented(doc, comments).nest(INDENT) 1850 } else { 1851 commented(doc, comments) 1852 } 1853 } 1854 } 1855 } 1856 1857 fn tuple_index<'a>(&mut self, tuple: &'a UntypedExpr, index: u64) -> Document<'a> { 1858 match tuple { 1859 // In case we have a block with a single variable tuple access we 1860 // remove that redundat wrapper: 1861 // 1862 // {tuple.1}.0 becomes 1863 // tuple.1.0 1864 // 1865 UntypedExpr::Block { statements, .. } => match statements.as_slice() { 1866 [Statement::Expression(tuple @ UntypedExpr::TupleIndex { tuple: inner, .. })] 1867 // We can't apply this change if the inner thing is a 1868 // literal tuple because the compiler cannot currently parse 1869 // it: `#(1, #(2, 3)).1.0` is a syntax error at the moment. 1870 if !inner.is_tuple() => 1871 { 1872 self.expr(tuple) 1873 } 1874 _ => self.expr(tuple), 1875 }, 1876 _ => self.expr(tuple), 1877 } 1878 .append(".") 1879 .append(index) 1880 } 1881 1882 fn case_clause_value<'a>(&mut self, expr: &'a UntypedExpr) -> Document<'a> { 1883 match expr { 1884 UntypedExpr::Fn { .. } 1885 | UntypedExpr::List { .. } 1886 | UntypedExpr::Tuple { .. } 1887 | UntypedExpr::BitArray { .. } => { 1888 let expression_comments = self.pop_comments(expr.location().start); 1889 let expression_doc = self.expr(expr); 1890 match printed_comments(expression_comments, true) { 1891 Some(comments) => line().append(comments).append(expression_doc).nest(INDENT), 1892 None => " ".to_doc().append(expression_doc), 1893 } 1894 } 1895 1896 UntypedExpr::Case { .. } => line().append(self.expr(expr)).nest(INDENT), 1897 1898 UntypedExpr::Block { 1899 statements, 1900 location, 1901 .. 1902 } => " ".to_doc().append(self.block(location, statements, true)), 1903 1904 _ => break_("", " ").append(self.expr(expr).group()).nest(INDENT), 1905 } 1906 .next_break_fits(NextBreakFitsMode::Disabled) 1907 .group() 1908 } 1909 1910 fn assigned_value<'a>(&mut self, expr: &'a UntypedExpr) -> Document<'a> { 1911 match expr { 1912 UntypedExpr::Case { .. } => " ".to_doc().append(self.expr(expr)).group(), 1913 _ => self.case_clause_value(expr), 1914 } 1915 } 1916 1917 fn clause<'a>(&mut self, clause: &'a UntypedClause, index: u32) -> Document<'a> { 1918 let space_before = self.pop_empty_lines(clause.location.start); 1919 let comments = self.pop_comments(clause.location.start); 1920 1921 let clause_doc = match &clause.guard { 1922 None => self.alternative_patterns(clause), 1923 Some(guard) => self 1924 .alternative_patterns(clause) 1925 .append(break_("", " ").nest(INDENT)) 1926 .append("if ") 1927 .append(self.clause_guard(guard).group().nest(INDENT)), 1928 }; 1929 1930 // In case there's a guard or multiple subjects, if we decide to break 1931 // the patterns on multiple lines we also want the arrow to end up on 1932 // its own line to improve legibility. 1933 // 1934 // This looks like this: 1935 // ```gleam 1936 // case wibble, wobble { 1937 // Wibble(_), // pretend this goes over the line limit 1938 // Wobble(_) 1939 // -> todo 1940 // // Notice how the arrow is broken on its own line, the same goes 1941 // // for patterns with `if` guards. 1942 // } 1943 // ``` 1944 1945 let has_guard = clause.guard.is_some(); 1946 let has_multiple_subjects = clause.pattern.len() > 1; 1947 let arrow_break = if has_guard || has_multiple_subjects { 1948 break_("", " ") 1949 } else { 1950 " ".to_doc() 1951 }; 1952 1953 let clause_doc = clause_doc 1954 .append(arrow_break) 1955 .group() 1956 .append("->") 1957 .append(self.case_clause_value(&clause.then).group()) 1958 .group(); 1959 1960 let clause_doc = match printed_comments(comments, false) { 1961 Some(comments) => comments.append(line()).append(clause_doc), 1962 None => clause_doc, 1963 }; 1964 1965 if index == 0 { 1966 clause_doc 1967 } else if space_before { 1968 lines(2).append(clause_doc) 1969 } else { 1970 line().append(clause_doc) 1971 } 1972 } 1973 1974 fn alternative_patterns<'a>(&mut self, clause: &'a UntypedClause) -> Document<'a> { 1975 let has_guard = clause.guard.is_some(); 1976 let has_multiple_subjects = clause.pattern.len() > 1; 1977 1978 // In case there's an `if` guard but no multiple subjects we want to add 1979 // additional indentation before the vartical bar separating alternative 1980 // patterns `|`. 1981 // We're not adding the indentation if there's multiple subjects as that 1982 // would make things harder to read, aligning the vertical bar with the 1983 // different subjects: 1984 // ``` 1985 // case wibble, wobble { 1986 // Wibble, 1987 // Wobble 1988 // | Wibble, // <- we don't want this indentation! 1989 // Wobble -> todo 1990 // } 1991 // ``` 1992 let alternatives_separator = if has_guard && !has_multiple_subjects { 1993 break_("", " ").nest(INDENT).append("| ") 1994 } else { 1995 break_("", " ").append("| ") 1996 }; 1997 1998 let alternative_patterns = std::iter::once(&clause.pattern) 1999 .chain(&clause.alternative_patterns) 2000 .enumerate() 2001 .map(|(alternative_index, p)| { 2002 // Here `p` is a single pattern that can be comprised of 2003 // multiple subjects. 2004 // ```gleam 2005 // case wibble, wobble { 2006 // True, False 2007 // //^^^^^^^^^^^ This is a single pattern with multiple subjects 2008 // | _, _ -> todo 2009 // } 2010 // ``` 2011 let is_first_alternative = alternative_index == 0; 2012 let subject_docs = p.iter().enumerate().map(|(subject_index, subject)| { 2013 // There's a small catch in turning each subject into a document. 2014 // Sadly we can't simply call `self.pattern` on each subject and 2015 // then nest each one in case it gets broken. 2016 // The first ever pattern that appears in a case clause (that is 2017 // the first subject of the first alternative) must not be nested 2018 // further; otherwise, when broken, it would have 2 extra spaces 2019 // of indentation: https://github.com/gleam-lang/gleam/issues/2940. 2020 let is_first_subject = subject_index == 0; 2021 let is_first_pattern_of_clause = is_first_subject && is_first_alternative; 2022 let subject_doc = self.pattern(subject); 2023 if is_first_pattern_of_clause { 2024 subject_doc 2025 } else { 2026 subject_doc.nest(INDENT) 2027 } 2028 }); 2029 // We join all subjects with a breakable comma (that's also 2030 // going to be nested) and make the subjects into a group to 2031 // make sure the formatter tries to keep them on a single line. 2032 join(subject_docs, break_(",", ", ").nest(INDENT)).group() 2033 }); 2034 // Last, we make sure that the formatter tries to keep each 2035 // alternative on a single line by making it a group! 2036 join(alternative_patterns, alternatives_separator).group() 2037 } 2038 2039 fn list<'a>( 2040 &mut self, 2041 elements: &'a [UntypedExpr], 2042 tail: Option<&'a UntypedExpr>, 2043 location: &SrcSpan, 2044 ) -> Document<'a> { 2045 if elements.is_empty() { 2046 return match tail { 2047 Some(tail) => self.expr(tail), 2048 // We take all comments that come _before_ the end of the list, 2049 // that is all comments that are inside "[" and "]", if there's 2050 // any comment we want to put it inside the empty list! 2051 None => match printed_comments(self.pop_comments(location.end), false) { 2052 None => "[]".to_doc(), 2053 Some(comments) => "[" 2054 .to_doc() 2055 .append(break_("", "").nest(INDENT)) 2056 .append(comments) 2057 .append(break_("", "")) 2058 .append("]") 2059 // vvv We want to make sure the comments are on a separate 2060 // line from the opening and closing brackets so we 2061 // force the breaks to be split on newlines. 2062 .force_break(), 2063 }, 2064 }; 2065 } 2066 2067 let comma = if tail.is_none() && elements.iter().all(UntypedExpr::is_simple_constant) { 2068 flex_break(",", ", ") 2069 } else { 2070 break_(",", ", ") 2071 }; 2072 2073 let list_size = elements.len() 2074 + match tail { 2075 Some(_) => 1, 2076 None => 0, 2077 }; 2078 2079 let elements = join( 2080 elements 2081 .iter() 2082 .map(|e| self.comma_separated_item(e, list_size)), 2083 comma, 2084 ) 2085 .next_break_fits(NextBreakFitsMode::Disabled); 2086 2087 let doc = break_("[", "[").append(elements); 2088 // We need to keep the last break aside and do not add it immediately 2089 // because in case there's a final comment before the closing square 2090 // bracket we want to add indentation (to just that break). Otherwise, 2091 // the final comment would be less indented than list's elements. 2092 let (doc, last_break) = match tail { 2093 None => (doc.nest(INDENT), break_(",", "")), 2094 2095 Some(tail) => { 2096 let comments = self.pop_comments(tail.location().start); 2097 let tail = commented(docvec!["..", self.expr(tail)], comments); 2098 ( 2099 doc.append(break_(",", ", ")).append(tail).nest(INDENT), 2100 break_("", ""), 2101 ) 2102 } 2103 }; 2104 2105 // We get all remaining comments that come before the list's closing 2106 // square bracket. 2107 // If there's any we add those before the closing square bracket instead 2108 // of moving those out of the list. 2109 // Otherwise those would be moved out of the list. 2110 let comments = self.pop_comments(location.end); 2111 match printed_comments(comments, false) { 2112 None => doc.append(last_break).append("]").group(), 2113 Some(comment) => doc 2114 .append(last_break.nest(INDENT)) 2115 // ^ See how here we're adding the missing indentation to the 2116 // final break so that the final comment is as indented as the 2117 // list's items. 2118 .append(comment) 2119 .append(line()) 2120 .append("]") 2121 .force_break(), 2122 } 2123 } 2124 2125 /// Pretty prints an expression to be used in a comma separated list; for 2126 /// example as a list item, a tuple item or as an argument of a function call. 2127 fn comma_separated_item<'a>( 2128 &mut self, 2129 expression: &'a UntypedExpr, 2130 siblings: usize, 2131 ) -> Document<'a> { 2132 // If there's more than one item in the comma separated list and there's a 2133 // pipeline or long binary chain, we want to indent those to make it 2134 // easier to tell where one item ends and the other starts. 2135 // Othewise we just print the expression as a normal expr. 2136 match expression { 2137 UntypedExpr::BinOp { 2138 name, left, right, .. 2139 } if siblings > 1 => { 2140 let comments = self.pop_comments(expression.start_byte_index()); 2141 let doc = self.bin_op(name, left, right, true).group(); 2142 commented(doc, comments) 2143 } 2144 UntypedExpr::PipeLine { expressions } if siblings > 1 => { 2145 let comments = self.pop_comments(expression.start_byte_index()); 2146 let doc = self.pipeline(expressions, true).group(); 2147 commented(doc, comments) 2148 } 2149 _ => self.expr(expression).group(), 2150 } 2151 } 2152 2153 fn pattern<'a>(&mut self, pattern: &'a UntypedPattern) -> Document<'a> { 2154 let comments = self.pop_comments(pattern.location().start); 2155 let doc = match pattern { 2156 Pattern::Int { value, .. } => self.int(value), 2157 2158 Pattern::Float { value, .. } => self.float(value), 2159 2160 Pattern::String { value, .. } => self.string(value), 2161 2162 Pattern::Variable { name, .. } => name.to_doc(), 2163 2164 Pattern::VarUsage { name, .. } => name.to_doc(), 2165 2166 Pattern::Assign { name, pattern, .. } => { 2167 self.pattern(pattern).append(" as ").append(name.as_str()) 2168 } 2169 2170 Pattern::Discard { name, .. } => name.to_doc(), 2171 2172 Pattern::List { elements, tail, .. } => self.list_pattern(elements, tail), 2173 2174 Pattern::Constructor { 2175 name, 2176 arguments: args, 2177 module, 2178 spread, 2179 location, 2180 .. 2181 } => self.pattern_constructor(name, args, module, *spread, location), 2182 2183 Pattern::Tuple { 2184 elements, location, .. 2185 } => { 2186 let args = elements 2187 .iter() 2188 .map(|element| self.pattern(element)) 2189 .collect_vec(); 2190 "#".to_doc() 2191 .append(self.wrap_args(args, location.end)) 2192 .group() 2193 } 2194 2195 Pattern::BitArray { 2196 segments, location, .. 2197 } => { 2198 let segment_docs = segments 2199 .iter() 2200 .map(|segment| bit_array_segment(segment, |pattern| self.pattern(pattern))) 2201 .collect_vec(); 2202 2203 self.bit_array(segment_docs, false, location) 2204 } 2205 2206 Pattern::StringPrefix { 2207 left_side_string: left, 2208 right_side_assignment: right, 2209 left_side_assignment: left_assign, 2210 .. 2211 } => { 2212 let left = self.string(left); 2213 let right = match right { 2214 AssignName::Variable(name) => name.to_doc(), 2215 AssignName::Discard(name) => name.to_doc(), 2216 }; 2217 match left_assign { 2218 Some((name, _)) => docvec![left, " as ", name, " <> ", right], 2219 None => docvec![left, " <> ", right], 2220 } 2221 } 2222 2223 Pattern::Invalid { .. } => panic!("invalid patterns can not be in an untyped ast"), 2224 }; 2225 commented(doc, comments) 2226 } 2227 2228 fn list_pattern<'a>( 2229 &mut self, 2230 elements: &'a [UntypedPattern], 2231 tail: &'a Option<Box<UntypedPattern>>, 2232 ) -> Document<'a> { 2233 if elements.is_empty() { 2234 return match tail { 2235 Some(tail) => self.pattern(tail), 2236 None => "[]".to_doc(), 2237 }; 2238 } 2239 let elements = join( 2240 elements.iter().map(|element| self.pattern(element)), 2241 break_(",", ", "), 2242 ); 2243 let doc = break_("[", "[").append(elements); 2244 match tail { 2245 None => doc.nest(INDENT).append(break_(",", "")), 2246 2247 Some(tail) => { 2248 let comments = self.pop_comments(tail.location().start); 2249 let tail = if tail.is_discard() { 2250 "..".to_doc() 2251 } else { 2252 docvec!["..", self.pattern(tail)] 2253 }; 2254 let tail = commented(tail, comments); 2255 doc.append(break_(",", ", ")) 2256 .append(tail) 2257 .nest(INDENT) 2258 .append(break_("", "")) 2259 } 2260 } 2261 .append("]") 2262 .group() 2263 } 2264 2265 fn pattern_call_arg<'a>(&mut self, arg: &'a CallArg<UntypedPattern>) -> Document<'a> { 2266 self.format_call_arg(arg, pattern_call_arg_formatting, |this, value| { 2267 this.pattern(value) 2268 }) 2269 } 2270 2271 pub fn clause_guard_bin_op<'a>( 2272 &mut self, 2273 name: &'a BinOp, 2274 left: &'a UntypedClauseGuard, 2275 right: &'a UntypedClauseGuard, 2276 ) -> Document<'a> { 2277 self.clause_guard_bin_op_side(name, left, left.precedence()) 2278 .append(break_("", " ")) 2279 .append(name.to_doc()) 2280 .append(" ") 2281 .append(self.clause_guard_bin_op_side(name, right, right.precedence() - 1)) 2282 } 2283 2284 fn clause_guard_bin_op_side<'a>( 2285 &mut self, 2286 name: &BinOp, 2287 side: &'a UntypedClauseGuard, 2288 // As opposed to `bin_op_side`, here we take the side precedence as an 2289 // argument instead of computing it ourselves. That's because 2290 // `clause_guard_bin_op` will reduce the precedence of any right side to 2291 // make sure the formatter doesn't remove any needed curly bracket. 2292 side_precedence: u8, 2293 ) -> Document<'a> { 2294 let side_doc = self.clause_guard(side); 2295 match side.bin_op_name() { 2296 // In case the other side is a binary operation as well and it can 2297 // be grouped together with the current binary operation, the two 2298 // docs are simply concatenated, so that they will end up in the 2299 // same group and the formatter will try to keep those on a single 2300 // line. 2301 Some(side_name) if side_name.can_be_grouped_with(name) => { 2302 self.operator_side(side_doc, name.precedence(), side_precedence) 2303 } 2304 // In case the binary operations cannot be grouped together the 2305 // other side is treated as a group on its own so that it can be 2306 // broken independently of other pieces of the binary operations 2307 // chain. 2308 _ => self.operator_side(side_doc.group(), name.precedence(), side_precedence), 2309 } 2310 } 2311 2312 fn clause_guard<'a>(&mut self, clause_guard: &'a UntypedClauseGuard) -> Document<'a> { 2313 match clause_guard { 2314 ClauseGuard::And { left, right, .. } => { 2315 self.clause_guard_bin_op(&BinOp::And, left, right) 2316 } 2317 ClauseGuard::Or { left, right, .. } => { 2318 self.clause_guard_bin_op(&BinOp::Or, left, right) 2319 } 2320 ClauseGuard::Equals { left, right, .. } => { 2321 self.clause_guard_bin_op(&BinOp::Eq, left, right) 2322 } 2323 ClauseGuard::NotEquals { left, right, .. } => { 2324 self.clause_guard_bin_op(&BinOp::NotEq, left, right) 2325 } 2326 ClauseGuard::GtInt { left, right, .. } => { 2327 self.clause_guard_bin_op(&BinOp::GtInt, left, right) 2328 } 2329 ClauseGuard::GtEqInt { left, right, .. } => { 2330 self.clause_guard_bin_op(&BinOp::GtEqInt, left, right) 2331 } 2332 ClauseGuard::LtInt { left, right, .. } => { 2333 self.clause_guard_bin_op(&BinOp::LtInt, left, right) 2334 } 2335 ClauseGuard::LtEqInt { left, right, .. } => { 2336 self.clause_guard_bin_op(&BinOp::LtEqInt, left, right) 2337 } 2338 ClauseGuard::GtFloat { left, right, .. } => { 2339 self.clause_guard_bin_op(&BinOp::GtFloat, left, right) 2340 } 2341 ClauseGuard::GtEqFloat { left, right, .. } => { 2342 self.clause_guard_bin_op(&BinOp::GtEqFloat, left, right) 2343 } 2344 ClauseGuard::LtFloat { left, right, .. } => { 2345 self.clause_guard_bin_op(&BinOp::LtFloat, left, right) 2346 } 2347 ClauseGuard::LtEqFloat { left, right, .. } => { 2348 self.clause_guard_bin_op(&BinOp::LtEqFloat, left, right) 2349 } 2350 ClauseGuard::AddInt { left, right, .. } => { 2351 self.clause_guard_bin_op(&BinOp::AddInt, left, right) 2352 } 2353 ClauseGuard::AddFloat { left, right, .. } => { 2354 self.clause_guard_bin_op(&BinOp::AddFloat, left, right) 2355 } 2356 ClauseGuard::SubInt { left, right, .. } => { 2357 self.clause_guard_bin_op(&BinOp::SubInt, left, right) 2358 } 2359 ClauseGuard::SubFloat { left, right, .. } => { 2360 self.clause_guard_bin_op(&BinOp::SubFloat, left, right) 2361 } 2362 ClauseGuard::MultInt { left, right, .. } => { 2363 self.clause_guard_bin_op(&BinOp::MultInt, left, right) 2364 } 2365 ClauseGuard::MultFloat { left, right, .. } => { 2366 self.clause_guard_bin_op(&BinOp::MultFloat, left, right) 2367 } 2368 ClauseGuard::DivInt { left, right, .. } => { 2369 self.clause_guard_bin_op(&BinOp::DivInt, left, right) 2370 } 2371 ClauseGuard::DivFloat { left, right, .. } => { 2372 self.clause_guard_bin_op(&BinOp::DivFloat, left, right) 2373 } 2374 ClauseGuard::RemainderInt { left, right, .. } => { 2375 self.clause_guard_bin_op(&BinOp::RemainderInt, left, right) 2376 } 2377 2378 ClauseGuard::Var { name, .. } => name.to_doc(), 2379 2380 ClauseGuard::TupleIndex { tuple, index, .. } => { 2381 self.clause_guard(tuple).append(".").append(*index).to_doc() 2382 } 2383 2384 ClauseGuard::FieldAccess { 2385 container, label, .. 2386 } => self 2387 .clause_guard(container) 2388 .append(".") 2389 .append(label) 2390 .to_doc(), 2391 2392 ClauseGuard::ModuleSelect { 2393 module_name, label, .. 2394 } => module_name.to_doc().append(".").append(label).to_doc(), 2395 2396 ClauseGuard::Constant(constant) => self.const_expr(constant), 2397 2398 ClauseGuard::Not { expression, .. } => docvec!["!", self.clause_guard(expression)], 2399 } 2400 } 2401 2402 fn constant_call_arg<'a, A, B>(&mut self, arg: &'a CallArg<Constant<A, B>>) -> Document<'a> { 2403 self.format_call_arg(arg, constant_call_arg_formatting, |this, value| { 2404 this.const_expr(value) 2405 }) 2406 } 2407 2408 fn negate_bool<'a>(&mut self, expr: &'a UntypedExpr) -> Document<'a> { 2409 match expr { 2410 UntypedExpr::BinOp { .. } => "!".to_doc().append(wrap_block(self.expr(expr))), 2411 _ => docvec!["!", self.expr(expr)], 2412 } 2413 } 2414 2415 fn negate_int<'a>(&mut self, expr: &'a UntypedExpr) -> Document<'a> { 2416 match expr { 2417 UntypedExpr::BinOp { .. } | UntypedExpr::NegateInt { .. } => { 2418 "- ".to_doc().append(self.expr(expr)) 2419 } 2420 2421 _ => docvec!["-", self.expr(expr)], 2422 } 2423 } 2424 2425 fn use_<'a>(&mut self, use_: &'a UntypedUse) -> Document<'a> { 2426 let comments = self.pop_comments(use_.location.start); 2427 2428 let call = if use_.call.is_call() { 2429 docvec![" ", self.expr(&use_.call)] 2430 } else { 2431 docvec![break_("", " "), self.expr(&use_.call)].nest(INDENT) 2432 } 2433 .group(); 2434 2435 let doc = if use_.assignments.is_empty() { 2436 docvec!["use <-", call] 2437 } else { 2438 let assignments = use_.assignments.iter().map(|use_assignment| { 2439 let pattern = self.pattern(&use_assignment.pattern); 2440 let annotation = use_assignment 2441 .annotation 2442 .as_ref() 2443 .map(|a| ": ".to_doc().append(self.type_ast(a))); 2444 2445 pattern.append(annotation).group() 2446 }); 2447 let assignments = Itertools::intersperse(assignments, break_(",", ", ")); 2448 let left = ["use".to_doc(), break_("", " ")] 2449 .into_iter() 2450 .chain(assignments); 2451 let left = concat(left).nest(INDENT).append(break_("", " ")).group(); 2452 docvec![left, "<-", call].group() 2453 }; 2454 2455 commented(doc, comments) 2456 } 2457 2458 fn bit_array<'a>( 2459 &mut self, 2460 segments: Vec<Document<'a>>, 2461 is_simple: bool, 2462 location: &SrcSpan, 2463 ) -> Document<'a> { 2464 let comments = self.pop_comments(location.end); 2465 let comments_doc = printed_comments(comments, false); 2466 2467 // Avoid adding illegal comma in empty bit array by explicitly handling it 2468 if segments.is_empty() { 2469 // We take all comments that come _before_ the end of the bit array, 2470 // that is all comments that are inside "<<" and ">>", if there's 2471 // any comment we want to put it inside the empty bit array! 2472 // Refer to the `list` function for a similar procedure. 2473 return match comments_doc { 2474 None => "<<>>".to_doc(), 2475 Some(comments) => "<<" 2476 .to_doc() 2477 .append(break_("", "").nest(INDENT)) 2478 .append(comments) 2479 .append(break_("", "")) 2480 .append(">>") 2481 // vvv We want to make sure the comments are on a separate 2482 // line from the opening and closing angle brackets so 2483 // we force the breaks to be split on newlines. 2484 .force_break(), 2485 }; 2486 } 2487 let comma = if is_simple { 2488 flex_break(",", ", ") 2489 } else { 2490 break_(",", ", ") 2491 }; 2492 2493 let last_break = break_(",", ""); 2494 let doc = break_("<<", "<<") 2495 .append(join(segments, comma)) 2496 .nest(INDENT); 2497 2498 match comments_doc { 2499 None => doc.append(last_break).append(">>").group(), 2500 Some(comments) => doc 2501 .append(last_break.nest(INDENT)) 2502 // ^ Notice how in this case we nest the final break before 2503 // adding it: this way the comments are going to be as 2504 // indented as the bit array items. 2505 .append(comments.nest(INDENT)) 2506 .append(line()) 2507 .append(">>") 2508 .force_break() 2509 .group(), 2510 } 2511 } 2512 2513 fn bit_array_segment_expr<'a>(&mut self, expr: &'a UntypedExpr) -> Document<'a> { 2514 match expr { 2515 UntypedExpr::Placeholder { .. } => panic!("Placeholders should not be formatted"), 2516 2517 UntypedExpr::BinOp { .. } => wrap_block(self.expr(expr)), 2518 2519 UntypedExpr::Int { .. } 2520 | UntypedExpr::Float { .. } 2521 | UntypedExpr::String { .. } 2522 | UntypedExpr::Var { .. } 2523 | UntypedExpr::Fn { .. } 2524 | UntypedExpr::List { .. } 2525 | UntypedExpr::Call { .. } 2526 | UntypedExpr::PipeLine { .. } 2527 | UntypedExpr::Case { .. } 2528 | UntypedExpr::FieldAccess { .. } 2529 | UntypedExpr::Tuple { .. } 2530 | UntypedExpr::TupleIndex { .. } 2531 | UntypedExpr::Todo { .. } 2532 | UntypedExpr::Panic { .. } 2533 | UntypedExpr::Echo { .. } 2534 | UntypedExpr::BitArray { .. } 2535 | UntypedExpr::RecordUpdate { .. } 2536 | UntypedExpr::NegateBool { .. } 2537 | UntypedExpr::NegateInt { .. } 2538 | UntypedExpr::Block { .. } => self.expr(expr), 2539 } 2540 } 2541 2542 fn statement<'a>(&mut self, statement: &'a UntypedStatement) -> Document<'a> { 2543 match statement { 2544 Statement::Expression(expression) => self.expr(expression), 2545 Statement::Assignment(assignment) => self.assignment(assignment), 2546 Statement::Use(use_) => self.use_(use_), 2547 Statement::Assert(_) => todo!(), 2548 } 2549 } 2550 2551 fn block<'a>( 2552 &mut self, 2553 location: &SrcSpan, 2554 statements: &'a Vec1<UntypedStatement>, 2555 force_breaks: bool, 2556 ) -> Document<'a> { 2557 let statements_doc = docvec![break_("", " "), self.statements(statements)].nest(INDENT); 2558 let trailing_comments = self.pop_comments(location.end); 2559 let trailing_comments = printed_comments(trailing_comments, false); 2560 let block_doc = match trailing_comments { 2561 Some(trailing_comments_doc) => docvec![ 2562 "{", 2563 statements_doc, 2564 line().nest(INDENT), 2565 trailing_comments_doc.nest(INDENT), 2566 line(), 2567 "}" 2568 ] 2569 .force_break(), 2570 None => docvec!["{", statements_doc, break_("", " "), "}"], 2571 }; 2572 2573 if force_breaks { 2574 block_doc.force_break().group() 2575 } else { 2576 block_doc.group() 2577 } 2578 } 2579 2580 pub fn wrap_function_call_args<'a, I>(&mut self, args: I, location: &SrcSpan) -> Document<'a> 2581 where 2582 I: IntoIterator<Item = Document<'a>>, 2583 { 2584 let mut args = args.into_iter().peekable(); 2585 if args.peek().is_none() { 2586 return "()".to_doc(); 2587 } 2588 2589 let args_doc = break_("", "") 2590 .append(join(args, break_(",", ", "))) 2591 .nest_if_broken(INDENT); 2592 2593 // We get all remaining comments that come before the call's closing 2594 // parenthesis. 2595 // If there's any we add those before the closing parenthesis instead 2596 // of moving those out of the call. 2597 // Otherwise those would be moved out of the call. 2598 let comments = self.pop_comments(location.end); 2599 let closing_parens = match printed_comments(comments, false) { 2600 None => docvec![break_(",", ""), ")"], 2601 Some(comment) => { 2602 docvec![break_(",", "").nest(INDENT), comment, line(), ")"].force_break() 2603 } 2604 }; 2605 2606 "(".to_doc().append(args_doc).append(closing_parens).group() 2607 } 2608 2609 pub fn wrap_args<'a, I>(&mut self, args: I, comments_limit: u32) -> Document<'a> 2610 where 2611 I: IntoIterator<Item = Document<'a>>, 2612 { 2613 let mut args = args.into_iter().peekable(); 2614 if args.peek().is_none() { 2615 let comments = self.pop_comments(comments_limit); 2616 return match printed_comments(comments, false) { 2617 Some(comments) => "(" 2618 .to_doc() 2619 .append(break_("", "")) 2620 .append(comments) 2621 .nest_if_broken(INDENT) 2622 .force_break() 2623 .append(break_("", "")) 2624 .append(")"), 2625 None => "()".to_doc(), 2626 }; 2627 } 2628 let doc = break_("(", "(").append(join(args, break_(",", ", "))); 2629 2630 // Include trailing comments if there are any 2631 let comments = self.pop_comments(comments_limit); 2632 match printed_comments(comments, false) { 2633 Some(comments) => doc 2634 .append(break_(",", "")) 2635 .append(comments) 2636 .nest_if_broken(INDENT) 2637 .force_break() 2638 .append(break_("", "")) 2639 .append(")"), 2640 None => doc 2641 .nest_if_broken(INDENT) 2642 .append(break_(",", "")) 2643 .append(")"), 2644 } 2645 } 2646 2647 pub fn wrap_args_with_spread<'a, I>(&mut self, args: I, comments_limit: u32) -> Document<'a> 2648 where 2649 I: IntoIterator<Item = Document<'a>>, 2650 { 2651 let mut args = args.into_iter().peekable(); 2652 if args.peek().is_none() { 2653 return self.wrap_args(args, comments_limit); 2654 } 2655 let doc = break_("(", "(") 2656 .append(join(args, break_(",", ", "))) 2657 .append(break_(",", ", ")) 2658 .append(".."); 2659 2660 // Include trailing comments if there are any 2661 let comments = self.pop_comments(comments_limit); 2662 match printed_comments(comments, false) { 2663 Some(comments) => doc 2664 .append(break_(",", "")) 2665 .append(comments) 2666 .nest_if_broken(INDENT) 2667 .force_break() 2668 .append(break_("", "")) 2669 .append(")"), 2670 None => doc 2671 .nest_if_broken(INDENT) 2672 .append(break_(",", "")) 2673 .append(")"), 2674 } 2675 } 2676 2677 /// Given some regular comments it pretty prints those with any respective 2678 /// doc comment that might be preceding those. 2679 /// For example: 2680 /// 2681 /// ```gleam 2682 /// /// Doc 2683 /// // comment 2684 /// 2685 /// /// Doc 2686 /// pub fn wibble() {} 2687 /// ``` 2688 /// 2689 /// We don't want the first doc comment to be merged together with 2690 /// `wibble`'s doc comment, so when we run into comments like `// comment` 2691 /// we need to first print all documentation comments that come before it. 2692 /// 2693 fn printed_documented_comments<'a, 'b>( 2694 &mut self, 2695 comments: impl IntoIterator<Item = (u32, Option<&'b str>)>, 2696 ) -> Option<Document<'a>> { 2697 let mut comments = comments.into_iter().peekable(); 2698 let _ = comments.peek()?; 2699 2700 let mut doc = Vec::new(); 2701 while let Some(c) = comments.next() { 2702 let (is_doc_commented, c) = match c { 2703 (comment_start, Some(c)) => { 2704 let doc_comment = self.doc_comments(comment_start); 2705 let is_doc_commented = !doc_comment.is_empty(); 2706 doc.push(doc_comment); 2707 (is_doc_commented, c) 2708 } 2709 (_, None) => continue, 2710 }; 2711 doc.push("//".to_doc().append(EcoString::from(c))); 2712 match comments.peek() { 2713 // Next line is a comment 2714 Some((_, Some(_))) => doc.push(line()), 2715 // Next line is empty 2716 Some((_, None)) => { 2717 let _ = comments.next(); 2718 doc.push(lines(2)); 2719 } 2720 // We've reached the end, there are no more lines 2721 None => { 2722 if is_doc_commented { 2723 doc.push(lines(2)); 2724 } else { 2725 doc.push(line()); 2726 } 2727 } 2728 } 2729 } 2730 let doc = concat(doc); 2731 Some(doc.force_break()) 2732 } 2733 2734 fn echo<'a>(&mut self, expression: &'a Option<Box<UntypedExpr>>) -> Document<'a> { 2735 let Some(expression) = expression else { 2736 return "echo".to_doc(); 2737 }; 2738 2739 match expression.as_ref() { 2740 // When a pipeline gets broken on multiple lines we don't want it to 2741 // be on the same line as echo, or it would look confusing; instead 2742 // it's nested onto a new line: 2743 // 2744 // ```gleam 2745 // echo first 2746 // |> wobble 2747 // |> wibble 2748 // ``` 2749 // 2750 // So it's easier to see echo is printing the whole thing. Otherwise, 2751 // it would look like echo is printing just the first item: 2752 // 2753 // ```gleam 2754 // echo first 2755 // |> wobble 2756 // |> wibble 2757 // ``` 2758 // 2759 UntypedExpr::PipeLine { .. } => docvec!["echo ", self.expr(expression).nest(INDENT)], 2760 _ => docvec!["echo ", self.expr(expression)], 2761 } 2762 } 2763} 2764 2765fn init_and_last<T>(vec: &[T]) -> Option<(&[T], &T)> { 2766 match vec { 2767 [] => None, 2768 _ => match vec.split_at(vec.len() - 1) { 2769 (init, [last]) => Some((init, last)), 2770 _ => panic!("unreachable"), 2771 }, 2772 } 2773} 2774 2775impl<'a> Documentable<'a> for &'a ArgNames { 2776 fn to_doc(self) -> Document<'a> { 2777 match self { 2778 ArgNames::Named { name, .. } | ArgNames::Discard { name, .. } => name.to_doc(), 2779 ArgNames::LabelledDiscard { label, name, .. } 2780 | ArgNames::NamedLabelled { label, name, .. } => { 2781 docvec![label, " ", name] 2782 } 2783 } 2784 } 2785} 2786 2787fn pub_(publicity: Publicity) -> Document<'static> { 2788 match publicity { 2789 Publicity::Public | Publicity::Internal { .. } => "pub ".to_doc(), 2790 Publicity::Private => nil(), 2791 } 2792} 2793 2794impl<'a> Documentable<'a> for &'a UnqualifiedImport { 2795 fn to_doc(self) -> Document<'a> { 2796 self.name.as_str().to_doc().append(match &self.as_name { 2797 None => nil(), 2798 Some(s) => " as ".to_doc().append(s.as_str()), 2799 }) 2800 } 2801} 2802 2803impl<'a> Documentable<'a> for &'a BinOp { 2804 fn to_doc(self) -> Document<'a> { 2805 match self { 2806 BinOp::And => "&&", 2807 BinOp::Or => "||", 2808 BinOp::LtInt => "<", 2809 BinOp::LtEqInt => "<=", 2810 BinOp::LtFloat => "<.", 2811 BinOp::LtEqFloat => "<=.", 2812 BinOp::Eq => "==", 2813 BinOp::NotEq => "!=", 2814 BinOp::GtEqInt => ">=", 2815 BinOp::GtInt => ">", 2816 BinOp::GtEqFloat => ">=.", 2817 BinOp::GtFloat => ">.", 2818 BinOp::AddInt => "+", 2819 BinOp::AddFloat => "+.", 2820 BinOp::SubInt => "-", 2821 BinOp::SubFloat => "-.", 2822 BinOp::MultInt => "*", 2823 BinOp::MultFloat => "*.", 2824 BinOp::DivInt => "/", 2825 BinOp::DivFloat => "/.", 2826 BinOp::RemainderInt => "%", 2827 BinOp::Concatenate => "<>", 2828 } 2829 .to_doc() 2830 } 2831} 2832 2833pub fn break_block(doc: Document<'_>) -> Document<'_> { 2834 "{".to_doc() 2835 .append(line().append(doc).nest(INDENT)) 2836 .append(line()) 2837 .append("}") 2838 .force_break() 2839} 2840 2841pub fn wrap_block(doc: Document<'_>) -> Document<'_> { 2842 break_("{", "{ ") 2843 .append(doc) 2844 .nest(INDENT) 2845 .append(break_("", " ")) 2846 .append("}") 2847} 2848 2849fn printed_comments<'a, 'comments>( 2850 comments: impl IntoIterator<Item = Option<&'comments str>>, 2851 trailing_newline: bool, 2852) -> Option<Document<'a>> { 2853 let mut comments = comments.into_iter().peekable(); 2854 let _ = comments.peek()?; 2855 2856 let mut doc = Vec::new(); 2857 while let Some(c) = comments.next() { 2858 let c = match c { 2859 Some(c) => c, 2860 None => continue, 2861 }; 2862 doc.push("//".to_doc().append(EcoString::from(c))); 2863 match comments.peek() { 2864 // Next line is a comment 2865 Some(Some(_)) => doc.push(line()), 2866 // Next line is empty 2867 Some(None) => { 2868 let _ = comments.next(); 2869 match comments.peek() { 2870 Some(_) => doc.push(lines(2)), 2871 None => { 2872 if trailing_newline { 2873 doc.push(lines(2)); 2874 } 2875 } 2876 } 2877 } 2878 // We've reached the end, there are no more lines 2879 None => { 2880 if trailing_newline { 2881 doc.push(line()); 2882 } 2883 } 2884 } 2885 } 2886 let doc = concat(doc); 2887 if trailing_newline { 2888 Some(doc.force_break()) 2889 } else { 2890 Some(doc) 2891 } 2892} 2893 2894fn commented<'a, 'comments>( 2895 doc: Document<'a>, 2896 comments: impl IntoIterator<Item = Option<&'comments str>>, 2897) -> Document<'a> { 2898 match printed_comments(comments, true) { 2899 Some(comments) => comments.append(doc.group()), 2900 None => doc, 2901 } 2902} 2903 2904fn bit_array_segment<Value, Type, ToDoc>( 2905 segment: &BitArraySegment<Value, Type>, 2906 mut to_doc: ToDoc, 2907) -> Document<'_> 2908where 2909 ToDoc: FnMut(&Value) -> Document<'_>, 2910{ 2911 match segment { 2912 BitArraySegment { value, options, .. } if options.is_empty() => to_doc(value), 2913 2914 BitArraySegment { value, options, .. } => to_doc(value).append(":").append(join( 2915 options 2916 .iter() 2917 .map(|option| segment_option(option, |value| to_doc(value))), 2918 "-".to_doc(), 2919 )), 2920 } 2921} 2922 2923fn segment_option<ToDoc, Value>(option: &BitArrayOption<Value>, mut to_doc: ToDoc) -> Document<'_> 2924where 2925 ToDoc: FnMut(&Value) -> Document<'_>, 2926{ 2927 match option { 2928 BitArrayOption::Bytes { .. } => "bytes".to_doc(), 2929 BitArrayOption::Bits { .. } => "bits".to_doc(), 2930 BitArrayOption::Int { .. } => "int".to_doc(), 2931 BitArrayOption::Float { .. } => "float".to_doc(), 2932 BitArrayOption::Utf8 { .. } => "utf8".to_doc(), 2933 BitArrayOption::Utf16 { .. } => "utf16".to_doc(), 2934 BitArrayOption::Utf32 { .. } => "utf32".to_doc(), 2935 BitArrayOption::Utf8Codepoint { .. } => "utf8_codepoint".to_doc(), 2936 BitArrayOption::Utf16Codepoint { .. } => "utf16_codepoint".to_doc(), 2937 BitArrayOption::Utf32Codepoint { .. } => "utf32_codepoint".to_doc(), 2938 BitArrayOption::Signed { .. } => "signed".to_doc(), 2939 BitArrayOption::Unsigned { .. } => "unsigned".to_doc(), 2940 BitArrayOption::Big { .. } => "big".to_doc(), 2941 BitArrayOption::Little { .. } => "little".to_doc(), 2942 BitArrayOption::Native { .. } => "native".to_doc(), 2943 2944 BitArrayOption::Size { 2945 value, 2946 short_form: false, 2947 .. 2948 } => "size" 2949 .to_doc() 2950 .append("(") 2951 .append(to_doc(value)) 2952 .append(")"), 2953 2954 BitArrayOption::Size { 2955 value, 2956 short_form: true, 2957 .. 2958 } => to_doc(value), 2959 2960 BitArrayOption::Unit { value, .. } => "unit" 2961 .to_doc() 2962 .append("(") 2963 .append(eco_format!("{value}")) 2964 .append(")"), 2965 } 2966} 2967 2968pub fn comments_before<'a>( 2969 comments: &'a [Comment<'a>], 2970 empty_lines: &'a [u32], 2971 limit: u32, 2972 retain_empty_lines: bool, 2973) -> ( 2974 impl Iterator<Item = (u32, Option<&'a str>)>, 2975 &'a [Comment<'a>], 2976 &'a [u32], 2977) { 2978 let end_comments = comments 2979 .iter() 2980 .position(|c| c.start > limit) 2981 .unwrap_or(comments.len()); 2982 let end_empty_lines = empty_lines 2983 .iter() 2984 .position(|l| *l > limit) 2985 .unwrap_or(empty_lines.len()); 2986 let popped_comments = comments 2987 .get(0..end_comments) 2988 .expect("0..end_comments is guaranteed to be in bounds") 2989 .iter() 2990 .map(|c| (c.start, Some(c.content))); 2991 let popped_empty_lines = if retain_empty_lines { empty_lines } else { &[] } 2992 .get(0..end_empty_lines) 2993 .unwrap_or(&[]) 2994 .iter() 2995 .map(|i| (i, i)) 2996 // compact consecutive empty lines into a single line 2997 .coalesce(|(a_start, a_end), (b_start, b_end)| { 2998 if *a_end + 1 == *b_start { 2999 Ok((a_start, b_end)) 3000 } else { 3001 Err(((a_start, a_end), (b_start, b_end))) 3002 } 3003 }) 3004 .map(|l| (*l.0, None)); 3005 let popped = popped_comments 3006 .merge_by(popped_empty_lines, |(a, _), (b, _)| a < b) 3007 .skip_while(|(_, comment_or_line)| comment_or_line.is_none()); 3008 ( 3009 popped, 3010 comments.get(end_comments..).expect("in bounds"), 3011 empty_lines.get(end_empty_lines..).expect("in bounds"), 3012 ) 3013} 3014 3015fn is_breakable_argument(expr: &UntypedExpr, arity: usize) -> bool { 3016 match expr { 3017 // A call is only breakable if it is the only argument 3018 UntypedExpr::Call { .. } => arity == 1, 3019 3020 UntypedExpr::Fn { .. } 3021 | UntypedExpr::Block { .. } 3022 | UntypedExpr::Case { .. } 3023 | UntypedExpr::List { .. } 3024 | UntypedExpr::Tuple { .. } 3025 | UntypedExpr::BitArray { .. } => true, 3026 _ => false, 3027 } 3028} 3029 3030enum CallArgFormatting<'a, A> { 3031 ShorthandLabelled(&'a EcoString), 3032 Unlabelled(&'a A), 3033 Labelled(&'a EcoString, &'a A), 3034} 3035 3036fn expr_call_arg_formatting(arg: &CallArg<UntypedExpr>) -> CallArgFormatting<'_, UntypedExpr> { 3037 match arg { 3038 // An argument supplied using label shorthand syntax. 3039 _ if arg.uses_label_shorthand() => CallArgFormatting::ShorthandLabelled( 3040 arg.label.as_ref().expect("label shorthand with no label"), 3041 ), 3042 // A labelled argument. 3043 CallArg { 3044 label: Some(label), 3045 value, 3046 .. 3047 } => CallArgFormatting::Labelled(label, value), 3048 // An unlabelled argument. 3049 CallArg { value, .. } => CallArgFormatting::Unlabelled(value), 3050 } 3051} 3052 3053fn pattern_call_arg_formatting( 3054 arg: &CallArg<UntypedPattern>, 3055) -> CallArgFormatting<'_, UntypedPattern> { 3056 match arg { 3057 // An argument supplied using label shorthand syntax. 3058 _ if arg.uses_label_shorthand() => CallArgFormatting::ShorthandLabelled( 3059 arg.label.as_ref().expect("label shorthand with no label"), 3060 ), 3061 // A labelled argument. 3062 CallArg { 3063 label: Some(label), 3064 value, 3065 .. 3066 } => CallArgFormatting::Labelled(label, value), 3067 // An unlabelled argument. 3068 CallArg { value, .. } => CallArgFormatting::Unlabelled(value), 3069 } 3070} 3071 3072fn constant_call_arg_formatting<A, B>( 3073 arg: &CallArg<Constant<A, B>>, 3074) -> CallArgFormatting<'_, Constant<A, B>> { 3075 match arg { 3076 // An argument supplied using label shorthand syntax. 3077 _ if arg.uses_label_shorthand() => CallArgFormatting::ShorthandLabelled( 3078 arg.label.as_ref().expect("label shorthand with no label"), 3079 ), 3080 // A labelled argument. 3081 CallArg { 3082 label: Some(label), 3083 value, 3084 .. 3085 } => CallArgFormatting::Labelled(label, value), 3086 // An unlabelled argument. 3087 CallArg { value, .. } => CallArgFormatting::Unlabelled(value), 3088 } 3089} 3090 3091struct AttributesPrinter<'a> { 3092 external_erlang: &'a Option<(EcoString, EcoString, SrcSpan)>, 3093 external_javascript: &'a Option<(EcoString, EcoString, SrcSpan)>, 3094 deprecation: &'a Deprecation, 3095 internal: bool, 3096} 3097 3098impl<'a> AttributesPrinter<'a> { 3099 pub fn new() -> Self { 3100 Self { 3101 external_erlang: &None, 3102 external_javascript: &None, 3103 deprecation: &Deprecation::NotDeprecated, 3104 internal: false, 3105 } 3106 } 3107 3108 pub fn set_external_erlang( 3109 mut self, 3110 external: &'a Option<(EcoString, EcoString, SrcSpan)>, 3111 ) -> Self { 3112 self.external_erlang = external; 3113 self 3114 } 3115 3116 pub fn set_external_javascript( 3117 mut self, 3118 external: &'a Option<(EcoString, EcoString, SrcSpan)>, 3119 ) -> Self { 3120 self.external_javascript = external; 3121 self 3122 } 3123 3124 pub fn set_internal(mut self, publicity: Publicity) -> Self { 3125 self.internal = publicity.is_internal(); 3126 self 3127 } 3128 3129 pub fn set_deprecation(mut self, deprecation: &'a Deprecation) -> Self { 3130 self.deprecation = deprecation; 3131 self 3132 } 3133} 3134 3135impl<'a> Documentable<'a> for AttributesPrinter<'a> { 3136 fn to_doc(self) -> Document<'a> { 3137 let mut attributes = vec![]; 3138 3139 // @deprecated attribute 3140 if let Deprecation::Deprecated { message } = self.deprecation { 3141 attributes.push(docvec!["@deprecated(\"", message, "\")"]) 3142 }; 3143 3144 // @external attributes 3145 if let Some((m, f, _)) = self.external_erlang { 3146 attributes.push(docvec!["@external(erlang, \"", m, "\", \"", f, "\")"]) 3147 }; 3148 3149 if let Some((m, f, _)) = self.external_javascript { 3150 attributes.push(docvec!["@external(javascript, \"", m, "\", \"", f, "\")"]) 3151 }; 3152 3153 // @internal attribute 3154 if self.internal { 3155 attributes.push("@internal".to_doc()); 3156 }; 3157 3158 if attributes.is_empty() { 3159 nil() 3160 } else { 3161 join(attributes, line()).append(line()) 3162 } 3163 } 3164}