PrettyPrint implements a facility for defining and installing user-defined prettyprinters over (monomorphic) user-defined types for use by the top-level loop of the SML compiler. The underlying algorithm is that of Oppen (TOPLAS, 1980). There are more expressive prettyprinting languages around, notably that of PPML, but the Oppen interface has the benefit of being efficiently implementable. Thus it is a good option for modest prettyprinting tasks that need to be quick, such as the printing of SML values. These same prettyprinting facilities are used by the compiler for such tasks as printing values in the interactive top level and printing expressions in error messages.
The high-level view is that the user will define a prettyprinter for a datatype and install it in a prettyprinter table. When it comes time for the compiler to print a value, it looks first in the prettyprinter table, to see if a prettyprinter is installed for that type. If so, it calls the prettyprinter on the value, otherwise, it calls the default printing routine.
The Oppen algorithm provides a block abstraction: a block establishes a level of indentation. Since blocks can be nested and offset from one another, levels of indentation can be achieved. A block can be broken up by one or more breaks, which mark possible places to add carriage returns. There are two styles of block: CONSISTENT and INCONSISTENT. If a consistent block does not fit completely onto the current line, a carriage return will be added after each component of the block. If an INCONSISTENT block does not fit completely onto the current line, a carriage return is added after the last item that does fit on the line; this style of block conserves on vertical space.
datatype break_style = CONSISTENT | INCONSISTENT
exception PP_FAIL of string
val mk_ppstream : ppconsumer -> ppstream
val dest_ppstream : ppstream -> ppconsumer
val add_break : ppstream -> (int * int) -> unit
val add_newline : ppstream -> unit
val add_string : ppstream -> string -> unit
val begin_block : ppstream -> break_style -> int -> unit
val end_block : ppstream -> unit
val clear_ppstream : ppstream -> unit
val flush_ppstream : ppstream -> unit
val with_pp : ppconsumer -> (ppstream -> unit) -> unit
val pp_to_string : int -> (ppstream -> 'a -> unit) -> 'a -> string
add_break pp (i, j)
block_style = CONSISTENT and the entire block fits on the remainder of the current line: output
block_style = CONSISTENT and the block does not fit on the rest of the line: add a carriage return after each component in the block and add the block offset to the current indentation level. Each component will be further indented by offset spaces.
block_style = INCONSISTENT and the next component of the current block fits on the rest of the line: output
block_style = INCONSISTENT and the next component doesn't fit on the rest of the line: output a carriage return and indent to the current indentation level plus the block offset plus offset extra spaces.
Size is taken into account when determining if there is enough room to print the next component in the block.
add_string pps s
begin_block pp bs i
with_pp consumer printfn
pp_to_string i printit x