I am pleased to announce the first official release of PPrint, an OCaml library for pretty-printing textual documents.
A taste of the layout language
At the heart of PPrint is a little domain-specific language of documents. This language has a well-defined semantics, which the printing engine implements. This language rests upon a small number of fundamental concepts.
There are combinators for creating atomic documents. For instance,
is a simple, unbreakable document.
There is also a concatenation operator, which joins two documents. For instance,
string "hello" ^^ string "world"
is a composite document. It is in fact equivalent to
So far, nothing very exciting. The next two combinators are more original and interesting.
The first of these combinators,
break 1, is a breakable
space. If printed in flat mode, it produces an ordinary space character;
if printed in normal mode, it produces a newline character.
Yes, there are two printing modes, namely flat mode and normal mode. The printing engine goes back and forth between these two modes. Exactly where and how the engine switches from one mode to the other is controlled by the next combinator.
The second of these combinators,
group, introduces a
choice between flat mode and normal mode. It is a document transformer:
d is a document, then
group d is a
document. When the printing engine encounters
group d, two
possibilities arise. The first possibility is to print all of
d on a single line. This is known as flat mode. The engine
tries this first (ignoring any
group combinators inside
d). If it succeeds, great. If it fails, by lack of space on
the current line, then the engine backtracks and reverts to the second
possibility, which is to simply ignore the
combinator, and just print
d. This has subtle consequences:
there might be further groups inside
d, and each of these
groups will give rise to further choices.
This gives rise to an interesting language, where
is used to indicate a choice point, and the appearance of
break is dependent upon the choice point(s) that appear
higher up in the hierarchical structure of the document. For instance,
group (string "This" ^^ break 1 ^^ string "is" ^^ break 1 ^^ string "pretty.")
will be printed either on a single line, if it fits, or on three lines. It will not be printed on two lines: there is just one choice point, so either the two breakable spaces will be broken, or none of them will. By the way, this document can be abbreviated as follows:
group (string "This" ^/^ string "is" ^/^ string "pretty.")
On the other hand, the document:
string "This" ^^ group (break 1 ^^ string "is") ^^ group (break 1 ^^ string "pretty.")
could be printed on one, two, or three lines. There are two choice
points, each of which influences one of the two breakable spaces. The
two choices are independent of one another. Each of the words in the
This is pretty. will be printed on the current
line if it fits, and on a new line otherwise. By the way, this document
can be abbreviated as follows:
flow 1 [ string "This" ; string "is" ; string "pretty." ]
There are more combinators, such as
nest, which controls
indentation, and it is relatively easy to roll your own combinators on
top of those that are provided.
One limitation of the library is that the document must be entirely built in memory before it is printed. So far, we have used the library in small- to medium-scale applications, and this has not been a problem. In principle, one could work around this limitation by adding a new document constructor whose argument is a suspended document computation.
PPrint was written by François Pottier and Nicolas Pouillard, with contributions by Yann Régis-Gianas, Gabriel Scherer, and Jonathan Protzenko.
Have fun! Feel free to make comments, suggestions, and to let me know if and how you are using this library.
A Final Example
For a larger example, here is the code of the file
PPrintTest.ml included in the distribution (with the
maximum line width adapted for this blog post):
open PPrint let document = prefix 2 1 (string "TITLE:") (string "PPrint") ^^ hardline ^^ prefix 2 1 (string "AUTHORS:") (utf8string "François Pottier and Nicolas Pouillard") ^^ hardline ^^ prefix 2 1 (string "ABSTRACT:") ( flow 1 (words "This is an adaptation of Daan Leijen's \"PPrint\" library, which itself is based on the ideas developed by Philip Wadler in \"A Prettier Printer\". For more information about Wadler's and Leijen's work, please consult the following references:") ^^ nest 2 ( twice (break 1) ^^ separate_map (break 1) (fun s -> nest 2 (url s)) [ "http://www.cs.uu.nl/~daan/pprint.html"; "http://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf"; ] ) ^^ twice (break 1) ^^ flow 1 (words "To install PPrint, type \"make -C src install\". ocamlfind is required.") ^^ twice (break 1) ^^ flow 1 (words "The documentation for PPrint is built by \"make doc\" and is found in the file doc/index.html.") ) ^^ hardline let () = ToChannel.pretty 1. 60 stdout document; flush stdout
It produces the following output:
TITLE: PPrint AUTHORS: François Pottier and Nicolas Pouillard ABSTRACT: This is an adaptation of Daan Leijen's "PPrint" library, which itself is based on the ideas developed by Philip Wadler in "A Prettier Printer". For more information about Wadler's and Leijen's work, please consult the following references: http://www.cs.uu.nl/~daan/pprint.html http://homepages.inf.ed.ac.uk/wadler/papers/prettier/ prettier.pdf To install PPrint, type "make -C src install". ocamlfind is required. The documentation for PPrint is built by "make doc" and is found in the file doc/index.html.