FPretty-1.1: Efficient simple pretty printing combinators

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

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.

FPretty uses far less space than other pretty printing libraries for large documents. It does require space linear in the nesting depth of nest/align combinators; however, having these deeply nested leads to a bad layout anyway.

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 #

A Document represents a *set* of layouts.

Instances

Show Doc # 

Methods

showsPrec :: Int -> Doc -> ShowS #

show :: Doc -> String #

showList :: [Doc] -> ShowS #

Pretty printing

pretty :: Int -> Doc -> String #

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 :: Doc #

The empty document; equal to text "".

text :: String -> Doc #

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

Basic documents with several layouts

line :: Doc #

Either a space or a new line.

linebreak :: Doc #

Either nothing (empty) or a new line.

softline :: Doc #

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

softbreak :: Doc #

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

Combining two documents

The base binary combinator

(<>) :: Doc -> Doc -> Doc infixr 6 #

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

Derived binary combinators

(<+>) :: Doc -> Doc -> Doc infixr 6 #

Combine with a space in between.

(<$>) :: Doc -> Doc -> Doc infixr 5 #

Combine with a line in between.

(<$$>) :: Doc -> Doc -> Doc infixr 5 #

Combine with a linebreak in between.

(</>) :: Doc -> Doc -> Doc infixr 5 #

Combine with a softline in between.

(<//>) :: Doc -> Doc -> Doc infixr 5 #

Combine with a softbreak in between.

Modifying the layouts of one document

group :: Doc -> Doc #

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 -> Doc #

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

align :: Doc -> Doc #

Set indentation to current column.

hang :: Int -> Doc -> Doc #

Increase identation relative to the *current* column.

Combining many documents

hsep :: [Doc] -> Doc #

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

vsep :: [Doc] -> Doc #

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

fillSep :: [Doc] -> Doc #

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

sep :: [Doc] -> Doc #

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

hcat :: [Doc] -> Doc #

Combine non-empty list of documents with <>.

vcat :: [Doc] -> Doc #

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

fillCat :: [Doc] -> Doc #

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

cat :: [Doc] -> Doc #

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