Pretty's API

Table of Contents

This document describes the API of the library.

1 Usage patterns

The first step is to construct a document. Larger documents are the result of combining smaller documents. The smallest documents are called "primitives".

When a document is ready, it can be converted to a layout for a particular page width. A layout can be written to an output stream, converted to a string, or traversed arbitrarily ("visited").

2 Preliminaries

Pretty is contained in a single header file:

#include <pretty.hpp>

All of Pretty's public interface is in the pretty namespace. For convenience, the rest of this document assumes

using namespace pretty;

3 Documents

Documents are constructed via combinators. Once constructed, a document is immutable (it can't be changed).

Documents are cheap to copy and move.

3.1 Document primitives

These documents are the basis for more complex documents.

3.1.1 nil

  • Doc{}
  • Doc{Doc::Nil{}}
  • nil()

nil() is the empty document.

Example:

Doc d = nil();

3.1.2 char

  • Doc{Doc::Char{}, 'a'}
  • char_('a')

char_(c) is the document consisting of the single character c.

Example:

Doc d = char_('x');

3.1.3 line

  • Doc{Doc::Line{}}
  • line()

line() is the document consisting of a new-line.

Example:

Doc d = line();

3.1.4 text

  • Doc{Doc::Text{}, "hello"}
  • text("hello")

text(s) is the document consisting of the string s.

Generally speaking, s should not contain whitespace since it will not be handled by the pretty-printing engine.

Example:

Doc d = text("hello");

3.1.5 concat

  • Doc{Doc::Concat{}, d1, d2}
  • concat(d1, d2)
  • d1 + d2

d1 + d2 is the document consisting of the document d1 followed by the document d2.

Example:

Doc d = text("AAA") + text("BBB");

3.1.6 nest

  • Doc{Doc::Nest{}, i, d}
  • nest(i, d)

nest(i, d) is the document d except every line after the first new-line is indented by i characters.

Example:

Doc d1 = text("AAA") + line() + text("BBB") + line() + text("CCC");
Doc d2 = nest(2, d1);

When formatted (with sufficient width), d2 produces the output:

AAA
  BBB
  CCC

3.1.7 group

  • Doc{Doc::Group{}, d}
  • group(d)

All the document primitives so far can only be formatted in a single way. This primitive differs in an important way.

Given a document d, group(d) is a document that can be formatted in one of two ways: d is either flattened so that all new-lines become single spaces, or it's unchanged. The choice is made during layout: the flattened document is chosen if it fits in the available space.

Example:

Doc d = group(text("AAA") + line() + text("BBB") + line() + text("CCC"));

Given a page width of 20, d produces the output:

AAA BBB CCC

However, given a page width of 8, the output is:

AAA
BBB
CCC

3.2 Building documents

3.2.1 cut

cut() is equivalent to group(line()).

Example:

text("AAA") + cut() + text("BBB") + cut() + text("CCC") + cut() + text("DDD")

formatted to a width of 8 is

AAA BBB
CCC DDD

3.2.2 space

space() is char(' ').

3.2.3 beside

beside(d1, d2) is d1 + space() + d2.

3.2.4 above

above(d1, d2) is d1 + line() + d2.

3.2.5 bracket

bracket(left, d, right) nests d between the strings left and right.

Example:

bracket("{", above(text("AAA"), text("BBB")), "}")

formatted to a width of 10 is

{
  AAA
  BBB
}

3.2.6 fold

template <typename F, std::same_as<Doc>... Ds>
Doc fold(F, Ds const &...);

template <std::input_iterator I, typename F>
Doc fold(I begin, I end, F);

A right-fold on a sequence of documents. Folding over an empty sequence produces the nil() document.

3.2.7 spread

template <std::same_as<Doc>... Ds>
Doc spread(Ds const &...);

template <std::input_iterator I>
Doc spread(I begin, I end);

spread(d1, d2, ...) is fold(beside, d1, d2, ...).

3.2.8 stack

template <std::same_as<Doc>... Ds>
Doc stack(Ds const &...);

template <std::input_iterator I>
Doc stack(I begin, I end);

stack(d1, d2, ...) is fold(above, d1, d2, ...).

3.2.9 fill

template <std::same_as<Doc>... Ds>
Doc fill(Ds const &...);

template <std::input_iterator I>
Doc fill(I begin, I end);

Quoting Wadler, fill

[…] collapes a list of documents into a document. It puts a space between two documents when this leads to a reasonable layout, and a newline otherwise.

3.2.10 fill_words

Doc fill_words(std::string const &);

fill_words is a document with as many words as possible fit on each line.

A word is considered to be a sequence of one or more non-whitespace ASCII characters.

4 Layouts

After a document is constructed, it can be formatted to fit to a particular width.

Layout Doc::fit_to(std::size_t width) const;

A document can be fit to a layout any number of times. Layouts are immutable and cheap to copy and move.

4.1 Layout output

A layout can be converted to a string:

std::string Layout::to_string() const;

or written to an output stream:

std::ostream& operator<<(std::ostream&, Layout const&);

4.2 Layout traversal

For advanced users, layouts can also be traversed using the visitor pattern.

class Visitor {
 public:
  Visitor() = default;
  Visitor(Visitor const &) = delete;
  Visitor(Visitor &&) = delete;

  Visitor &operator=(Visitor const &) = delete;
  Visitor &operator=(Visitor &&) = delete;

  virtual ~Visitor() = default;

  virtual void nil() = 0;

  virtual void char_(char) = 0;

  virtual void text(std::string const &) = 0;

  virtual void line(std::size_t) = 0;
};
void Layout::visit(Visitor&) const;

For convenience, a function can be invoked for each line of output in the layout:

template <typename F>
void each_line(Layout const&, F);

Author: Jesse Haber-Kucharsky

Created: 2022-06-12 Sun 15:38