Fork of daniellemaywood.uk/gleam — Wasm codegen work
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(¤t_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(¤t_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}