PPrint:   A Universal Pretty-Printer
1 Getting Started with PPrint
2 Abstract Documents
doc?
3 Library Documentation
3.1 Rendering Documents
pretty-print
pretty-format
pretty-markup
current-page-width
3.2 Basic Documents
empty
char
text
nest
label
markup
group
line
break
soft-line
soft-break
3.3 Compound Documents
h-append
hs-append
v-append
vs-append
vb-append
vsb-append
3.4 List Utilities
h-concat
hs-concat
v-concat
vs-concat
v-concat/  s
vb-concat
vsb-concat
vb-concat/  s
apply-infix
3.5 Fillers
fill
fill/  break
3.6 Context-Sensitive Alignment
align
hang
indent
3.7 Useful Constants
comma
semi
colon
lparen
rparen
lbracket
rbracket
lbrace
rbrace
langle
rangle
space
ellipsis
squote
dquote
dot
backslash
equals
4 Haskell Compatibility Library
empty
char
text
nest
group
line
linebreak
softline
softbreak
<>
<+  >
<$>
</  >
<$$>
</  /  >
hcat
hsep
vsep
fill-sep
sep
vcat
fill-cat
cat
punctuate
fill
fill-break
align
hang
indent
comma
semi
colon
lparen
rparen
lbrace
rbrace
lbracket
rbracket
langle
rangle
space
ellipsis
squote
dquote
dot
backslash
equals
5 Design Notes
5.1 History
5.2 Mercury Port
5.3 Racket Port
5.4 Modification History
Bibliography
7.0

PPrint: A Universal Pretty-Printer

by Dave Herman (dherman at ccs dot neu dot edu)

PPrint is a library for pretty-printing: generating textual representations formatted to fit as well as possible on a fixed-width device such as a text editor or printer. While Racket provides an excellent pretty-printer for Racket code, this package provides a more general library for pretty-printing any text.

    1 Getting Started with PPrint

    2 Abstract Documents

    3 Library Documentation

      3.1 Rendering Documents

      3.2 Basic Documents

      3.3 Compound Documents

      3.4 List Utilities

      3.5 Fillers

      3.6 Context-Sensitive Alignment

      3.7 Useful Constants

    4 Haskell Compatibility Library

    5 Design Notes

      5.1 History

      5.2 Mercury Port

      5.3 Racket Port

      5.4 Modification History

    Bibliography

1 Getting Started with PPrint

To use PPrint, first require it from the package catalog:

(require pprint)

Here’s a simple example of pretty-printing a fragment of code.

Example:
> (pretty-print
   (v-append
    (nest 4 (v-append (text "while (true) {")
                      (text "f();")
                      (nest 4 (v-append (text "if (done())")
                                        (text "exit();")))))
    (text "}")))

while (true) {

    f();

    if (done())

        exit();

}

Things to notice about this example:

2 Abstract Documents

Formatting text in PPrint involves creating an "abstract document" or doc, which encapsulates formatting information for the pretty printer. The library functions of PPrint build and combine docs, which can then be rendered for pretty printing (see Rendering Documents).

procedure

(doc? x)  boolean?

  x : any
Determines whether a value is a member of the doc datatype.

When using the markup constructor, the doc datatype may be thought of as a parameterized type doc a for arbitrary markup of type a. See the documentation for markup for details.

3 Library Documentation

3.1 Rendering Documents

procedure

(pretty-print d [out width])  any

  d : doc?
  out : output-port? = (current-output-port)
  width : (or/c #f natural-number/c) = (current-page-width)
Pretty prints the doc d to the output out with a maximum page width of width. If width is #f, the page width is considered infinite.

procedure

(pretty-format d [width])  string?

  d : doc?
  width : (or/c #f natural-number/c) = (current-page-width)
Pretty prints the doc d to a string with a maximum page width of width. If width is #f, the page width is considered infinite.

procedure

(pretty-markup d combine [width])  (or/c string? a)

  d : doc?
  combine : ((or/c string? a) (or/c string? a) -> (or/c string? a))
  width : (or/c #f natural-number/c) = (current-page-width)
Pretty prints the doc d to an instance of type a, which is determined by the type of the markup nodes in d, with a maximum page width of width. If width is #f, the page width is considered infinite.

The process of generating the markup relies on the ability to concatenate strings or markup, and this concatenation is dependent on the type a. So the combine argument is required in order to concatenate fragments of marked-up text.

parameter

(current-page-width)  (or/c #f natural-number/c)

(current-page-width w)  void?
  w : (or/c #f natural-number/c)
A parameter specifying the default maximum page width, in columns, for pretty printing. If #f, the page width is considered infinite.

3.2 Basic Documents

value

empty : doc?

The empty document, which contains the empty string.

procedure

(char c)  doc?

  c : char?
Constructs a document containing the single character c.

procedure

(text s)  doc?

  s : string?
Constructs a document containing the fixed string s.

procedure

(nest n d)  doc?

  n : natural-number/c
  d : doc?
Constructs a document like d but with the current indentation level increased by n.

NOTE: The nest combinator does not affect the current line’s indentation. Indentation is only inserted after a line or a break.

Examples:
> (pretty-print (nest 4 (text "not indented")))

not indented

> (pretty-print (nest 4 (h-append (text "not indented")
                                  line
                                  (text "indented"))))

not indented

    indented

procedure

(label s d)  doc?

  s : string?
  d : doc?
Constructs a document like d but with the current indentation suffixed by the string s.

procedure

(markup f d)  (doc a)

  f : ((or/c string? a) -> (or/c string? a))
  d : (doc a)
Creates a document node with a markup transformer, which is applied by pretty-markup to produce a pretty-printed document with markup information. The markup is assumed not to affect the width of the string. This allows you, for example, to produce X-expressions from pretty-printed source.

Examples:
(define (empty-xexpr? x)
  (or (null? x) (equal? x "")))

 

(define (combine x1 x2)
  (cond
    [(empty-xexpr? x1) x2]
    [(empty-xexpr? x2) x1]
    [else (list x1 x2)]))

 

> (pretty-markup (markup (λ (x) `(em ,x)) (text "hi!")) combine)

'(em "hi!")

procedure

(group d)  doc?

  d : doc?
Creates a document like d but with all line breaks removed, if it fits on a single line.

value

line : doc?

A document containing a line break, which is replaced with a single space when placed in the context of a group.

value

break : doc?

A document containing a line break, which is replaced with the empty string when placed in the context of a group.

value

soft-line : doc?

Equivalent to (group line).

value

soft-break : doc?

Equivalent to (group break).

3.3 Compound Documents

procedure

(h-append d ...)  doc?

  d : doc?
Concatenates documents d ....

procedure

(hs-append d ...)  doc?

  d : doc?
Concatenates documents d ... with successive pairs of documents separated by space.

procedure

(v-append d ...)  doc?

  d : doc?
Concatenates documents d ... with successive pairs of documents separated by line.

procedure

(vs-append d ...)  doc?

  d : doc?
Concatenates documents d ... with successive pairs of documents separated by soft-line.

procedure

(vb-append d ...)  doc?

  d : doc?
Concatenates documents d ... with successive pairs of documents separated by break.

procedure

(vsb-append d ...)  doc?

  d : doc?
Concatenates documents d ... with successive pairs of documents separated by soft-break.

3.4 List Utilities

procedure

(h-concat ds)  doc?

  ds : (listof doc?)
Concatenates documents ds.

procedure

(hs-concat ds)  doc?

  ds : (listof doc?)
Concatenates documents ds with successive pairs of documents separated by space.

procedure

(v-concat ds)  doc?

  ds : (listof doc?)
Concatenates documents ds with successive pairs of documents separated by line.

procedure

(vs-concat ds)  doc?

  ds : (listof doc?)
Concatenates documents ds with successive pairs of documents separated by soft-line.

procedure

(v-concat/s ds)  doc?

  ds : (listof doc?)
Concatenates documents ds with successive pairs of documents separated by spaces if they all fit on one line; otherwise concatenates them vertically. Equivalent to (group (v-concat ds)).

procedure

(vb-concat ds)  doc?

  ds : (listof doc?)
Concatenates documents ds with successive pairs of documents separated by break.

procedure

(vsb-concat ds)  doc?

  ds : (listof doc?)
Concatenates documents ds with successive pairs of documents separated by soft-break.

procedure

(vb-concat/s ds)  doc?

  ds : (listof doc?)
Concatenates documents ds if they all fit on one line; otherwise concatenates them vertically. Equivalent to (group (vb-concat ds)).

procedure

(apply-infix d ds)  (listof doc?)

  d : doc?
  ds : (listof doc?)
Concatenates documents ds with successive pairs of documents separated by d.

3.5 Fillers

procedure

(fill n d)  doc?

  n : natural-number/c
  d : doc?
Creates a document like d but with enough spaces to pad its width to n, or no spaces if the width is already greater than or equal to n.

Example:
> (pretty-print
   (hs-append
    (text "let")
    (align (vb-append (hs-append (fill 6 (text "empty"))
                                 (text "::")
                                 (text "Doc"))
                      (hs-append (fill 6 (text "nest"))
                                 (text "::")
                                 (text "Int -> Doc -> Doc"))
                      (hs-append (fill 6 (text "linebreak"))
                                 (text "::")
                                 (text "Doc"))))))

let empty  :: Doc

    nest   :: Int -> Doc -> Doc

    linebreak :: Doc

procedure

(fill/break n d)  doc?

  n : natural-number/c
  d : doc?
Creates a document like d but with enough spaces to pad its width to n, or if the width is already n or greater, increases the nesting level by n and appends a line.

Example:
> (pretty-print
   (hs-append
    (text "let")
    (align (vb-append (hs-append (fill/break 6 (text "empty"))
                                 (text "::")
                                 (text "Doc"))
                      (hs-append (fill/break 6 (text "nest"))
                                 (text "::")
                                 (text "Int -> Doc -> Doc"))
                      (hs-append (fill/break 6 (text "linebreak"))
                                 (text "::")
                                 (text "Doc"))))))

let empty  :: Doc

    nest   :: Int -> Doc -> Doc

    linebreak

           :: Doc

3.6 Context-Sensitive Alignment

The alignment operators were introduced in Daan Leijen’s PPrint library for Haskell. These are useful in practice but more expensive than other operations. They determine their layout relative to the current column.

procedure

(align d)  doc?

  d : doc?
Creates a document like d but with the nesting level set to the current column.

procedure

(hang n d)  doc?

  n : natural-number/c
  d : doc?
Creates a document like d but with the nesting level set to the current column plus n. Equivalent to (align (nest n d)).

procedure

(indent n d)  doc?

  n : natural-number/c
  d : doc?
Creates a document like d but indented by n spaces from the current column.

3.7 Useful Constants

value

comma : doc?

(char #\,)

value

semi : doc?

(char #\;)

value

colon : doc?

(char #\:)

value

lparen : doc?

(char #\()

value

rparen : doc?

(char #\))

value

lbracket : doc?

(char #\[)

value

rbracket : doc?

(char #\])

value

lbrace : doc?

(char #\{)

value

rbrace : doc?

(char #\})

value

langle : doc?

(char #\<)

value

rangle : doc?

(char #\>)

value

space : doc?

(char #\space)

value

ellipsis : doc?

(text "...")

value

squote : doc?

(char #\')

value

dquote : doc?

(char #\")

value

dot : doc?

(char #\.)

value

backslash : doc?

(char #\\)

value

equals : doc?

(char #\=)

4 Haskell Compatibility Library

(require pprint/haskell)

For those who are more familiar with the names in the Haskell library, this library is provided as a compatibility mode. (This might be useful for porting existing Haskell code, for example.)

value

empty : doc?

Same as empty.

value

char : doc?

Same as char.

value

text : doc?

Same as text.

value

nest : (natural-number/c doc? -> doc?)

Same as nest.

value

group : (doc? -> doc?)

Same as group.

value

line : doc?

Same as line.

value

linebreak : doc?

Same as break.

value

softline : doc?

Same as soft-line.

value

softbreak : doc?

Same as soft-break.

value

<> : (doc? ... -> doc?)

Same as h-append.

value

<+> : (doc? ... -> doc?)

Same as hs-append.

value

<$> : (doc? ... -> doc?)

Same as v-append.

value

</> : (doc? ... -> doc?)

Same as vs-append.

value

<$$> : (doc? ... -> doc?)

Same as vb-append.

value

<//> : (doc? ... -> doc?)

Same as vsb-append.

value

hcat : ((listof doc?) -> doc?)

Same as h-concat.

value

hsep : ((listof doc?) -> doc?)

Same as hs-concat.

value

vsep : ((listof doc?) -> doc?)

Same as v-concat.

value

fill-sep : ((listof doc?) -> doc?)

Same as vs-concat.

value

sep : ((listof doc?) -> doc?)

Same as v-concat/s.

value

vcat : ((listof doc?) -> doc?)

Same as vb-concat.

value

fill-cat : ((listof doc?) -> doc?)

Same as vsb-concat.

value

cat : ((listof doc?) -> doc?)

Same as vb-concat/s.

value

punctuate : (doc? (listof doc?) -> doc?)

Same as apply-infix.

value

fill : (natural-number/c doc? -> doc?)

Same as fill.

value

fill-break : (natural-number/c doc? -> doc?)

Same as fill/break.

value

align : (doc? -> doc?)

Same as align.

value

hang : (natural-number/c doc? -> doc?)

Same as hang.

value

indent : (natural-number/c doc? -> doc?)

Same as indent.

value

comma : doc?

Same as comma.

value

semi : doc?

Same as semi.

value

colon : doc?

Same as colon.

value

lparen : doc?

Same as lparen.

value

rparen : doc?

Same as rparen.

value

lbrace : doc?

Same as lbrace.

value

rbrace : doc?

Same as rbrace.

value

lbracket : doc?

Same as lbracket.

value

rbracket : doc?

Same as rbracket.

value

langle : doc?

Same as langle.

value

rangle : doc?

Same as rangle.

value

space : doc?

Same as space.

value

ellipsis : doc?

Same as ellipsis.

value

squote : doc?

Same as squote.

value

dquote : doc?

Same as dquote.

value

dot : doc?

Same as dot.

value

backslash : doc?

Same as backslash.

value

equals : doc?

Same as equals.

5 Design Notes

5.1 History

Functional pretty printers have a surprisingly long and illustrious tradition in the literature. The ancestry of this library goes something like this:
  • 1995 - John Hughes publishes a paper Hughes (1995) on creating an algebra of "pretty documents" for the implementation of a pretty-printing library.

  • 1997 - Simon Peyton Jones implements this as a Haskell library Jones (1997).

  • 1998 - Philip Wadler publishes a paper Wadler (1998) improving on Hughes’ algebra and design.

  • 2001 - Daan Leijen implements this as a Haskell library Leijen (2001).

  • 2001 - Ralph Becket ports Leijen’s library to Mercury, a strict functional/logic language Becket (2002).

This library is a translation of the Haskell PPrint library, but with help from Becket’s Mercury implementation for maintaining efficiency in a strict language.

5.2 Mercury Port

Becket’s port makes the following modifications to the Haskell library:

Becket further modifies the Haskell algorithm by eliminating the SimpleDoc datatype and directly producing output from within the layout function, rather than first generating the intermediate SimpleDoc. However, this changes the behavior of the original algorithm. The layout function in the Haskell library examines not just the current sub-document but its entire context (i.e., the rest of the document) in order to determine whether it fits on the current line. The Mercury port, however, only uses the current sub-document to make this decision.

The following example demonstrates the difference in behavior:

Example:
> (parameterize ([current-page-width 13])
    (pretty-print
     (vs-append (text "pretty") (text "printer"))))

pretty

printer

With a column width less than 14 (i.e., (string-length "pretty printer")), the Haskell library would determine that the flattened document does not fit, and decide to break lines. The Mercury library, however, only looks at the soft break and chooses not to break because (text " ") has length 1 and therefore fits, and it subsequently overruns the length of the line.

5.3 Racket Port

I’ve chosen a design somewhere in between the two. The code mostly follows the Haskell version, but I’ve replaced the UNION constructor with a GROUP construct as in Becket’s implementation. This way there is no unnecessary duplication of data. Furthermore, the flattened version is only computed by need when the layout function reaches a GROUP node, and of course the recursion on the non-flattened version is only computed if the flattened version fails to fit.

I’ve also added Becket’s LABEL constructor.

5.4 Modification History

Bibliography

Ralph Becket. pprint.m. 2002. http://www.cs.mu.oz.au/research/mercury/information/doc-latest/mercury_library/pprint.html

John Hughes. The Design of a Pretty-Printing Library. 1995. http://www.cs.chalmers.se/~rjmh/Papers/pretty.html

Simon Peyton Jones. A Pretty-Printer Library in Haskell. 1997. http://research.microsoft.com/~simonpj/downloads/pretty-printer/pretty.html

Daan Leijen. PPrint, a Prettier Printer. 2001. http://research.microsoft.com/users/daan/pprint.html

Philip Wadler. A Prettier Printer. 1998. http://homepages.inf.ed.ac.uk/wadler/topics/language-design.html#prettier