pretty/
lib.rs

1//! This crate defines a
2//! [Wadler-style](http://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf)
3//! pretty-printing API.
4//!
5//! Start with with the static functions of [Doc](enum.Doc.html).
6//!
7//! ## Quick start
8//!
9//! Let's pretty-print simple sexps!  We want to pretty print sexps like
10//!
11//! ```lisp
12//! (1 2 3)
13//! ```
14//! or, if the line would be too long, like
15//!
16//! ```lisp
17//! ((1)
18//!  (2 3)
19//!  (4 5 6))
20//! ```
21//!
22//! A _simple symbolic expression_ consists of a numeric _atom_ or a nested ordered _list_ of
23//! symbolic expression children.
24//!
25//! ```rust
26//! # extern crate pretty;
27//! # use pretty::*;
28//! enum SExp {
29//!     Atom(u32),
30//!     List(Vec<SExp>),
31//! }
32//! use SExp::*;
33//! # fn main() { }
34//! ```
35//!
36//! We define a simple conversion to a [Doc](enum.Doc.html).  Atoms are rendered as strings; lists
37//! are recursively rendered, with spaces between children where appropriate.  Children are
38//! [nested]() and [grouped](), allowing them to be laid out in a single line as appropriate.
39//!
40//! ```rust
41//! # extern crate pretty;
42//! # use pretty::*;
43//! # enum SExp {
44//! #     Atom(u32),
45//! #     List(Vec<SExp>),
46//! # }
47//! # use SExp::*;
48//! impl SExp {
49//!     /// Return a pretty printed format of self.
50//!     pub fn to_doc(&self) -> Doc<BoxDoc<()>> {
51//!         match *self {
52//!             Atom(ref x) => Doc::as_string(x),
53//!             List(ref xs) =>
54//!                 Doc::text("(")
55//!                     .append(Doc::intersperse(xs.into_iter().map(|x| x.to_doc()), Doc::space()).nest(1).group())
56//!                     .append(Doc::text(")"))
57//!         }
58//!     }
59//! }
60//! # fn main() { }
61//! ```
62//!
63//! Next, we convert the [Doc](enum.Doc.html) to a plain old string.
64//!
65//! ```rust
66//! # extern crate pretty;
67//! # use pretty::*;
68//! # enum SExp {
69//! #     Atom(u32),
70//! #     List(Vec<SExp>),
71//! # }
72//! # use SExp::*;
73//! # impl SExp {
74//! #     /// Return a pretty printed format of self.
75//! #     pub fn to_doc(&self) -> Doc<BoxDoc<()>> {
76//! #         match *self {
77//! #             Atom(ref x) => Doc::as_string(x),
78//! #             List(ref xs) =>
79//! #                 Doc::text("(")
80//! #                     .append(Doc::intersperse(xs.into_iter().map(|x| x.to_doc()), Doc::space()).nest(1).group())
81//! #                     .append(Doc::text(")"))
82//! #         }
83//! #     }
84//! # }
85//! impl SExp {
86//!     pub fn to_pretty(&self, width: usize) -> String {
87//!         let mut w = Vec::new();
88//!         self.to_doc().render(width, &mut w).unwrap();
89//!         String::from_utf8(w).unwrap()
90//!     }
91//! }
92//! # fn main() { }
93//! ```
94//!
95//! And finally we can test that the nesting and grouping behaves as we expected.
96//!
97//! ```rust
98//! # extern crate pretty;
99//! # use pretty::*;
100//! # enum SExp {
101//! #     Atom(u32),
102//! #     List(Vec<SExp>),
103//! # }
104//! # use SExp::*;
105//! # impl SExp {
106//! #     /// Return a pretty printed format of self.
107//! #     pub fn to_doc(&self) -> Doc<BoxDoc<()>> {
108//! #         match *self {
109//! #             Atom(ref x) => Doc::as_string(x),
110//! #             List(ref xs) =>
111//! #                 Doc::text("(")
112//! #                     .append(Doc::intersperse(xs.into_iter().map(|x| x.to_doc()), Doc::space()).nest(1).group())
113//! #                     .append(Doc::text(")"))
114//! #         }
115//! #     }
116//! # }
117//! # impl SExp {
118//! #     pub fn to_pretty(&self, width: usize) -> String {
119//! #         let mut w = Vec::new();
120//! #         self.to_doc().render(width, &mut w).unwrap();
121//! #         String::from_utf8(w).unwrap()
122//! #     }
123//! # }
124//! # fn main() {
125//! let atom = SExp::Atom(5);
126//! assert_eq!("5", atom.to_pretty(10));
127//! let list = SExp::List(vec![SExp::Atom(1), SExp::Atom(2), SExp::Atom(3)]);
128//! assert_eq!("(1 2 3)", list.to_pretty(10));
129//! assert_eq!("\
130//! (1
131//!  2
132//!  3)", list.to_pretty(5));
133//! # }
134//! ```
135//!
136//! ## Advanced usage
137//!
138//! There's a more efficient pattern that uses the [DocAllocator](trait.DocAllocator.html) trait, as
139//! implemented by [BoxAllocator](struct.BoxAllocator.html), to allocate
140//! [DocBuilder](struct.DocBuilder.html) instances.  See
141//! [examples/trees.rs](https://github.com/freebroccolo/pretty.rs/blob/master/examples/trees.rs#L39)
142//! for this approach.
143
144#[cfg(feature = "termcolor")]
145pub extern crate termcolor;
146extern crate typed_arena;
147
148use std::borrow::Cow;
149use std::fmt;
150use std::io;
151use std::ops::Deref;
152#[cfg(feature = "termcolor")]
153use termcolor::{ColorSpec, WriteColor};
154
155mod render;
156
157#[cfg(feature = "termcolor")]
158pub use self::render::TermColored;
159pub use self::render::{FmtWrite, IoWrite, Render, RenderAnnotated};
160
161/// The concrete document type. This type is not meant to be used directly. Instead use the static
162/// functions on `Doc` or the methods on an `DocAllocator`.
163///
164/// The `T` parameter is used to abstract over pointers to `Doc`. See `RefDoc` and `BoxDoc` for how
165/// it is used
166#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
167pub enum Doc<'a, T, A = ()> {
168    Nil,
169    Append(T, T),
170    Group(T),
171    Nest(usize, T),
172    Space,
173    Newline,
174    Text(Cow<'a, str>),
175    Annotated(A, T),
176}
177
178impl<'a, T, A> Doc<'a, T, A> {
179    /// An empty document.
180    #[inline]
181    pub fn nil() -> Doc<'a, T, A> {
182        Doc::Nil
183    }
184
185    /// The text `t.to_string()`.
186    ///
187    /// The given text must not contain line breaks.
188    #[inline]
189    pub fn as_string<U: ToString>(data: U) -> Doc<'a, T, A> {
190        Doc::text(data.to_string())
191    }
192
193    /// A single newline.
194    #[inline]
195    pub fn newline() -> Doc<'a, T, A> {
196        Doc::Newline
197    }
198
199    /// The given text, which must not contain line breaks.
200    #[inline]
201    pub fn text<U: Into<Cow<'a, str>>>(data: U) -> Doc<'a, T, A> {
202        Doc::Text(data.into())
203    }
204
205    /// A space.
206    #[inline]
207    pub fn space() -> Doc<'a, T, A> {
208        Doc::Space
209    }
210}
211
212impl<'a, A> Doc<'a, BoxDoc<'a, A>, A> {
213    /// Append the given document after this document.
214    #[inline]
215    pub fn append<D>(self, that: D) -> Doc<'a, BoxDoc<'a, A>, A>
216    where
217        D: Into<Doc<'a, BoxDoc<'a, A>, A>>,
218    {
219        DocBuilder(&BOX_ALLOCATOR, self).append(that).into()
220    }
221
222    /// A single document concatenating all the given documents.
223    #[inline]
224    pub fn concat<I>(docs: I) -> Doc<'a, BoxDoc<'a, A>, A>
225    where
226        I: IntoIterator,
227        I::Item: Into<Doc<'a, BoxDoc<'a, A>, A>>,
228    {
229        docs.into_iter().fold(Doc::nil(), |a, b| a.append(b))
230    }
231
232    /// A single document interspersing the given separator `S` between the given documents.  For
233    /// example, if the documents are `[A, B, C, ..., Z]`, this yields `[A, S, B, S, C, S, ..., S, Z]`.
234    ///
235    /// Compare [the `intersperse` method from the `itertools` crate](https://docs.rs/itertools/0.5.9/itertools/trait.Itertools.html#method.intersperse).
236    #[inline]
237    pub fn intersperse<I, S>(docs: I, separator: S) -> Doc<'a, BoxDoc<'a, A>, A>
238    where
239        I: IntoIterator,
240        I::Item: Into<Doc<'a, BoxDoc<'a, A>, A>>,
241        S: Into<Doc<'a, BoxDoc<'a, A>, A>> + Clone,
242        A: Clone,
243    {
244        let mut result = Doc::nil();
245        let mut iter = docs.into_iter();
246
247        if let Some(first) = iter.next() {
248            result = result.append(first);
249
250            for doc in iter {
251                result = result.append(separator.clone());
252                result = result.append(doc);
253            }
254        }
255
256        result
257    }
258
259    /// Mark this document as a group.
260    ///
261    /// Groups are layed out on a single line if possible.  Within a group, all basic documents with
262    /// several possible layouts are assigned the same layout, that is, they are all layed out
263    /// horizontally and combined into a one single line, or they are each layed out on their own
264    /// line.
265    #[inline]
266    pub fn group(self) -> Doc<'a, BoxDoc<'a, A>, A> {
267        DocBuilder(&BOX_ALLOCATOR, self).group().into()
268    }
269
270    /// Increase the indentation level of this document.
271    #[inline]
272    pub fn nest(self, offset: usize) -> Doc<'a, BoxDoc<'a, A>, A> {
273        DocBuilder(&BOX_ALLOCATOR, self).nest(offset).into()
274    }
275
276    #[inline]
277    pub fn annotate(self, ann: A) -> Doc<'a, BoxDoc<'a, A>, A> {
278        DocBuilder(&BOX_ALLOCATOR, self).annotate(ann).into()
279    }
280}
281
282impl<'a, T, A, S> From<S> for Doc<'a, T, A>
283where
284    S: Into<Cow<'a, str>>,
285{
286    fn from(s: S) -> Doc<'a, T, A> {
287        Doc::Text(s.into())
288    }
289}
290
291pub struct Pretty<'a, T, A>
292where
293    A: 'a,
294    T: 'a,
295{
296    doc: &'a Doc<'a, T, A>,
297    width: usize,
298}
299
300impl<'a, T, A> fmt::Display for Pretty<'a, T, A>
301where
302    T: Deref<Target = Doc<'a, T, A>>,
303{
304    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
305        self.doc.render_fmt(self.width, f)
306    }
307}
308
309impl<'a, T, A> Doc<'a, T, A> {
310    /// Writes a rendered document to a `std::io::Write` object.
311    #[inline]
312    pub fn render<'b, W>(&'b self, width: usize, out: &mut W) -> io::Result<()>
313    where
314        T: Deref<Target = Doc<'b, T, A>>,
315        W: ?Sized + io::Write,
316    {
317        self.render_raw(width, &mut IoWrite::new(out))
318    }
319
320    /// Writes a rendered document to a `std::fmt::Write` object.
321    #[inline]
322    pub fn render_fmt<'b, W>(&'b self, width: usize, out: &mut W) -> fmt::Result
323    where
324        T: Deref<Target = Doc<'b, T, A>>,
325        W: ?Sized + fmt::Write,
326    {
327        self.render_raw(width, &mut FmtWrite::new(out))
328    }
329
330    /// Writes a rendered document to a `RenderAnnotated<A>` object.
331    #[inline]
332    pub fn render_raw<'b, W>(&'b self, width: usize, out: &mut W) -> Result<(), W::Error>
333    where
334        T: Deref<Target = Doc<'b, T, A>>,
335        W: ?Sized + render::RenderAnnotated<A>,
336    {
337        render::best(self, width, out)
338    }
339
340    /// Returns a value which implements `std::fmt::Display`
341    ///
342    /// ```
343    /// use pretty::Doc;
344    /// let doc = Doc::<_>::group(
345    ///     Doc::text("hello").append(Doc::space()).append(Doc::text("world"))
346    /// );
347    /// assert_eq!(format!("{}", doc.pretty(80)), "hello world");
348    /// ```
349    #[inline]
350    pub fn pretty<'b>(&'b self, width: usize) -> Pretty<'b, T, A>
351    where
352        T: Deref<Target = Doc<'b, T, A>>,
353    {
354        Pretty { doc: self, width }
355    }
356}
357
358#[cfg(feature = "termcolor")]
359impl<'a, T> Doc<'a, T, ColorSpec> {
360    #[inline]
361    pub fn render_colored<'b, W>(&'b self, width: usize, out: W) -> io::Result<()>
362    where
363        T: Deref<Target = Doc<'b, T, ColorSpec>>,
364        W: WriteColor,
365    {
366        render::best(self, width, &mut TermColored::new(out))
367    }
368}
369
370#[derive(Clone, Eq, Ord, PartialEq, PartialOrd)]
371pub struct BoxDoc<'a, A>(Box<Doc<'a, BoxDoc<'a, A>, A>>);
372
373impl<'a, A> fmt::Debug for BoxDoc<'a, A>
374where
375    A: fmt::Debug,
376{
377    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
378        self.0.fmt(f)
379    }
380}
381
382impl<'a, A> BoxDoc<'a, A> {
383    fn new(doc: Doc<'a, BoxDoc<'a, A>, A>) -> BoxDoc<'a, A> {
384        BoxDoc(Box::new(doc))
385    }
386}
387
388impl<'a, A> Deref for BoxDoc<'a, A> {
389    type Target = Doc<'a, BoxDoc<'a, A>, A>;
390
391    fn deref(&self) -> &Self::Target {
392        &self.0
393    }
394}
395
396/// The `DocBuilder` type allows for convenient appending of documents even for arena allocated
397/// documents by storing the arena inline.
398#[derive(Eq, Ord, PartialEq, PartialOrd)]
399pub struct DocBuilder<'a, D, A = ()>(pub &'a D, pub Doc<'a, D::Doc, A>)
400where
401    D: ?Sized + DocAllocator<'a, A> + 'a;
402
403impl<'a, A, D> Clone for DocBuilder<'a, D, A>
404where
405    A: Clone,
406    D: DocAllocator<'a, A> + 'a,
407    D::Doc: Clone,
408{
409    fn clone(&self) -> Self {
410        DocBuilder(self.0, self.1.clone())
411    }
412}
413
414impl<'a, D, A> Into<Doc<'a, D::Doc, A>> for DocBuilder<'a, D, A>
415where
416    D: ?Sized + DocAllocator<'a, A>,
417{
418    fn into(self) -> Doc<'a, D::Doc, A> {
419        self.1
420    }
421}
422
423/// The `DocAllocator` trait abstracts over a type which can allocate (pointers to) `Doc`.
424pub trait DocAllocator<'a, A = ()> {
425    type Doc: Deref<Target = Doc<'a, Self::Doc, A>>;
426
427    fn alloc(&'a self, Doc<'a, Self::Doc, A>) -> Self::Doc;
428
429    /// Allocate an empty document.
430    #[inline]
431    fn nil(&'a self) -> DocBuilder<'a, Self, A> {
432        DocBuilder(self, Doc::Nil)
433    }
434
435    /// Allocate a single newline.
436    #[inline]
437    fn newline(&'a self) -> DocBuilder<'a, Self, A> {
438        DocBuilder(self, Doc::Newline)
439    }
440
441    /// Allocate a single space.
442    #[inline]
443    fn space(&'a self) -> DocBuilder<'a, Self, A> {
444        DocBuilder(self, Doc::Space)
445    }
446
447    /// Allocate a document containing the text `t.to_string()`.
448    ///
449    /// The given text must not contain line breaks.
450    #[inline]
451    fn as_string<U: ToString>(&'a self, data: U) -> DocBuilder<'a, Self, A> {
452        self.text(data.to_string())
453    }
454
455    /// Allocate a document containing the given text.
456    ///
457    /// The given text must not contain line breaks.
458    #[inline]
459    fn text<U: Into<Cow<'a, str>>>(&'a self, data: U) -> DocBuilder<'a, Self, A> {
460        DocBuilder(self, Doc::Text(data.into()))
461    }
462
463    /// Allocate a document concatenating the given documents.
464    #[inline]
465    fn concat<I>(&'a self, docs: I) -> DocBuilder<'a, Self, A>
466    where
467        I: IntoIterator,
468        I::Item: Into<Doc<'a, Self::Doc, A>>,
469    {
470        docs.into_iter().fold(self.nil(), |a, b| a.append(b))
471    }
472
473    /// Allocate a document that intersperses the given separator `S` between the given documents
474    /// `[A, B, C, ..., Z]`, yielding `[A, S, B, S, C, S, ..., S, Z]`.
475    ///
476    /// Compare [the `intersperse` method from the `itertools` crate](https://docs.rs/itertools/0.5.9/itertools/trait.Itertools.html#method.intersperse).
477    #[inline]
478    fn intersperse<I, S>(&'a self, docs: I, separator: S) -> DocBuilder<'a, Self, A>
479    where
480        I: IntoIterator,
481        I::Item: Into<Doc<'a, Self::Doc, A>>,
482        S: Into<Doc<'a, Self::Doc, A>> + Clone,
483    {
484        let mut result = self.nil();
485        let mut iter = docs.into_iter();
486
487        if let Some(first) = iter.next() {
488            result = result.append(first);
489
490            for doc in iter {
491                result = result.append(separator.clone());
492                result = result.append(doc);
493            }
494        }
495
496        result
497    }
498}
499
500impl<'a, 's, D, A> DocBuilder<'a, D, A>
501where
502    D: ?Sized + DocAllocator<'a, A>,
503{
504    /// Append the given document after this document.
505    #[inline]
506    pub fn append<E>(self, that: E) -> DocBuilder<'a, D, A>
507    where
508        E: Into<Doc<'a, D::Doc, A>>,
509    {
510        let DocBuilder(allocator, this) = self;
511        let that = that.into();
512        let doc = match (this, that) {
513            (Doc::Nil, that) => that,
514            (this, Doc::Nil) => this,
515            (this, that) => Doc::Append(allocator.alloc(this), allocator.alloc(that)),
516        };
517        DocBuilder(allocator, doc)
518    }
519
520    /// Mark this document as a group.
521    ///
522    /// Groups are layed out on a single line if possible.  Within a group, all basic documents with
523    /// several possible layouts are assigned the same layout, that is, they are all layed out
524    /// horizontally and combined into a one single line, or they are each layed out on their own
525    /// line.
526    #[inline]
527    pub fn group(self) -> DocBuilder<'a, D, A> {
528        let DocBuilder(allocator, this) = self;
529        DocBuilder(allocator, Doc::Group(allocator.alloc(this)))
530    }
531
532    /// Increase the indentation level of this document.
533    #[inline]
534    pub fn nest(self, offset: usize) -> DocBuilder<'a, D, A> {
535        if offset == 0 {
536            return self;
537        }
538        let DocBuilder(allocator, this) = self;
539        DocBuilder(allocator, Doc::Nest(offset, allocator.alloc(this)))
540    }
541
542    #[inline]
543    pub fn annotate(self, ann: A) -> DocBuilder<'a, D, A> {
544        let DocBuilder(allocator, this) = self;
545        DocBuilder(allocator, Doc::Annotated(ann, allocator.alloc(this)))
546    }
547}
548
549/// Newtype wrapper for `&Doc`
550#[derive(Clone, Eq, Ord, PartialEq, PartialOrd)]
551pub struct RefDoc<'a, A: 'a>(&'a Doc<'a, RefDoc<'a, A>, A>);
552
553impl<'a, A> fmt::Debug for RefDoc<'a, A>
554where
555    A: fmt::Debug,
556{
557    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
558        self.0.fmt(f)
559    }
560}
561
562impl<'a, A> Deref for RefDoc<'a, A> {
563    type Target = Doc<'a, RefDoc<'a, A>, A>;
564
565    fn deref(&self) -> &Self::Target {
566        &self.0
567    }
568}
569
570/// An arena which can be used to allocate `Doc` values.
571pub type Arena<'a, A = ()> = typed_arena::Arena<Doc<'a, RefDoc<'a, A>, A>>;
572
573impl<'a, D, A> DocAllocator<'a, A> for &'a D
574where
575    D: ?Sized + DocAllocator<'a, A>,
576{
577    type Doc = D::Doc;
578
579    #[inline]
580    fn alloc(&'a self, doc: Doc<'a, Self::Doc, A>) -> Self::Doc {
581        (**self).alloc(doc)
582    }
583}
584
585impl<'a, A> DocAllocator<'a, A> for Arena<'a, A> {
586    type Doc = RefDoc<'a, A>;
587
588    #[inline]
589    fn alloc(&'a self, doc: Doc<'a, Self::Doc, A>) -> Self::Doc {
590        RefDoc(match doc {
591            // Return 'static references for unit variants to save a small
592            // amount of space in the arena
593            Doc::Nil => &Doc::Nil,
594            Doc::Space => &Doc::Space,
595            Doc::Newline => &Doc::Newline,
596            _ => Arena::alloc(self, doc),
597        })
598    }
599}
600
601pub struct BoxAllocator;
602
603static BOX_ALLOCATOR: BoxAllocator = BoxAllocator;
604
605impl<'a, A> DocAllocator<'a, A> for BoxAllocator {
606    type Doc = BoxDoc<'a, A>;
607
608    #[inline]
609    fn alloc(&'a self, doc: Doc<'a, Self::Doc, A>) -> Self::Doc {
610        BoxDoc::new(doc)
611    }
612}
613
614#[cfg(test)]
615mod tests {
616    use super::*;
617
618    macro_rules! test {
619        ($size:expr, $actual:expr, $expected:expr) => {
620            let mut s = String::new();
621            $actual.render_fmt($size, &mut s).unwrap();
622            assert_eq!(s, $expected);
623        };
624        ($actual:expr, $expected:expr) => {
625            test!(70, $actual, $expected)
626        };
627    }
628
629    #[test]
630    fn box_doc_inference() {
631        let doc = Doc::<_>::group(
632            Doc::text("test")
633                .append(Doc::space())
634                .append(Doc::text("test")),
635        );
636
637        test!(doc, "test test");
638    }
639
640    #[test]
641    fn newline_in_text() {
642        let doc = Doc::<_>::group(
643            Doc::text("test").append(
644                Doc::space()
645                    .append(Doc::text("\"test\n     test\""))
646                    .nest(4),
647            ),
648        );
649
650        test!(5, doc, "test\n    \"test\n     test\"");
651    }
652
653    #[test]
654    fn forced_newline() {
655        let doc = Doc::<_>::group(
656            Doc::text("test")
657                .append(Doc::newline())
658                .append(Doc::text("test")),
659        );
660
661        test!(doc, "test\ntest");
662    }
663
664    #[test]
665    fn space_do_not_reset_pos() {
666        let doc = Doc::<_>::group(Doc::text("test").append(Doc::space()))
667            .append(Doc::text("test"))
668            .append(Doc::group(Doc::space()).append(Doc::text("test")));
669
670        test!(9, doc, "test test\ntest");
671    }
672
673    // Tests that the `Doc::newline()` does not cause the rest of document to think that it fits on
674    // a single line but instead breaks on the `Doc::space()` to fit with 6 columns
675    #[test]
676    fn newline_does_not_cause_next_line_to_be_to_long() {
677        let doc = Doc::<_>::group(
678            Doc::text("test").append(Doc::newline()).append(
679                Doc::text("test")
680                    .append(Doc::space())
681                    .append(Doc::text("test")),
682            ),
683        );
684
685        test!(6, doc, "test\ntest\ntest");
686    }
687
688    #[test]
689    fn block() {
690        let doc = Doc::<_>::group(
691            Doc::text("{")
692                .append(
693                    Doc::space()
694                        .append(Doc::text("test"))
695                        .append(Doc::space())
696                        .append(Doc::text("test"))
697                        .nest(2),
698                )
699                .append(Doc::space())
700                .append(Doc::text("}")),
701        );
702
703        test!(5, doc, "{\n  test\n  test\n}");
704    }
705
706    #[test]
707    fn annotation_no_panic() {
708        let doc = Doc::group(
709            Doc::text("test")
710                .annotate(())
711                .append(Doc::newline())
712                .annotate(())
713                .append(Doc::text("test")),
714        );
715
716        test!(doc, "test\ntest");
717    }
718}