1use std::cell::Cell;
2use std::fmt;
3
4use crate::cache::InputFormat;
5use crate::identifier::{Ident, LocIdent};
6use crate::parser::lexer::KEYWORDS;
7use crate::term::{
8 pattern::*,
9 record::{Field, FieldMetadata, Include, RecordData},
10 *,
11};
12use crate::{term, typ::*};
13
14use malachite::base::num::{basic::traits::Zero, conversion::traits::ToSci};
15use once_cell::sync::Lazy;
16use pretty::docs;
17pub use pretty::{DocAllocator, DocBuilder, Pretty};
18use regex::Regex;
19
20#[derive(Clone, Copy, Eq, PartialEq)]
21pub enum StringRenderStyle {
22 ForceMonoline,
24 Multiline,
26}
27
28fn min_interpolate_sign(text: &str) -> usize {
30 let reg = Regex::new(r#"([%]+\{)|("[%]+)"#).unwrap();
31 reg.find_iter(text)
32 .map(|m| {
33 m.end() - m.start()
42 })
43 .max()
44 .unwrap_or(1)
45}
46
47fn sorted_map<K: Ord, V>(m: &'_ IndexMap<K, V>) -> Vec<(&'_ K, &'_ V)> {
48 let mut ret: Vec<(&K, &V)> = m.iter().collect();
49 ret.sort_by_key(|(k, _)| *k);
50 ret
51}
52
53fn escape(s: &str) -> String {
55 s.replace('\\', "\\\\")
56 .replace("%{", "\\%{")
57 .replace('\"', "\\\"")
58 .replace('\n', "\\n")
59 .replace('\r', "\\r")
60}
61
62static QUOTING_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new("^_*[a-zA-Z][_a-zA-Z0-9-]*$").unwrap());
63
64pub fn ident_quoted(ident: impl Into<Ident>) -> String {
68 let ident = ident.into();
69 let label = ident.label();
70 if QUOTING_REGEX.is_match(label) && !KEYWORDS.contains(&label) {
71 String::from(label)
72 } else {
73 format!("\"{}\"", escape(label))
74 }
75}
76
77pub fn enum_tag_quoted(ident: impl Into<Ident>) -> String {
81 let ident = ident.into();
82 let label = ident.label();
83 if QUOTING_REGEX.is_match(label) {
84 String::from(label)
85 } else {
86 format!("\"{}\"", escape(label))
87 }
88}
89
90#[derive(Clone, Copy, Eq, PartialEq, Debug)]
91enum RecursivePriority {
92 Default,
93 Force,
94 None,
95}
96
97impl RecursivePriority {
98 fn is_present(&self) -> bool {
99 !matches!(self, RecursivePriority::None)
100 }
101}
102
103fn split_recursive_priority(value: &RichTerm) -> (RecursivePriority, RichTerm) {
113 if let Term::App(f, x) = value.as_ref() {
114 match f.as_ref() {
115 Term::Var(id) if id.label() == "$rec_default" => {
116 return (RecursivePriority::Default, x.clone());
117 }
118 Term::Var(id) if id.label() == "$rec_force" => {
119 return (RecursivePriority::Force, x.clone());
120 }
121 _ => (),
122 }
123 };
124 (RecursivePriority::None, value.clone())
125}
126
127fn contains_newline<T>(chunks: &[StrChunk<T>]) -> bool {
129 chunks.iter().any(|chunk| match chunk {
130 StrChunk::Literal(str) => str.contains('\n'),
131 StrChunk::Expr(_, _) => false,
132 })
133}
134
135fn contains_carriage_return<T>(chunks: &[StrChunk<T>]) -> bool {
138 chunks.iter().any(|chunk| match chunk {
139 StrChunk::Literal(str) => str.contains('\r'),
140 StrChunk::Expr(_, _) => false,
141 })
142}
143
144fn needs_parens_in_type_pos(typ: &Type) -> bool {
153 if let TypeF::Contract(term) = &typ.typ {
154 matches!(
155 term.as_ref(),
156 Term::Fun(..)
157 | Term::FunPattern(..)
158 | Term::CustomContract(_)
159 | Term::Let(..)
160 | Term::LetPattern(..)
161 | Term::Op1(UnaryOp::IfThenElse, _)
162 | Term::Import { .. }
163 | Term::ResolvedImport(..)
164 )
165 } else {
166 false
167 }
168}
169
170pub fn fmt_pretty<T>(value: &T, f: &mut fmt::Formatter) -> fmt::Result
171where
172 T: for<'a> Pretty<'a, Allocator, ()> + Clone,
173{
174 let allocator = Allocator::default();
175 let doc: DocBuilder<_, ()> = value.clone().pretty(&allocator);
176 doc.render_fmt(80, f)
177}
178
179#[derive(Clone, Copy, Debug, Default)]
180struct SizeBound {
181 depth: usize,
182 size: usize,
183}
184
185pub struct Allocator {
197 inner: pretty::BoxAllocator,
198 bound: Option<Cell<SizeBound>>,
199}
200
201impl Default for Allocator {
203 fn default() -> Self {
204 Self {
205 inner: pretty::BoxAllocator,
206 bound: None,
207 }
208 }
209}
210
211impl Allocator {
212 pub fn bounded(max_depth: usize, max_size: usize) -> Self {
214 Self {
215 inner: pretty::BoxAllocator,
216 bound: Some(Cell::new(SizeBound {
217 depth: max_depth,
218 size: max_size,
219 })),
220 }
221 }
222
223 fn shrunken<'a, F: FnOnce(&'a Allocator) -> DocBuilder<'a, Self>>(
225 &'a self,
226 child_size: usize,
227 f: F,
228 ) -> DocBuilder<'a, Self> {
229 if let Some(bound) = self.bound.as_ref() {
230 let old = bound.get();
231 bound.set(SizeBound {
232 depth: old.depth.saturating_sub(1),
233 size: child_size,
234 });
235
236 let ret = f(self);
237
238 bound.set(old);
239
240 ret
241 } else {
242 f(self)
243 }
244 }
245
246 fn depth_constraint(&self) -> usize {
247 self.bound.as_ref().map_or(usize::MAX, |b| b.get().depth)
248 }
249
250 fn size_constraint(&self) -> usize {
251 self.bound.as_ref().map_or(usize::MAX, |b| b.get().size)
252 }
253}
254
255impl<'a> DocAllocator<'a> for Allocator {
256 type Doc = pretty::BoxDoc<'a>;
257
258 fn alloc(&'a self, doc: pretty::Doc<'a, Self::Doc>) -> Self::Doc {
259 self.inner.alloc(doc)
260 }
261
262 fn alloc_column_fn(
263 &'a self,
264 f: impl Fn(usize) -> Self::Doc + 'a,
265 ) -> <Self::Doc as pretty::DocPtr<'a, ()>>::ColumnFn {
266 self.inner.alloc_column_fn(f)
267 }
268
269 fn alloc_width_fn(
270 &'a self,
271 f: impl Fn(isize) -> Self::Doc + 'a,
272 ) -> <Self::Doc as pretty::DocPtr<'a, ()>>::WidthFn {
273 self.inner.alloc_width_fn(f)
274 }
275}
276
277impl Allocator {
278 fn record<'a>(
279 &'a self,
280 record_data: &RecordData,
281 includes: &[Include],
282 dyn_fields: &[(RichTerm, Field)],
283 ) -> DocBuilder<'a, Self> {
284 let size_per_child =
285 self.size_constraint() / (record_data.fields.len() + dyn_fields.len()).max(1);
286 if record_data.fields.is_empty() && dyn_fields.is_empty() && !record_data.attrs.open {
287 self.text("{}")
288 } else if size_per_child == 0 || self.depth_constraint() == 0 {
289 "{…}".pretty(self)
290 } else {
291 self.shrunken(size_per_child, |alloc| {
292 docs![
293 alloc,
294 alloc.line(),
295 alloc.intersperse(
296 includes
297 .iter()
298 .map(|incl| {
302 docs![
303 alloc,
304 "include",
305 alloc.space(),
306 incl.ident.to_string(),
307 self.field_metadata(&incl.metadata, true)
308 ]
309 }),
310 docs![alloc, ",", alloc.line()]
311 ),
312 if !includes.is_empty() {
313 docs![alloc, ",", alloc.line()]
314 } else {
315 alloc.nil()
316 },
317 alloc.fields(&record_data.fields),
318 if !dyn_fields.is_empty() {
319 docs![alloc, alloc.line(), alloc.dyn_fields(dyn_fields)]
320 } else {
321 alloc.nil()
322 },
323 if record_data.attrs.open {
324 docs![alloc, alloc.line(), ".."]
325 } else {
326 alloc.nil()
327 }
328 ]
329 .nest(2)
330 .append(self.line())
331 .braces()
332 .group()
333 })
334 }
335 }
336
337 fn record_type<'a>(&'a self, rows: &RecordRows) -> DocBuilder<'a, Self> {
338 let child_count = rows.iter().count().max(1);
339 let size_per_child = self.size_constraint() / child_count.max(1);
340 if size_per_child == 0 || self.depth_constraint() == 0 {
341 "{…}".pretty(self)
342 } else {
343 self.shrunken(size_per_child, |alloc| {
344 let tail = match rows.iter().last() {
345 Some(RecordRowsIteratorItem::TailDyn) => docs![alloc, ";", alloc.line(), "Dyn"],
346 Some(RecordRowsIteratorItem::TailVar(id)) => {
347 docs![alloc, ";", alloc.line(), id.to_string()]
348 }
349 _ => alloc.nil(),
350 };
351
352 let rows = rows.iter().filter_map(|r| match r {
353 RecordRowsIteratorItem::Row(r) => Some(r),
354 _ => None,
355 });
356
357 docs![
358 alloc,
359 alloc.line(),
360 alloc.intersperse(rows, docs![alloc, ",", alloc.line()]),
361 tail
362 ]
363 .nest(2)
364 .append(alloc.line())
365 .braces()
366 .group()
367 })
368 }
369 }
370
371 fn escaped_string<'a>(&'a self, s: &str) -> DocBuilder<'a, Self> {
374 self.text(escape(s))
375 }
376
377 fn chunks<'a>(
379 &'a self,
380 chunks: &[StrChunk<RichTerm>],
381 string_style: StringRenderStyle,
382 ) -> DocBuilder<'a, Self> {
383 let multiline = string_style == StringRenderStyle::Multiline
384 && contains_newline(chunks)
385 && !contains_carriage_return(chunks);
386
387 let nb_perc = if multiline {
388 chunks
389 .iter()
390 .map(
391 |c| {
392 if let StrChunk::Literal(s) = c {
393 min_interpolate_sign(s)
394 } else {
395 1
396 }
397 }, )
399 .max()
400 .unwrap_or(1)
401 } else {
402 1
403 };
404
405 let interp: String = "%".repeat(nb_perc);
406
407 let line_maybe = if multiline {
408 self.hardline()
409 } else {
410 self.nil()
411 };
412
413 let start_delimiter = if multiline {
414 format!("m{interp}")
415 } else {
416 String::new()
417 };
418
419 let end_delimiter = if multiline {
420 interp.clone()
421 } else {
422 String::new()
423 };
424
425 line_maybe
426 .clone()
427 .append(self.concat(chunks.iter().rev().map(|c| {
428 match c {
429 StrChunk::Literal(s) => {
430 if multiline {
431 self.concat(
432 s.split_inclusive('\n').map(|line| {
437 if let Some(s) = line.strip_suffix('\n') {
438 self.text(s.to_owned()).append(self.hardline())
439 } else {
440 self.text(line.to_owned())
441 }
442 }),
443 )
444 } else {
445 self.escaped_string(s)
446 }
447 }
448 StrChunk::Expr(e, _i) => docs![self, interp.clone(), "{", e, "}"],
449 }
450 })))
451 .nest(if multiline { 2 } else { 0 })
452 .append(line_maybe)
453 .double_quotes()
454 .enclose(start_delimiter, end_delimiter)
455 }
456
457 fn binding<'a>(&'a self, lhs: impl Pretty<'a, Self>, rt: RichTerm) -> DocBuilder<'a, Self> {
458 docs![
459 self,
460 lhs,
461 if let Term::Annotated(annot, _) = rt.as_ref() {
462 annot.pretty(self)
463 } else {
464 self.nil()
465 },
466 self.line(),
467 "= ",
468 if let Term::Annotated(_, inner) = rt.as_ref() {
469 inner.pretty(self)
470 } else {
471 rt.pretty(self)
472 },
473 ]
474 }
475
476 fn function<'a>(
481 &'a self,
482 first_param: impl Pretty<'a, Self>,
483 mut body: &RichTerm,
484 ) -> DocBuilder<'a, Self> {
485 let mut builder = docs![self, "fun", self.line(), first_param];
486
487 loop {
488 match body.as_ref() {
489 Term::Fun(id, rt) => {
490 builder = docs![self, builder, self.line(), self.as_string(id)];
491 body = rt;
492 }
493 Term::FunPattern(pat, rt) => {
494 builder = docs![self, builder, self.line(), self.pat_with_parens(pat)];
495 body = rt;
496 }
497 _ => break,
498 }
499 }
500
501 docs![
502 self,
503 builder,
504 self.line(),
505 "=>",
506 self.line(),
507 body.pretty(self)
508 ]
509 .nest(2)
510 .group()
511 }
512
513 fn field_metadata<'a>(
514 &'a self,
515 metadata: &FieldMetadata,
516 with_doc: bool,
517 ) -> DocBuilder<'a, Self> {
518 docs![
519 self,
520 &metadata.annotation,
521 if with_doc {
522 metadata
523 .doc
524 .clone()
525 .map(|doc| {
526 docs![
527 self,
528 self.line(),
529 "| doc ",
530 self.chunks(&[StrChunk::Literal(doc)], StringRenderStyle::Multiline),
531 ]
532 })
533 .unwrap_or_else(|| self.nil())
534 } else {
535 self.nil()
536 },
537 if metadata.opt {
538 docs![self, self.line(), "| optional"]
539 } else {
540 self.nil()
541 },
542 match &metadata.priority {
543 MergePriority::Bottom => docs![self, self.line(), "| default"],
544 MergePriority::Neutral => self.nil(),
545 MergePriority::Numeral(p) =>
546 docs![self, self.line(), "| priority ", p.to_sci().to_string()],
547 MergePriority::Top => docs![self, self.line(), "| force"],
548 }
549 ]
550 }
551
552 fn field<'a>(&'a self, id: &LocIdent, field: &Field) -> DocBuilder<'a, Self> {
553 self.text(ident_quoted(id))
554 .append(self.field_body(field))
555 .group()
556 }
557
558 fn dyn_field<'a>(&'a self, id_expr: &RichTerm, field: &Field) -> DocBuilder<'a, Self> {
559 match id_expr.as_ref() {
560 Term::StrChunks(chunks) => self.chunks(chunks, StringRenderStyle::ForceMonoline),
562 Term::ParseError(_) => docs![self, "%<parse error>"],
563 _ => unimplemented!("Dynamic record fields must be StrChunks currently"),
564 }
565 .append(self.field_body(field))
566 .group()
567 }
568
569 fn field_body<'a>(&'a self, field: &Field) -> DocBuilder<'a, Self> {
570 docs![
571 self,
572 self.field_metadata(&field.metadata, true),
573 if let Some((priority, value)) = field.value.as_ref().map(split_recursive_priority) {
574 let has_metadata =
575 field.metadata != FieldMetadata::default() || priority.is_present();
576
577 docs![
578 self,
579 priority,
580 if has_metadata {
581 docs![self, self.line(), "= "]
582 } else {
583 docs![self, " =", self.line()]
584 },
585 value.pretty(self).nest(2)
586 ]
587 } else {
588 self.nil()
589 },
590 ","
591 ]
592 .nest(2)
593 }
594
595 fn fields<'a>(&'a self, fields: &IndexMap<LocIdent, Field>) -> DocBuilder<'a, Self> {
596 self.intersperse(
597 sorted_map(fields)
598 .iter()
599 .map(|(id, field)| self.field(id, field)),
600 self.line(),
601 )
602 }
603
604 fn dyn_fields<'a>(&'a self, fields: &[(RichTerm, Field)]) -> DocBuilder<'a, Self> {
605 self.intersperse(
606 fields
607 .iter()
608 .map(|(id_term, field)| self.dyn_field(id_term, field)),
609 self.line(),
610 )
611 }
612
613 fn atom<'a>(&'a self, rt: &RichTerm) -> DocBuilder<'a, Self> {
614 rt.pretty(self).parens_if(!rt.as_ref().is_atom())
615 }
616
617 fn type_part<'a>(&'a self, typ: &Type) -> DocBuilder<'a, Self> {
629 typ.pretty(self).parens_if(needs_parens_in_type_pos(typ))
630 }
631
632 fn pat_with_parens<'a>(&'a self, pattern: &Pattern) -> DocBuilder<'a, Self> {
639 pattern.pretty(self).parens_if(matches!(
640 pattern.data,
641 PatternData::Enum(EnumPattern {
642 pattern: Some(_),
643 ..
644 }) | PatternData::Or(_)
645 ))
646 }
647}
648
649trait NickelDocBuilderExt {
650 fn parens_if(self, parens: bool) -> Self;
652}
653
654impl NickelDocBuilderExt for DocBuilder<'_, Allocator> {
655 fn parens_if(self, parens: bool) -> Self {
656 if parens {
657 self.parens()
658 } else {
659 self
660 }
661 }
662}
663
664impl<'a> Pretty<'a, Allocator> for LocIdent {
665 fn pretty(self, allocator: &'a Allocator) -> DocBuilder<'a, Allocator> {
666 allocator.text(self.into_label())
667 }
668}
669
670impl<'a> Pretty<'a, Allocator> for RecursivePriority {
671 fn pretty(self, allocator: &'a Allocator) -> DocBuilder<'a, Allocator> {
672 match self {
673 RecursivePriority::Default => allocator.text("| rec default"),
674 RecursivePriority::Force => allocator.text("| rec force"),
675 RecursivePriority::None => allocator.nil(),
676 }
677 }
678}
679
680impl<'a> Pretty<'a, Allocator> for &TypeAnnotation {
681 fn pretty(self, allocator: &'a Allocator) -> DocBuilder<'a, Allocator> {
682 docs![
683 allocator,
684 if let Some(typ) = &self.typ {
685 docs![
686 allocator,
687 allocator.line(),
688 ": ",
689 allocator.type_part(&typ.typ)
690 ]
691 } else {
692 allocator.nil()
693 },
694 if !self.contracts.is_empty() {
695 allocator.line()
696 } else {
697 allocator.nil()
698 },
699 allocator.intersperse(
700 self.contracts
701 .iter()
702 .map(|c| { docs![allocator, "| ", allocator.type_part(&c.typ)] }),
703 allocator.line(),
704 )
705 ]
706 }
707}
708
709impl<'a> Pretty<'a, Allocator> for &UnaryOp {
710 fn pretty(self, allocator: &'a Allocator) -> DocBuilder<'a, Allocator> {
711 use UnaryOp::*;
712 match self {
713 BoolNot => allocator.text("!"),
714 BoolAnd | BoolOr | RecordAccess(_) => {
715 unreachable!(
716 "These are handled specially since they are actually encodings \
717 of binary operators (`BoolAnd` and `BoolOr`) or need special \
718 formatting (`StaticAccess`). This currently happens in the `App` \
719 branch of `Term::pretty`"
720 )
721 }
722 EnumEmbed(id) => docs![
723 allocator,
724 "%enum/embed%",
725 docs![allocator, allocator.line(), id.to_string()].nest(2)
726 ],
727 op => allocator.text(format!("%{op}%")).append(allocator.space()),
728 }
729 }
730}
731
732impl<'a> Pretty<'a, Allocator> for &BinaryOp {
733 fn pretty(self, allocator: &'a Allocator) -> DocBuilder<'a, Allocator> {
734 use BinaryOp::*;
735 match self {
736 Plus => allocator.text("+"),
737 Sub => allocator.text("-"),
738
739 Mult => allocator.text("*"),
740 Div => allocator.text("/"),
741 Modulo => allocator.text("%"),
742
743 Eq => allocator.text("=="),
744 LessThan => allocator.text("<"),
745 GreaterThan => allocator.text(">"),
746 GreaterOrEq => allocator.text(">="),
747 LessOrEq => allocator.text("<="),
748
749 Merge(_) => allocator.text("&"),
750
751 StringConcat => allocator.text("++"),
752 ArrayConcat => allocator.text("@"),
753
754 RecordGet => allocator.text("."),
755
756 op => allocator.as_string(format!("%{op}%")),
757 }
758 }
759}
760
761impl<'a> Pretty<'a, Allocator> for &NAryOp {
762 fn pretty(self, allocator: &'a Allocator) -> DocBuilder<'a, Allocator> {
763 allocator.as_string(format!("%{self}%"))
764 }
765}
766
767impl<'a> Pretty<'a, Allocator> for &Pattern {
768 fn pretty(self, allocator: &'a Allocator) -> DocBuilder<'a, Allocator> {
769 let alias_prefix = if let Some(alias) = self.alias {
770 docs![
771 allocator,
772 alias.to_string(),
773 allocator.space(),
774 "@",
775 allocator.space()
776 ]
777 } else {
778 allocator.nil()
779 };
780
781 docs![allocator, alias_prefix, &self.data]
782 }
783}
784
785impl<'a> Pretty<'a, Allocator> for &PatternData {
786 fn pretty(self, allocator: &'a Allocator) -> DocBuilder<'a, Allocator> {
787 match self {
788 PatternData::Wildcard => allocator.text("_"),
789 PatternData::Any(id) => allocator.as_string(id),
790 PatternData::Record(rp) => rp.pretty(allocator),
791 PatternData::Array(ap) => ap.pretty(allocator),
792 PatternData::Enum(evp) => evp.pretty(allocator),
793 PatternData::Constant(cp) => cp.pretty(allocator),
794 PatternData::Or(op) => op.pretty(allocator),
795 }
796 }
797}
798
799impl<'a> Pretty<'a, Allocator> for &ConstantPattern {
800 fn pretty(self, allocator: &'a Allocator) -> DocBuilder<'a, Allocator> {
801 self.data.pretty(allocator)
802 }
803}
804
805impl<'a> Pretty<'a, Allocator> for &ConstantPatternData {
806 fn pretty(self, allocator: &'a Allocator) -> DocBuilder<'a, Allocator> {
807 match self {
808 ConstantPatternData::Bool(b) => allocator.as_string(b),
809 ConstantPatternData::Number(n) => allocator.as_string(format!("{}", n.to_sci())),
810 ConstantPatternData::String(s) => allocator.escaped_string(s).double_quotes(),
811 ConstantPatternData::Null => allocator.text("null"),
812 }
813 }
814}
815
816impl<'a> Pretty<'a, Allocator> for &EnumPattern {
817 fn pretty(self, allocator: &'a Allocator) -> DocBuilder<'a, Allocator> {
818 docs![
819 allocator,
820 "'",
821 enum_tag_quoted(&self.tag),
822 if let Some(ref arg_pat) = self.pattern {
823 docs![
824 allocator,
825 allocator.line(),
826 allocator.pat_with_parens(arg_pat)
827 ]
828 } else {
829 allocator.nil()
830 }
831 ]
832 }
833}
834
835impl<'a> Pretty<'a, Allocator> for &RecordPattern {
836 fn pretty(self, allocator: &'a Allocator) -> DocBuilder<'a, Allocator> {
837 let RecordPattern {
838 patterns: matches,
839 tail,
840 ..
841 } = self;
842 docs![
843 allocator,
844 allocator.line(),
845 allocator.intersperse(
846 matches.iter().map(|field_pat| {
847 docs![
848 allocator,
849 field_pat.matched_id.to_string(),
850 allocator.field_metadata(
851 &FieldMetadata {
852 annotation: field_pat.annotation.clone(),
853 ..Default::default()
854 },
855 false
856 ),
857 if let Some(default) = field_pat.default.as_ref() {
858 docs![allocator, allocator.line(), "? ", allocator.atom(default),]
859 } else {
860 allocator.nil()
861 },
862 match &field_pat.pattern.data {
863 PatternData::Any(id) if *id == field_pat.matched_id => allocator.nil(),
864 _ => docs![allocator, allocator.line(), "= ", &field_pat.pattern],
865 },
866 ","
867 ]
868 .nest(2)
869 }),
870 allocator.line()
871 ),
872 match tail {
873 TailPattern::Empty => allocator.nil(),
874 TailPattern::Open => docs![allocator, allocator.line(), ".."],
875 TailPattern::Capture(id) =>
876 docs![allocator, allocator.line(), "..", id.ident().to_string()],
877 },
878 ]
879 .nest(2)
880 .append(allocator.line())
881 .braces()
882 .group()
883 }
884}
885
886impl<'a> Pretty<'a, Allocator> for &ArrayPattern {
887 fn pretty(self, allocator: &'a Allocator) -> DocBuilder<'a, Allocator> {
888 docs![
889 allocator,
890 allocator.intersperse(
891 self.patterns.iter(),
892 docs![allocator, ",", allocator.line()],
893 ),
894 if !self.patterns.is_empty() && self.is_open() {
895 docs![allocator, ",", allocator.line()]
896 } else {
897 allocator.nil()
898 },
899 match self.tail {
900 TailPattern::Empty => allocator.nil(),
901 TailPattern::Open => allocator.text(".."),
902 TailPattern::Capture(id) => docs![allocator, "..", id.ident().to_string()],
903 },
904 ]
905 .nest(2)
906 .brackets()
907 .group()
908 }
909}
910
911impl<'a> Pretty<'a, Allocator> for &OrPattern {
912 fn pretty(self, allocator: &'a Allocator) -> DocBuilder<'a, Allocator> {
913 docs![
914 allocator,
915 allocator.intersperse(
916 self.patterns
917 .iter()
918 .map(|pat| allocator.pat_with_parens(pat)),
919 docs![allocator, allocator.line(), "or", allocator.space()],
920 ),
921 ]
922 .group()
923 }
924}
925
926impl<'a> Pretty<'a, Allocator> for &RichTerm {
927 fn pretty(self, allocator: &'a Allocator) -> DocBuilder<'a, Allocator> {
928 self.as_ref().pretty(allocator)
929 }
930}
931
932impl<'a> Pretty<'a, Allocator> for &Term {
933 fn pretty(self, allocator: &'a Allocator) -> DocBuilder<'a, Allocator> {
934 use Term::*;
935
936 match self {
937 Null => allocator.text("null"),
938 Bool(v) => allocator.as_string(v),
939 Num(n) => allocator.as_string(format!("{}", n.to_sci())),
940 Str(v) => allocator.escaped_string(v).double_quotes(),
941 StrChunks(chunks) => allocator.chunks(chunks, StringRenderStyle::Multiline),
942 Fun(id, body) => allocator.function(allocator.as_string(id), body),
943 CustomContract(ctr) => docs![
945 allocator,
946 "%contract/custom%",
947 docs![allocator, allocator.line(), allocator.atom(ctr),].nest(2),
948 ]
949 .group(),
950 FunPattern(pat, body) => allocator.function(allocator.pat_with_parens(pat), body),
951 Lbl(_lbl) => allocator.text("%<label>").append(allocator.line()),
952 Let(bindings, body, attrs) => docs![
953 allocator,
954 "let",
955 allocator.space(),
956 if attrs.rec {
957 docs![allocator, "rec", allocator.space()]
958 } else {
959 allocator.nil()
960 },
961 allocator.intersperse(
962 bindings
963 .iter()
964 .map(|(k, v)| allocator.binding(*k, v.clone())),
965 docs![allocator, ",", allocator.line()]
966 ),
967 allocator.line(),
968 "in",
969 ]
970 .nest(2)
971 .group()
972 .append(allocator.line())
973 .append(body.pretty(allocator).nest(2))
974 .group(),
975 LetPattern(bindings, body, attrs) => docs![
976 allocator,
977 "let",
978 allocator.space(),
979 if attrs.rec {
980 docs![allocator, "rec", allocator.space()]
981 } else {
982 allocator.nil()
983 },
984 allocator.intersperse(
985 bindings
986 .iter()
987 .map(|(k, v)| allocator.binding(k, v.clone())),
988 docs![allocator, ",", allocator.line()]
989 ),
990 allocator.line(),
991 "in",
992 ]
993 .nest(2)
994 .group()
995 .append(allocator.line())
996 .append(body.pretty(allocator).nest(2))
997 .group(),
998 App(rt1, rt2) => match rt1.as_ref() {
999 App(iop, t) if matches!(iop.as_ref(), Op1(UnaryOp::IfThenElse, _)) => {
1000 match iop.as_ref() {
1001 Op1(UnaryOp::IfThenElse, i) => docs![
1002 allocator,
1003 "if ",
1004 i,
1005 " then",
1006 docs![allocator, allocator.line(), t].nest(2),
1007 allocator.line(),
1008 "else",
1009 docs![allocator, allocator.line(), rt2].nest(2)
1010 ],
1011 _ => unreachable!(),
1012 }
1013 }
1014 Op1(op @ (UnaryOp::BoolAnd | UnaryOp::BoolOr), rt1) => docs![
1015 allocator,
1016 allocator.atom(rt1),
1017 allocator.line(),
1018 match op {
1019 UnaryOp::BoolAnd => "&& ",
1020 UnaryOp::BoolOr => "|| ",
1021 _ => unreachable!(),
1022 },
1023 allocator.atom(rt2)
1024 ],
1025 App(..) => docs![
1026 allocator,
1027 rt1,
1028 docs![allocator, allocator.line(), allocator.atom(rt2)].nest(2)
1029 ],
1030 _ => docs![
1031 allocator,
1032 allocator.atom(rt1),
1033 docs![allocator, allocator.line(), allocator.atom(rt2)].nest(2)
1034 ],
1035 }
1036 .group(),
1037 Var(id) => allocator.as_string(id),
1038 Enum(id) => allocator
1039 .text("'")
1040 .append(allocator.text(enum_tag_quoted(id))),
1041 EnumVariant { tag, arg, attrs: _ } => allocator
1042 .text("'")
1043 .append(allocator.text(enum_tag_quoted(tag)))
1044 .append(
1045 docs![allocator, allocator.line(), allocator.atom(arg)]
1046 .nest(2)
1047 .group(),
1048 ),
1049 Record(record_data) => allocator.record(record_data, &[], &[]),
1050 RecRecord(record_data, includes, dyn_fields, _) => {
1051 allocator.record(record_data, includes, dyn_fields)
1052 }
1053 Match(data) => docs![
1054 allocator,
1055 "match ",
1056 docs![
1057 allocator,
1058 allocator.line(),
1059 allocator.intersperse(
1060 data.branches.iter().map(|branch| docs![
1061 allocator,
1062 branch.pretty(allocator),
1063 ","
1064 ]),
1065 allocator.line(),
1066 ),
1067 ]
1068 .nest(2)
1069 .append(allocator.line())
1070 .braces()
1071 ]
1072 .group(),
1073 Array(fields, _) =>
1074 {
1077 docs![
1078 allocator,
1079 allocator.line(),
1080 allocator.intersperse(
1081 fields.iter().map(|rt| rt.pretty(allocator)),
1082 allocator.text(",").append(allocator.line()),
1083 ),
1084 ]
1085 .nest(2)
1086 .append(allocator.line())
1087 .brackets()
1088 .group()
1089 }
1090
1091 Op1(UnaryOp::RecordAccess(id), rt) => {
1092 docs![allocator, allocator.atom(rt), ".", ident_quoted(id)]
1093 }
1094 Op1(UnaryOp::BoolNot, rt) => docs![allocator, "!", allocator.atom(rt)],
1095 Op1(UnaryOp::BoolAnd, rt) => docs![allocator, "(&&)", allocator.atom(rt)],
1096 Op1(UnaryOp::BoolOr, rt) => docs![allocator, "(||)", allocator.atom(rt)],
1097 Op1(UnaryOp::IfThenElse, _) => unreachable!(),
1098 Op1(op, rt) => match op.pos() {
1099 OpPos::Prefix => docs![
1100 allocator,
1101 op,
1102 docs![allocator, allocator.line(), allocator.atom(rt)].nest(2)
1103 ]
1104 .group(),
1105 OpPos::Special | OpPos::Postfix | OpPos::Infix => {
1106 panic!("pretty print is not implemented for {op:?}")
1107 }
1108 },
1109 Op2(BinaryOp::RecordGet, rtl, rtr) => {
1110 docs![allocator, rtr, ".", rtl]
1111 }
1112 Op2(op, rtl, rtr) => docs![
1113 allocator,
1114 if (&BinaryOp::Sub, &Num(Number::ZERO)) == (op, rtl.as_ref()) {
1115 docs![allocator, allocator.text("-"), allocator.atom(rtr)]
1116 } else if op.pos() == OpPos::Prefix {
1117 op.pretty(allocator).append(
1118 docs![
1119 allocator,
1120 allocator.line(),
1121 allocator.atom(rtl),
1122 allocator.line(),
1123 allocator.atom(rtr)
1124 ]
1125 .nest(2),
1126 )
1127 } else {
1128 docs![
1129 allocator,
1130 allocator.atom(rtl),
1131 allocator.line(),
1132 op,
1133 " ",
1134 allocator.atom(rtr)
1135 ]
1136 },
1137 ]
1138 .group(),
1139 OpN(op, rts) => docs![
1140 allocator,
1141 op,
1142 docs![
1143 allocator,
1144 allocator.line(),
1145 allocator
1146 .intersperse(rts.iter().map(|rt| allocator.atom(rt)), allocator.line())
1147 ]
1148 .nest(2)
1149 ]
1150 .group(),
1151 ForeignId(_) => allocator.text("%<foreign>"),
1152 SealingKey(sym) => allocator.text(format!("%<sealing key: {sym}>")),
1153 Sealed(_i, _rt, _lbl) => allocator.text("%<sealed>"),
1154 Annotated(annot, rt) => allocator.atom(rt).append(annot.pretty(allocator)),
1155 Import(term::Import::Path { path, format }) => {
1156 docs![
1157 allocator,
1158 "import",
1159 allocator.space(),
1160 allocator
1161 .escaped_string(path.to_string_lossy().as_ref())
1162 .double_quotes(),
1163 if Some(*format) != InputFormat::from_path(path) {
1164 docs![
1165 allocator,
1166 allocator.space(),
1167 "as",
1168 allocator.space(),
1169 "'",
1170 format.to_str()
1171 ]
1172 } else {
1173 allocator.nil()
1174 },
1175 ]
1176 }
1177 Import(term::Import::Package { id }) => {
1178 allocator.text("import ").append(id.to_string())
1179 }
1180 ResolvedImport(id) => allocator.text(format!("import <file_id: {id:?}>")),
1181 Type { typ, contract: _ } => typ.pretty(allocator),
1183 ParseError(_) => allocator.text("%<parse error>"),
1184 RuntimeError(_) => allocator.text("%<runtime error>"),
1185 Closure(idx) => allocator.text(format!("%<closure@{idx:p}>")),
1186 }
1187 }
1188}
1189
1190impl<'a> Pretty<'a, Allocator> for &EnumRows {
1191 fn pretty(self, allocator: &'a Allocator) -> DocBuilder<'a, Allocator> {
1192 match &self.0 {
1193 EnumRowsF::Empty => allocator.nil(),
1194 EnumRowsF::TailVar(id) => docs![allocator, ";", allocator.line(), id.to_string()],
1195 EnumRowsF::Extend { row, tail } => {
1196 let mut result = row.pretty(allocator);
1197
1198 if let EnumRowsF::Extend { .. } = tail.0 {
1199 result = result.append(allocator.text(",").append(allocator.line()));
1200 }
1201
1202 result.append(tail.as_ref())
1203 }
1204 }
1205 }
1206}
1207
1208impl<'a> Pretty<'a, Allocator> for &EnumRow {
1209 fn pretty(self, allocator: &'a Allocator) -> DocBuilder<'a, Allocator> {
1210 let mut result = allocator
1211 .text("'")
1212 .append(allocator.text(enum_tag_quoted(&self.id)));
1213
1214 if let Some(typ) = self.typ.as_ref() {
1215 let ty_parenthesized = if typ.fmt_is_atom() {
1216 typ.pretty(allocator)
1217 } else {
1218 allocator
1219 .text("(")
1220 .append(allocator.line_())
1221 .append(typ.pretty(allocator))
1222 .append(allocator.line_())
1223 .append(")")
1224 };
1225
1226 result = result.append(allocator.text(" ")).append(ty_parenthesized);
1227 }
1228
1229 result
1230 }
1231}
1232
1233impl<'a> Pretty<'a, Allocator> for &RecordRows {
1234 fn pretty(self, allocator: &'a Allocator) -> DocBuilder<'a, Allocator> {
1235 match &self.0 {
1237 RecordRowsF::Empty => allocator.nil(),
1238 RecordRowsF::TailDyn => docs![allocator, ";", allocator.line(), "Dyn"],
1239 RecordRowsF::TailVar(id) => docs![allocator, ";", allocator.line(), id.to_string()],
1240 RecordRowsF::Extend { row, tail } => docs![
1241 allocator,
1242 row,
1243 if let RecordRowsF::Extend { .. } = tail.0 {
1244 docs![allocator, ",", allocator.line()]
1245 } else {
1246 allocator.nil()
1247 },
1248 tail.as_ref()
1249 ],
1250 }
1251 }
1252}
1253
1254impl<'a, Ty> Pretty<'a, Allocator> for &RecordRowF<Ty>
1255where
1256 Ty: std::ops::Deref<Target = Type>,
1257{
1258 fn pretty(self, allocator: &'a Allocator) -> DocBuilder<'a, Allocator> {
1259 docs![
1260 allocator,
1261 ident_quoted(&self.id),
1262 " : ",
1263 allocator.type_part(self.typ.deref()),
1264 ]
1265 }
1266}
1267
1268impl<'a> Pretty<'a, Allocator> for RecordRowF<&Type> {
1269 fn pretty(self, allocator: &'a Allocator) -> DocBuilder<'a, Allocator> {
1270 (&self).pretty(allocator)
1271 }
1272}
1273
1274impl<'a> Pretty<'a, Allocator> for &Type {
1275 fn pretty(self, allocator: &'a Allocator) -> DocBuilder<'a, Allocator> {
1276 use TypeF::*;
1277 match &self.typ {
1278 Dyn => allocator.text("Dyn"),
1279 Number => allocator.text("Number"),
1280 Bool => allocator.text("Bool"),
1281 String => allocator.text("String"),
1282 Array(ty) => if ty.fmt_is_atom() {
1283 docs![allocator, "Array", allocator.line(), ty.as_ref()].nest(2)
1284 } else {
1285 docs![
1286 allocator,
1287 "Array (",
1288 docs![allocator, allocator.line_(), ty.as_ref()].nest(2),
1289 allocator.line_(),
1290 ")"
1291 ]
1292 }
1293 .group(),
1294 ForeignId => allocator.text("ForeignId"),
1295 Symbol => allocator.text("Symbol"),
1296 Contract(t) => t.pretty(allocator),
1297 Var(var) => allocator.as_string(var),
1298 Forall { var, ref body, .. } => {
1299 let mut curr = body.as_ref();
1300 let mut foralls = vec![var];
1301 while let Type {
1302 typ: Forall { var, ref body, .. },
1303 ..
1304 } = curr
1305 {
1306 foralls.push(var);
1307 curr = body;
1308 }
1309 docs![
1310 allocator,
1311 "forall",
1312 allocator.line(),
1313 allocator.intersperse(
1314 foralls.iter().map(|i| allocator.as_string(i)),
1315 allocator.line(),
1316 ),
1317 ".",
1318 allocator.line(),
1319 allocator.type_part(curr)
1320 ]
1321 .nest(2)
1322 .group()
1323 }
1324 Enum(erows) => docs![allocator, allocator.line(), erows]
1325 .nest(2)
1326 .append(allocator.line())
1327 .enclose("[|", "|]")
1328 .group(),
1329 Record(rrows) => allocator.record_type(rrows),
1330 Dict {
1331 type_fields: ty,
1332 flavour: attrs,
1333 } => docs![
1334 allocator,
1335 allocator.line(),
1336 "_ ",
1337 match attrs {
1338 DictTypeFlavour::Type => ":",
1339 DictTypeFlavour::Contract => "|",
1340 },
1341 " ",
1342 allocator.type_part(ty.as_ref()),
1343 ]
1344 .nest(2)
1345 .append(allocator.line())
1346 .braces()
1347 .group(),
1348 Arrow(dom, codom) => docs![
1349 allocator,
1350 allocator
1351 .type_part(dom)
1352 .parens_if(matches!(dom.typ, Arrow(..) | Forall { .. }))
1353 .nest(2),
1354 allocator.line(),
1355 "-> ",
1356 allocator
1357 .type_part(codom)
1358 .parens_if(matches!(codom.typ, Forall { .. }))
1359 ]
1360 .group(),
1361 Wildcard(_) => allocator.text("_"),
1362 }
1363 }
1364}
1365
1366impl<'a> Pretty<'a, Allocator> for &MatchBranch {
1367 fn pretty(self, allocator: &'a Allocator) -> DocBuilder<'a, Allocator> {
1368 let guard = if let Some(guard) = &self.guard {
1369 docs![allocator, allocator.line(), "if", allocator.space(), guard]
1370 } else {
1371 allocator.nil()
1372 };
1373
1374 docs![
1375 allocator,
1376 &self.pattern,
1377 guard,
1378 allocator.space(),
1379 "=>",
1380 docs![allocator, allocator.line(), self.body.pretty(allocator),].nest(2),
1381 ]
1382 }
1383}
1384
1385#[macro_export]
1387macro_rules! impl_display_from_pretty {
1388 ($ty:ty) => {
1389 impl std::fmt::Display for $ty {
1390 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1391 $crate::pretty::fmt_pretty(&self, f)
1392 }
1393 }
1394 };
1395}
1396
1397pub trait PrettyPrintCap: ToString {
1400 fn pretty_print_cap(&self, max_width: usize) -> String {
1405 let output = self.to_string();
1406
1407 if output.len() <= max_width {
1408 output
1409 } else {
1410 let (end, _) = output.char_indices().nth(max_width).unwrap();
1411 let mut truncated = String::from(&output[..end]);
1412
1413 if max_width >= 2 {
1414 truncated.pop();
1415 truncated.push('\u{2026}');
1416 }
1417
1418 truncated
1419 }
1420 }
1421}
1422
1423#[cfg(test)]
1424mod tests {
1425 use crate::files::Files;
1426 use crate::parser::lexer::Lexer;
1427 use crate::parser::{
1428 grammar::{FixedTypeParser, TermParser},
1429 ErrorTolerantParserCompat,
1430 };
1431 use pretty::Doc;
1432
1433 use super::*;
1434 use indoc::indoc;
1435
1436 fn parse_type(s: &str) -> Type {
1438 let id = Files::new().add("<test>", s);
1439
1440 FixedTypeParser::new()
1441 .parse_strict_compat(id, Lexer::new(s))
1442 .unwrap()
1443 }
1444
1445 fn parse_term(s: &str) -> RichTerm {
1447 let id = Files::new().add("<test>", s);
1448
1449 TermParser::new()
1450 .parse_strict_compat(id, Lexer::new(s))
1451 .unwrap()
1452 }
1453
1454 #[track_caller]
1458 fn assert_long_short_type(long: &str, short: &str) {
1459 let ty = parse_type(long);
1460 let alloc = Allocator::default();
1461 let doc: DocBuilder<'_, _, ()> = ty.pretty(&alloc);
1462
1463 let mut long_lines = String::new();
1464 doc.render_fmt(usize::MAX, &mut long_lines).unwrap();
1465
1466 let mut short_lines = String::new();
1467 doc.render_fmt(0, &mut short_lines).unwrap();
1468
1469 assert_eq!(long_lines, long);
1470 assert_eq!(short_lines, short);
1471 }
1472
1473 #[track_caller]
1477 fn assert_long_short_term(long: &str, short: &str) {
1478 let term = parse_term(long);
1479 let alloc = Allocator::default();
1480 let doc: DocBuilder<'_, _, ()> = term.pretty(&alloc);
1481
1482 let mut long_lines = String::new();
1483 doc.render_fmt(160, &mut long_lines).unwrap();
1484
1485 let mut short_lines = String::new();
1486 doc.render_fmt(0, &mut short_lines).unwrap();
1487
1488 assert_eq!(long_lines, long);
1489 assert_eq!(short_lines, short);
1490 }
1491
1492 #[test]
1493 fn pretty_array_type() {
1494 assert_long_short_type("Array String", "Array\n String");
1495 assert_long_short_type(
1496 "Array (Number -> Array Dyn)",
1497 indoc! {"
1498 Array (
1499 Number
1500 -> Array
1501 Dyn
1502 )"
1503 },
1504 );
1505 }
1506
1507 #[test]
1508 fn pretty_arrow_type() {
1509 assert_long_short_type("Number -> Number", "Number\n-> Number");
1510 assert_long_short_type(
1511 "(Number -> Number -> Dyn) -> Number",
1512 indoc! {"
1513 (Number
1514 -> Number
1515 -> Dyn)
1516 -> Number"
1517 },
1518 );
1519 }
1520
1521 #[test]
1522 fn pretty_dict_type() {
1523 assert_long_short_type(
1524 "{ _ : Number }",
1525 indoc! {"
1526 {
1527 _ : Number
1528 }"
1529 },
1530 );
1531 assert_long_short_type(
1532 "{ _ : { x : Number, y : String } }",
1533 indoc! {"
1534 {
1535 _ : {
1536 x : Number,
1537 y : String
1538 }
1539 }"
1540 },
1541 );
1542 }
1543
1544 #[test]
1545 fn pretty_record_type() {
1546 assert_long_short_type(
1547 "{ x : Number, y : String; Dyn }",
1548 indoc! {"
1549 {
1550 x : Number,
1551 y : String;
1552 Dyn
1553 }"
1554 },
1555 );
1556 }
1557
1558 #[test]
1559 fn pretty_enum_type() {
1560 assert_long_short_type(
1561 "forall r. [| 'tag1, 'tag2, 'tag3; r |]",
1562 indoc! {"
1563 forall
1564 r.
1565 [|
1566 'tag1,
1567 'tag2,
1568 'tag3;
1569 r
1570 |]"
1571 },
1572 )
1573 }
1574
1575 #[test]
1576 fn pretty_forall_type() {
1577 assert_long_short_type(
1578 "forall a r. a -> { foo : a; r }",
1579 indoc! {"
1580 forall
1581 a
1582 r.
1583 a
1584 -> {
1585 foo : a;
1586 r
1587 }"
1588 },
1589 );
1590 }
1591
1592 #[test]
1593 fn pretty_opn() {
1594 assert_long_short_term(
1595 "%string/replace% string pattern replace",
1596 indoc! {"
1597 %string/replace%
1598 string
1599 pattern
1600 replace"
1601 },
1602 );
1603 }
1604
1605 #[test]
1606 fn pretty_binop() {
1607 assert_long_short_term(
1608 "a + b",
1609 indoc! {"
1610 a
1611 + b"
1612 },
1613 );
1614 assert_long_short_term(
1615 "%string/split% string sep",
1616 indoc! {"
1617 %string/split%
1618 string
1619 sep"
1620 },
1621 );
1622 assert_long_short_term("-5", "-5");
1623 assert_long_short_term(
1624 "a - (-b)",
1625 indoc! {"
1626 a
1627 - (-b)"
1628 },
1629 );
1630 }
1631
1632 #[test]
1633 fn pretty_unop() {
1634 assert_long_short_term("!xyz", "!xyz");
1635 assert_long_short_term(
1636 "a && b",
1637 indoc! {"
1638 a
1639 && b"
1640 },
1641 );
1642 assert_long_short_term(
1643 "(a && b) && c",
1644 indoc! {"
1645 (a
1646 && b)
1647 && c"
1648 },
1649 );
1650 assert_long_short_term(
1651 "a || b",
1652 indoc! {"
1653 a
1654 || b"
1655 },
1656 );
1657 assert_long_short_term(
1658 "if true then false else not",
1659 indoc! {"
1660 if true then
1661 false
1662 else
1663 not"
1664 },
1665 );
1666 assert_long_short_term(
1667 "%enum/embed% foo bar",
1668 indoc! {"
1669 %enum/embed%
1670 foo
1671 bar"
1672 },
1673 );
1674 }
1675
1676 #[test]
1677 fn pretty_arrays() {
1678 assert_long_short_term(
1679 "[ 1, 2, 3, 4 ]",
1680 indoc! {"
1681 [
1682 1,
1683 2,
1684 3,
1685 4
1686 ]"
1687 },
1688 );
1689 }
1690
1691 #[test]
1692 fn pretty_match() {
1693 assert_long_short_term(
1694 "match { 'A => a, 'B => b, 'C => c, }",
1695 indoc! {"
1696 match {
1697 'A =>
1698 a,
1699 'B =>
1700 b,
1701 'C =>
1702 c,
1703 }"
1704 },
1705 );
1706 }
1707
1708 #[test]
1709 fn pretty_record() {
1710 assert_long_short_term("{}", "{}");
1711 assert_long_short_term(
1712 "{ a = b, c = d, }",
1713 indoc! {"
1714 {
1715 a =
1716 b,
1717 c =
1718 d,
1719 }"
1720 },
1721 );
1722 assert_long_short_term(
1723 r#"{ a | String | force = b, c | Number | doc "" = d, }"#,
1724 indoc! {r#"
1725 {
1726 a
1727 | String
1728 | force
1729 = b,
1730 c
1731 | Number
1732 | doc ""
1733 = d,
1734 }"#
1735 },
1736 );
1737 assert_long_short_term(
1738 "{ a = b, .. }",
1739 indoc! {"
1740 {
1741 a =
1742 b,
1743 ..
1744 }"
1745 },
1746 );
1747 assert_long_short_term(
1748 r#"{ a = b, "%{a}" = c, .. }"#,
1749 indoc! {r#"
1750 {
1751 a =
1752 b,
1753 "%{a}" =
1754 c,
1755 ..
1756 }"#
1757 },
1758 );
1759 assert_long_short_term(
1760 r#"{ "=" = a, }"#,
1761 indoc! {r#"
1762 {
1763 "=" =
1764 a,
1765 }"#
1766 },
1767 );
1768 }
1769
1770 #[test]
1771 fn pretty_let() {
1772 assert_long_short_term(
1773 "let rec foo | String = c in {}",
1774 indoc! {"
1775 let rec foo
1776 | String
1777 = c
1778 in
1779 {}"
1780 },
1781 );
1782 assert_long_short_term(
1783 "let foo = c bar in {}",
1784 indoc! {"
1785 let foo
1786 = c
1787 bar
1788 in
1789 {}"
1790 },
1791 );
1792 assert_long_short_term(
1793 "let foo | String = c bar in {}",
1794 indoc! {"
1795 let foo
1796 | String
1797 = c
1798 bar
1799 in
1800 {}"
1801 },
1802 );
1803 }
1804
1805 #[test]
1806 fn pretty_multiline_strings() {
1807 let t: RichTerm = Term::StrChunks(vec![StrChunk::Literal("\n1.".to_owned())]).into();
1821 assert_eq!(format!("{t}"), "m%\"\n \n 1.\n\"%");
1822
1823 let t: RichTerm = Term::StrChunks(vec![StrChunk::Literal(
1824 "a multiline string\n\n\n\n".to_owned(),
1825 )])
1826 .into();
1827 assert_eq!(
1828 format!("{t}"),
1829 "m%\"\n a multiline string\n \n \n \n\n\"%"
1830 );
1831 }
1832
1833 #[test]
1834 fn pretty_let_pattern() {
1835 assert_long_short_term(
1836 "let foo @ { a | Bool ? true = a', b ? false, } = c in {}",
1837 indoc! {"
1838 let foo @ {
1839 a
1840 | Bool
1841 ? true
1842 = a',
1843 b
1844 ? false,
1845 }
1846 = c
1847 in
1848 {}"
1849 },
1850 );
1851 assert_long_short_term(
1852 "let foo @ { a = a', b = e @ { foo, .. }, } = c in {}",
1853 indoc! {"
1854 let foo @ {
1855 a
1856 = a',
1857 b
1858 = e @ {
1859 foo,
1860 ..
1861 },
1862 }
1863 = c
1864 in
1865 {}"
1866 },
1867 );
1868 assert_long_short_term(
1869 "let foo @ { a = a', b, } | String = c in {}",
1870 indoc! {"
1871 let foo @ {
1872 a
1873 = a',
1874 b,
1875 }
1876 | String
1877 = c
1878 in
1879 {}"
1880 },
1881 );
1882 }
1883
1884 #[test]
1885 fn pretty_fun() {
1886 assert_long_short_term(
1887 "fun x y z => x y z",
1888 indoc! {"
1889 fun
1890 x
1891 y
1892 z
1893 =>
1894 x
1895 y
1896 z"
1897 },
1898 );
1899 assert_long_short_term(
1900 "fun x @ { foo, bar ? true, } y @ { baz, } => x y z",
1901 indoc! {"
1902 fun
1903 x @ {
1904 foo,
1905 bar
1906 ? true,
1907 }
1908 y @ {
1909 baz,
1910 }
1911 =>
1912 x
1913 y
1914 z"
1915 },
1916 );
1917 }
1918
1919 #[test]
1920 fn pretty_app() {
1921 assert_long_short_term(
1922 "x y z",
1923 indoc! {"
1924 x
1925 y
1926 z"
1927 },
1928 );
1929 }
1930
1931 #[track_caller]
1938 fn assert_format_eq(s: &str) {
1939 let ty = parse_type(s);
1940 assert_eq!(s, &format!("{ty}"));
1941 }
1942
1943 #[test]
1944 fn types_pretty_printing() {
1945 assert_format_eq("Number");
1946 assert_format_eq("Number -> Number");
1947 assert_format_eq("(Number -> Number) -> (Number -> Number) -> Number -> Number");
1948 assert_format_eq("((Number -> Number) -> Number) -> Number");
1949 assert_format_eq("Number -> (forall a. a -> String) -> String");
1950
1951 assert_format_eq("{ _ : String }");
1952 assert_format_eq("{ _ : (String -> String) -> String }");
1953 assert_format_eq("{ _ | String }");
1954 assert_format_eq("{ _ | (String -> String) -> String }");
1955
1956 assert_format_eq("{ x : (Bool -> Bool) -> Bool, y : Bool }");
1957 assert_format_eq("forall r. { x : Bool, y : Bool, z : Bool; r }");
1958 assert_format_eq("{ x : Bool, y : Bool, z : Bool }");
1959
1960 assert_format_eq("[| 'a, 'b, 'c, 'd |]");
1961 assert_format_eq("forall r. [| 'tag1, 'tag2, 'tag3; r |]");
1962
1963 assert_format_eq("Array Number");
1964 assert_format_eq("Array (Array Number)");
1965 assert_format_eq("Number -> Array (Array String) -> Number");
1966 assert_format_eq("Array (Number -> Number)");
1967 assert_format_eq("Array (Array (Array Dyn) -> Number)");
1968
1969 assert_format_eq("_");
1970 assert_format_eq("_ -> _");
1971 assert_format_eq("{ x : _, y : Bool }");
1972 assert_format_eq("{ _ : _ }");
1973 }
1974
1975 fn format_short_term(input: &str, depth: usize, size: usize) -> String {
1976 let term = parse_term(input);
1977 let allocator = Allocator::bounded(depth, size);
1978 let doc: DocBuilder<_, ()> = term.pretty(&allocator);
1979 Doc::pretty(&doc, 1000).to_string()
1980 }
1981
1982 #[test]
1983 fn bounded_pretty_printing() {
1984 assert_eq!("{ hello = 1, }", &format_short_term("{hello = 1}", 1, 1));
1985 assert_eq!("{…}", &format_short_term("{hello = 1, bye = 2}", 1, 1));
1986 assert_eq!(
1987 "{ hello = 1, inner = { bye = 2, }, }",
1988 &format_short_term("{hello = 1, inner = { bye = 2 }}", 2, 2)
1989 );
1990 assert_eq!(
1991 "{ hello = 1, inner = {…}, }",
1992 &format_short_term("{hello = 1, inner = { bye = 2 }}", 1, 100)
1993 );
1994 assert_eq!(
1995 "{ hello = 1, inner = {…}, }",
1996 &format_short_term("{hello = 1, inner = { bye = 2, other = 3 }}", 100, 2)
1997 );
1998 }
1999}