FPretty-1.0: Efficient simple pretty printing combinators

Portabilityportable
MaintainerOlaf Chitil <O.Chitil@kent.ac.uk>
Safe HaskellSafe

Text.PrettyPrint.FPretty

Contents

Description

Fast pretty-printing library

A pretty printer turns a tree structure into indented text, such that the indentation reflects the tree structure. To minimise the number of lines, substructures are put on a single line as far as possible within the given line-width limit.

An pretty-printed example with 35 characters line-width:

 if True
    then if True then True else True
    else
       if False 
          then False 
          else False

To obtain the above the user of a library only has to convert their tree structure into a document of type Doc.

 data Exp = ETrue | EFalse | If Exp Exp Exp

 toDoc :: Exp -> Doc
 toDoc ETrue = text "True"
 toDoc EFalse = text "False"
 toDoc (If e1 e2 e3) =
   group (nest 3 (
     group (nest 3 (text "if" <> line <> toDoc e1)) <> line <>
     group (nest 3 (text "then" <> line <> toDoc e2)) <> line <>
     group (nest 3 (text "else" <> line <> toDoc e3))))

A document represents a set of layouts. The function pretty then takes a desired maximal printing width and a document and selects the layout that fits best.

Another example filling lines with elements of a list:

 list2Doc :: Show a => [a] -> Doc
 list2Doc xs = text "[" <> go xs <> text "]"
   where
   go [] = empty
   go [x] = text (show x)
   go (x:y:ys) = text (show x) </> text ", " <> go (y:ys)

 main = putStrLn (pretty 40 (list2Doc [1..20]))

The output is

 [1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10
 , 11 , 12 , 13 , 14 , 15 , 16 , 17 , 18
 , 19 , 20]

FPretty is an implementation of the simple combinators designed by Phil Wadler. The library uses a single associative combinator <> to concatenate documents with empty as identity. There is a primitive document for potential line breaks, i.e., its two layouts are both a line break and a space. The group combinator then enforces that all potential line breaks within a document must be layouted in the same way, i.e. either line breaks or spaces.

The time complexity is linear in the output size. In contrast, all other pretty printing libraries (original Phil Wadler, PPrint by Leijen, Hughes / Peyton Jones) use more or less backtracking, and their speed depends unpredictably on the desired output width.

Also FPretty provides both relative and absolute indentation via nest and align, whereas HughesPJ provides only relative indentation.

Unlike other libraries, FPretty does not provide several rendering modes, but could be extended to do so.

The combinators are a subset of those of PPrint and are similar to HughesPJ to make moving from one library to the other as painless as possible.

For more implementation notes see http://www.cs.kent.ac.uk/~oc/pretty.html or Doitse Swierstra and Olaf Chitil: Linear, bounded, functional pretty-printing. Journal of Functional Programming, 19(1):1-16, January 2009.

Synopsis

The type of documents

data Doc Source

A Document represents a *set* of layouts.

Pretty printing

pretty :: Int -> Doc -> StringSource

Pretty print within given width. Selects from the *set* of layouts that the document represents the widest that fits within the given width. If no such layout exists, then it will choose the narrowest that exceeds the given width.

Basic documents

empty :: DocSource

The empty document; equal to text "".

text :: String -> DocSource

Atomic document consisting of just the given text. There should be no newline \n in the string.

Basic documents with several layouts

line :: DocSource

Either a space or a new line.

linebreak :: DocSource

Either nothing (empty) or a new line.

softline :: DocSource

A space, if the following still fits on the current line, otherwise newline.

softbreak :: DocSource

Nothing, if the following still fits on the current line, otherwise newline.

Combining two documents

The base binary combinator

(<>) :: Doc -> Doc -> DocSource

Horizontal composition of two documents. Is associative with identity empty.

Derived binary combinators

(<+>) :: Doc -> Doc -> DocSource

Combine with a space in between.

(<$>) :: Doc -> Doc -> DocSource

Combine with a line in between.

(<$$>) :: Doc -> Doc -> DocSource

Combine with a linebreak in between.

(</>) :: Doc -> Doc -> DocSource

Combine with a softline in between.

(<//>) :: Doc -> Doc -> DocSource

Combine with a softbreak in between.

Modifying the layouts of one document

group :: Doc -> DocSource

Mark document as group, that is, layout as a single line if possible. Within a group for all basic documents with several layouts the same layout is chosen, that is, they are all horizontal or all new lines. Within a vertical group there can be a horizontal group, but within a horizontal group all groups are also layouted horizontally.

nest :: Int -> Doc -> DocSource

Increases current indentation level (absolute). Assumes argument >= 0.

align :: Doc -> DocSource

Set indentation to current column.

hang :: Int -> Doc -> DocSource

Increase identation relative to the *current* column.

Combining many documents

hsep :: [Doc] -> DocSource

Combine non-empty list of documents with <+>, i.e., a space separator.

vsep :: [Doc] -> DocSource

Combine non-empty list of documents with <$>, i.e., a line separator.

fillSep :: [Doc] -> DocSource

Combine non-empty list of documents with </>, i.e., a softline separator.

sep :: [Doc] -> DocSource

Combine non-empty list of documents vertically as a group. Seperated by space instead if all fit on one line.

hcat :: [Doc] -> DocSource

Combine non-empty list of documents with <>.

vcat :: [Doc] -> DocSource

Combine non-empty list of documents with <$$>, i.e., a linebreak separator.

fillCat :: [Doc] -> DocSource

Combine non-empty list of documents with <//>, i.e., a softbreak separator.

cat :: [Doc] -> DocSource

Combine non-empty list of documents, filling lines as far as possible.