The PRETTYPRINT signature

Compiler.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.


structure Compiler.PrettyPrint : PRETTYPRINT


type ppstream
type ppconsumer
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


type ppstream
This is an abstract type of prettyprint streams. A ppstream encapsulates the state of an Oppen prettyprinter.

type ppconsumer
This type describes a record that provides the ultimate consumer of the characters produced by the prettyprint operations. A ppconsumer is supplied to mk_ppstream and with_pp and is used by those operations to build ppstreams.

datatype break_style
This defines the behavior of breaks within a block. If the style is CONSISTENT, then if any break occurs, all breaks occur.

If the style is INCONSISTENT, then breaks will only occur when the current line is full.

exception PP_FAIL

mk_ppstream consumer
A new ppstream is constructed, with character output directed to the consumer.

dest_ppstream pps
Returns the consumer record used by pps.

add_break pp (i, j)
Notify the ppstream pps that a carriage return is possible. The argument to this function is a pair; the first member is the size of the break, the second member is an offset. This ``break'' offset is used for finer control over indentation than that offered by the block offset. The current block style and the size of the block determine what action is to be taken:

If block_style = CONSISTENT and the entire block fits on the remainder of the current line: output size spaces.

If 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.

If block_style = INCONSISTENT and the next component of the current block fits on the rest of the line: output size spaces.

If 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_newline pps
Forces the output of a line break on pps.

add_string pps s
Outputs the string s to pps.

begin_block pp bs i
This begins a new level of indentation, at the current offset from the left margin. The break_style argument determines how the block is to be broken. The third argument determines the new offset if the block gets broken.

end_block pps
Prettyprinting for pps reverts to the previous level of indentation.

clear_ppstream pps
Clears the state of pps.

flush_ppstream pps
Flushes currently accumulated text to pps's consumer and calls the flush operation of the consumer.

with_pp consumer printfn
Constructs a new ppstream from consumer and applies printfn to it.

pp_to_string i printit x
Constructs a new ppstream whose consumer accumulates output in a string. Applies the function printit to this ppstream and the value x, and then returns the string containing the prettyprinting output.