This package provides a pretty-printer and is abstracted from an effort to build a programming language with minimal external dependencies.
Documents
are defined as things which can be printed nicely and appended to
efficiently. Documents are composed of Text
, line-breaks
and regions of
indentation
and may be appended
together.
Documents can be constructed by Printers
which may fail and be composed where
each or either Printer must succeed. A design goal is to represent them as a
dual to Parsers
such that syntax can be defined that maps to both a Printer and
a Parser that are the inverse of each other.
Documents can be deconstructed by a render
function which takes formatting
parameters to produce Text. The main supported parameter is line length. When
rendering a Document, lines which exceed this length will have a newline
character forcibly inserted.
Module | Location | Description |
---|---|---|
PLPrinter | PLPrinter.hs | Defines 'Printer's - functions which convert values into 'Doc'uments as well as re-exporting the contents of PLPrinter.Doc |
PLPrinter.Doc | PLPrinter/Doc.hs | Declares 'Doc'uments, their construction and deconstruction functions. |
Main | Test/HedgehogTests.hs | A small number of test cases for the rendering of Documents. Run with stack test |
Build documents from text with:
-- Single characters.
> char 'a'
a
-- Text which may not contain newlines.
> text "bcd"
bcd
-- Plain strings are converted to text.
> string "efg"
efg
-- Ints are converted with their 'Show' instance.
> int 1
1
-- Bools are converted with their 'Show' instance.
> bool False
False
-- Values with show instances can be converted to documents.
> usingShow (True,1)
(True,1)
-- An empty document has no text.
> emptyDoc
Append documents:
-- Documents append together across a line.
> char 'a' <> text "bcd" <> string "efg"
abcdefg
-- Empty documents do not effect the output.
> char 'a' <> emptyDoc <> text "bcd" <> emptyDoc <> text "efg"
abcdefg
Line breaks:
-- Linebreaks insert a newline.
> text "abc" <> lineBreak <> "def"-- | Two alternative printers can be tried left to right and succeeds if either
-- succeeds.
altPrinter :: Printer a -> Printer a -> Printer a
altPrinter (Printer p) (Printer q) = Printer $ \a -> mplus (p a) (q a)
abc
def
Documents may be indented:
-- Add the desired number of spaces of indentation before a document.
> indent 4 $ text "abc"
abc
-- Indentation can be applied to an appended document.
-- Following documents are un-indented automatically.
> indent 4 (text "abc" <> text "def") <> text "ghi"
abcdef
ghi
-- Linebreaks inside an indent retain the indentation level for following text.
> indent 4 (text "abc" <> lineBreak <> text "def") <> text "ghi"
abc
def
ghi
-- Indentation can be nested.
> indent 4 (text "abc" <> lineBreak <> indent 4 (text "def")) <> text "ghi"
abc
def
ghi
Some combinators are provided:
-- Place a document between to others
> between (char '(') (char ')') (text "abc")
(abc)
-- Place a document between parentheses
> parens "abc"
(abc)
-- Create a bulleted list of documents
> bulleted [char "1", char "b", text "III.", text "four"]
- 1
- b
- III.
- four
A class 'Document' is provided for the 'canonical' representation of types:
-- Document is defined as:
> class Document d where
> document :: d -> Doc
-- Document common types:
> document "abc"
abc
> document True
true
-- Define document instances for custom types:
> data Tuple a b = Tuple a b
> instance (Document a, Document b) => Document (Tuple a b) where
> document (Tuple a b) = document "Tuple:" <> lineBreak <> indent 4 (bulleted . map document $ [a, b])
> document (Tuple True 1)
Tuple:
- True
- 1
Rendering a document means to transform it into a Textual representation.
Constituent documents are transformed into text, appended and indented and line
broke where necessary. The renderWith
function takes formatting configuration
which can be used to control how long lines will grow before they have an
automatic line break inserted. This process should respect indentation and
appends in a sensible way. E.G.
-- Lines are broken when they meet the given length.
> renderWith (mkDocFmt 2) $ text "123456789"
12
34
56
78
9
-- Indentation is continued on the newline.
> renderWith (mkDocFmt 5) $ indent 2 $ text "123456789"
123
456
789
-- Manual linebreaks behave predictably.
> renderWith (mkDocFmt 5) $ indent 2 $ text "1" <> lineBreak <> text "23456789"
1
234
567
89
A 'Printer' attempts to transform values into 'Doc'uments and can be though of as being
typed a -> Maybe Doc
. Some simple printers are provided.
Textual printers which always succeed:
-- Character printer.
> pprint anyCharPrinter 'a'
Just 'a'
-- Text printer.
> pprint anyTextPrinter "abc"
Just "abc"
The empty printer:
-- The empty printer always fails
> pprint emptyPrinter "abc"
Nothing
The pure printer:
-- The pure printer succeeds when the value is equal to the one
provided.
> pprint (purePrinter 'a') 'a'
Just 'a'
> pprint (purePrinter 'a') 'b'
Nothing
Two alternative printers can be tried left to right:
-- The alternative printer succeeds if either printer succeeds:
> pprint (altPrinter (anyCharPrinter 'a') (anyCharPrinter 'b')) 'a'
Just 'a'
> pprint (altPrinter (anyCharPrinter 'a') (anyCharPrinter 'b')) 'b'
Just 'b'
> pprint (altPrinter (anyCharPrinter 'a') (anyCharPrinter 'b')) 'c'
Nothing
We can compose two printers that must both succeed. This is similar to ap
but we must use tuples here rather than the standard applicative formation.
This is because we cannot create a sensible Doc
given the type `Printer (a ->
b):
-- When both printers succeed, so does the print.
> pprint (rapPrinter (anyCharPrinter 'a') (anyTextPrinter "bcd")) ('a',"bcd")
Just ('a',"bcd")
-- The first printer failing fails the print.
> pprint (rapPrinter (anyCharPrinter 'a') (anyTextPrinter "bcd")) ('z',"bcd")
Nothing
-- The second printer failing fails the print.
> pprint (rapPrinter (anyCharPrinter 'a') (anyTextPrinter "bcd")) ('a',"zzz")
Nothing