Pretty-Printing
The pretty printer of VerCors is derived from "A prettier printer" by Philip Wadler, accessible here. Typesetting a COL AST is done in three stages: it is first translated into a document tree, then a list of elements, then text.
COL
Each node implements layout
, which is externally accessible via show
. The only purpose of show
is to wrap the document tree with NodeDoc
, so that we may later recall what document tree corresponds to what COL tree. If you forget to implement layout
a debugging representation of the node is printed that does not look entirely terrible.
Expr
is a bit more special: it demands an implementation of def precedence: Int
for each expression. You can use bind
, assoc
, nassoc
, lassoc
and rassoc
as conveniences to see whether sub-expressions require parentheses:
bind
adds parentheses if the precedence of the subexpression is lower than the specified precedenceassoc
adds parentheses if the precedence of the subexpression is strictly lower than our precedence (i.e. we "associate" with the subexpression)nassoc
adds parentheses if the precedence of the subexpression is less or equal than our precedence (i.e. we do not "associate" with the subexpression)lassoc
is the correct implementation for a left-associative binary operator (e.g.+
,*
)rassoc
is the correct implementation for a right-associative binary operator (e.g.::
,==>
)
Document tree
The elements of a document tree are as follows:
Empty
, which renders as nothingText(String)
, which simply represents a piece of text. Must not contain newlines.Cons(Doc, Doc)
(orDoc <> Doc
), which represents the unspaced concatenation of two trees.Nest(Doc)
, which indicates the tree should be nested (i.e. indented)Group(Doc)
, which indicates that newlines should preferrably be removed from the tree.Line
andNonWsLine
, which are both newlines. If collapsed in aGroup
, onlyLine
is replaced with one space.NodeDoc(Node[_], Doc)
, which has no effect on typesetting, but indicates this document tree corresponds to this node.
The deviations from Wadler's algorithm are:
- We have no
<|>
to indicate alternatives. Instead our very opinionatedGroup
has in effect two alternatives: we prefer for it to be printed on one line, but it may keep theLine
s andNonWsLine
s if necessary to reduce its horizontal occupancy. WhenGroup
is nested we first peel off the outerGroup
, keeping the innerGroup
if possible. - The
flatten
ofNonWsLine
isEmpty
instead ofText(" ")
NodeDoc
is there for post-processing
Otherwise the typesetting algorithm is a straightforward transliteration of the paper.
Doc
has several convenient operators to make trees:
- On the right hand side of operators, you can write anything that implements
Show
, such asNode[_]
x <> y
isCons(x, y)
x <+> y
isx <> " " <> y
x </> y
isx <> NonWsLine <> y
x <+/> y
isx <> Line <> y
x <>> y
isx <> Nest(Line <> y)
Doc.fold
applies a function on pairs ofDoc
until there is only oneDoc
. If the sequence ofDoc
s is empty, the result isEmpty
.Doc.spread
,Doc.lspread
andDoc.rspread
place spaces betweenDoc
s.r
has an extra space on the right,l
has an extra space on the left.Doc.stack
places newlines betweenDoc
s that reduce to a space in aGroup
.Doc.arg
indents aDoc
on a new line, then appends another newline. Both newlines reduce to nothing in aGroup
. Appropriate for an argument to a function or control structure likewhile
, when wrapped in aGroup
.Doc.args
combines comma-separated arguments with newlines that reduce to a space.Doc.spec
andDoc.inlineSpec
surround theDoc
with/*@ @*/
if necessary. Note: the context only indicates to the tree argument that we are now in a spec if the argument toDoc.spec
is not yet aDoc
, but something yet to be showed. This can be delayed withShow.lazily
if necessary.
Elements
After typesetting the document we are left with a list of elements, which are one of two variants:
ELine(indent: Int)
, which renders a new line with a given amount of indentation. The context contains an indentation string, that is repeatedindent
amount of timesEText(text: String)
, which is rendered as its constitutent text.
Additionally, we again remember what corresponds to which node, for post-processing:
EStart(Node[_])
indicates the start of a region that represents that node.EEnd(Node[_])
indicates the end of that region.
The metadata is used in Doc.highlight
, which can point to a node in a rendered tree.
Any node that implements Show
has convenience functions write(Appendable)
and toStringWithContext(Ctx)
to convert the Show
to text.
Context
The class vct.col.print.Ctx
is available when rendering nodes and can be updated when the parent context is relevant (such as being inside a specification). Otherwise it contains information like declaration names, the horizontal width, etc.