package fmlib_pretty
Install
dune-project
Dependency
Authors
Maintainers
Sources
sha256=650393b6315075780d51cc698e2ee19bc359f114fc39365fbe137b24f663189e
doc/pretty.html
Pretty Printing Overview
Basics
The pretty printer allows to print nicely formatted ascii text. The user generates a document with break hints. The primitives to generate documents are
empty
Empty document.text str
Document which contains the stringstr
.str
should not contain newlines in order not to interfere with the formatter.break str
Break hint with alternative textstr
.doc2 <+> doc2
Concatenation of the documentsdoc1
anddoc2
nest indent doc
Indented document.group doc
Treat all top level break hints ofdoc
consistently i.e. either print all break hints with their alternative text or as a newline. If a group is flattened (i.e. break hints printed with their alternative text) when all inner groups are flattened as well.
With these primitives a surprisingly rich set of formattings can be made.
The user generates documents not only by using the primitives. There are a lot of convenience functions to make document generation easy.
Document creation is done lazily. Only very few resources are consumed in producing a document. The work starts with the layout function. The Fmlib_pretty.Pretty.layout
function does the layout and never processes more than one line.
The layout generates a stream of characters. Lines are formatted only if the characters of the line are pulled out of the stream.
If you just create a document and layout it but you never use the stream, then no work is done.
Term Printing
The usage of the pretty printer is best explained by an example. Suppose we want to print the function application f a b (g c d) e
where the function names and arguments might have different length. We create a document which represents the structure by
let doc =
group (
text "f" <+> space <+>
nest
2
(stack_or_pack
" "
[text "a";
text "b";
group (
text "(g" <+> space <+>
nest
2
(stack_or_pack " " [text "c"; text "d"])
<+> text ")");
text "e"])
)
where text "blabla"
is a document with some unbreakable text, <+>
concatenates two documents, space
is a break hint whose alternative text is a blank, stack_or_pack atxt [...]
stacks a list of documents separated by a break hint with the alternative text atxt
.
The command
let stream = layout 5 doc
creates a stream of characters (i.e. an element of type Fmlib_pretty.Pretty.Stream.t
) which is nicely formatted using a desired line width of 5 characters.
Character streams of type Fmlib_pretty.Pretty.Stream.t
can be read character by character (see Fmlib_pretty.Pretty.Stream
) or written to an ouput channel by Fmlib_pretty.Pretty.write_to_channel
or converted to a plain string by Fmlib_pretty.Pretty.to_string
.
Since 5 characters are not enough to put any of the subterms completely on a line, the output is
123456789012345
f
a
b
(g
c
d)
d
i.e. each break hint is printed as a newline.
If we give the pretty printer a line width of 10, it could pack the application g c d
on a line and print
123456789012345
f
a
b
(g c d)
d
If the pretty printer has enough line width e.g. a line width of 15, it can put the whole expression on a line.
123456789012345
f a b (g c d) d
By using stack_or_pack
we instructed the pretty printer to either print all break hints as newlines or all break hints with their alternative texts. If we use pack
instead of stack_or_pack
, the pretty printer tries to pack as many arguments as possible on a line.
E.g. with a line width of 11 and using pack
instead of stack_or_pack
we get the output
123456789012345
f
a b
(g c d) d
With a line width of 10 and using pack
we get
123456789012345
f
a b
(g c d)
d
because the pretty printer cannot pack (g c d)
and d
on a single line.
Character Stream
The type Fmlib_pretty.Pretty.Stream.t
of the pretty printer is a lazy character stream. I.e. characters are only generated if needed. The pretty printer implements the interface Fmlib_std.Interfaces.SOURCE
to represent a character stream. You can ask the stream has_more r
whether there are more characters in the stream and peek r
to get the next character. The instruction advance r
returns the stream r
advanced by one character position.
Formatted Paragraphs
There are functions to generate formatted paragraphs with indentation.
let words =
wrap_words "bla bla bla bla bla bla bla" <+> cut
let doc = paragraphs [
words;
words;
nest 4 words;
words;
]
let stream = layout 16 doc
The stream
produces the following output
12345678901234567890
bla bla bla bla
bla bla bla
bla bla bla bla
bla bla bla
bla bla bla
bla bla bla
bla
bla bla bla bla
bla bla bla
Generate Documents
Clearly, it is tedious to write documents by hand. Usually you have some tree like structure and you want to generate a document from the tree structure.
Let's assume you have a tree structure like
type tree =
{ name: string; children: tree list; }
let leaf (name: string): tree =
{name; children = [] }
let tree (name: string) (children: tree list): tree =
{name; children}
Write a function which converts the tree structure to a document.
let doc_of_tree (tree: tree): doc =
let rec doc is_top tree =
match tree.children with
| [] ->
text tree.name
| _ ->
let d =
parent_child
" " 2
(text tree.name)
(children tree.children ())
in
if is_top then
d
else
char '(' <+> d <+> char ')'
and children lst () =
match lst with
| [last] ->
doc false last
| head :: tail ->
doc false head <+> space
+| children tail (* Lazy concatenation!! *)
| [] ->
assert false (* 'lst' is never empty *)
in
doc true tree
Then the simple command
tree
"f"
[leaf "a";
leaf "b";
tree "g" [leaf "c"; leaf "d"];
leaf "e"]
|> layout 10
generates the character stream
123456789012345
f
a
b
(g c d)
e
Note the usage of the lazy concatentation operator +|
in the recursive part of the function handling the children. This makes sure that even if the tree structure is very big, the iteration over it is done only on demand. I.e. recursive calls are made only if the corresponding characters are needed when processing the character stream.