(* Copyright (C) 1989, Digital Equipment Corporation *) (* All rights reserved. *) (* See the file COPYRIGHT for a full description. *) (* Last modified on Thu Nov 2 18:00:28 1989 by muller *) (* modified on Wed Oct 11 13:14:44 1989 by kalsow *) (* modified on Tue May 2 10:36:01 1989 by ellis *) (* modified on Fri Jan 20 15:12:08 1989 by glassman *) INTERFACE Formatter; (* This interface provides primitives for constructing pretty printers, that is, for inserting line breaks and indentation into the linear textual representation of structured objects. A Formatter.T is a special output stream to which the client outputs a special language of formatting operators to guide the placement of line breaks. The sequence of operators is translated into textual output on a writer. There are several minor differences between Formatter and FStreams. The big difference is that formatter provides column alignment and "optimal" (maybe-break-from-leaf) break points. Index: formatted streams; printing, structured data; pretty printers The operators are, from highest precedence to lowest: Name Notation Group/End << e... >> Begin/End < e... > Align <| e... |> Text "hello" Char 'a' NoAlign ~ e Break _ e... PartialBreak !! NewLine \n UnitedBreak ! e... ! e ... ! e... Flush $ The interpretation of the operators is provided below with each particular operator. *****************************************************************************) IMPORT Text, Wr; TYPE T <: REFANY; PROCEDURE New (wr: Wr.T; width: CARDINAL := 75): T RAISES {}; (*| Creates a new Formatter.T whose output will go to "wr" and whose line width is initialized to 75. *) PROCEDURE UnderlyingWr (t: T): Wr.T RAISES {}; (*| Returns the writer of the Formatter.T *) PROCEDURE Close (t: T) RAISES {Wr.Failure}; (*| Outputs a Flush operator and shuts downs the stream by killing the stream's concurrent thread. *) PROCEDURE Flush (t: T) RAISES {Wr.Failure}; (*| Outputs a flush operator, which causes any queued output to be placed into the stream's writer. Returns only after the output has made it to the writer. *) (* The following procedures simply output operators into the formatting stream *) PROCEDURE Group (t: T) RAISES {Wr.Failure}; (*| << e... >> Group/End acts as traditional parentheses around the expressions "e... " *) PROCEDURE Begin (t: T; offset := 0; width: CARDINAL := LAST (CARDINAL)) RAISES {Wr.Failure}; (*| < e... > Begin/End delimits a structured object. It establishes a new left margin "offset" spaces to the right of the first character of the object; any line breaks occuring in the printing of the expressions "e..." will be relative to that new margin. If "offset" = LAST( INTEGER ), then the margin is left unchanged. If "width" < LAST( INTEGER ), it specifies a new line width for use while printing "e...". The old left margin and line width are restored after printing "e...". *) PROCEDURE End (t: T) RAISES {Wr.Failure}; (*| Delineates the end of Group and Begin operators; a Flush ($) also acts as an End. *) PROCEDURE PutChar (t: T; c: CHAR) RAISES {Wr.Failure}; PROCEDURE PutText (t: T; text: Text.T; raw:= FALSE) RAISES {Wr.Failure}; (*| 'a' "hello" The characters of Chars and Texts are output withtout any line breaks between adjacent characters. *) PROCEDURE Break (t: T; offset := 0; optimal := TRUE; freshLine := TRUE) RAISES {Wr.Failure}; (*| _ e... Breaks come in two flavors, non-optimal and optimal. Non-optimal: If the expressions "e..." can be printed on the current line without exceeding the line width, they are so printed. Otherwise, a NewLine( t, offset, freshLine ) is performed and the "e..." are then printed. Optimal: Two possibilities are compared: 1. Printing "e..." without any preceeding newline. 2. Doing a NewLine( t, offset, freshLine ) and then printing "e...". The option that uses fewer lines for "e..." without exceeding the line width is chosen; ties are broken in favor of 2. *) PROCEDURE UnitedBreak (t: T; offset := 0; freshLine := TRUE) RAISES {Wr.Failure}; (*| ! e1... ! e2... ! ... ! en... (This is a variadic, multi-fix operator.) If the expressions "e1...", "e2...", through "en..." can be printed so that they all fit on the first line of the current Begin/End object, then they are so printed. Otherwise, they are printed with a NewLine( t, offset, freshLine ) at each of the !'s. *) PROCEDURE PartialBreak (t: T; offset := 0; freshLine := TRUE) RAISES {Wr.Failure}; (*| !! Performs a Newline( t, offset, freshLine ) if more than one line has been used up to this point to print the current innermost object delimited by Begin/End. *) PROCEDURE NewLine (t: T; offset := 0; freshLine := TRUE) RAISES {Wr.Failure}; (*| \n Starts a new line indented "offset" spaces from the current left margin. If "freshline" is true, starts a newline only if "leftMargin + offset < c", where "c" is the current column of the current position. *) TYPE AlignPred = REF PROCEDURE (arg : REFANY; column : CARDINAL; maxWidth : CARDINAL; width : CARDINAL): BOOLEAN; PROCEDURE Align (t: T; columns : CARDINAL; tryOneLine : BOOLEAN := TRUE; offset : INTEGER := 0; alignPred : AlignPred := NIL; alignPredArg : REFANY := NIL) RAISES {Wr.Failure}; (*| <| row... |> The Align operator produces a table with the columns of the rows aligned if possible. Each row should should have the form: << e... >> or ~ e (discussed later) Each "e" in the row will be in a separate column. It is an error if a row has more than "columns" columns, but a row may have fewer. IF "tryOneLine" is true and the initial output position is in the first line of the enclosing Begin/End object, then Align will print all the rows on that one line (if they fit). There is an implicit Begin/End around the entire Align expression and around each row. "offset" specifies the left-margin offset for each row. Align will align the maximal set of leading rows such that each row fits on a single line, each row's columns satisfy the "alignPred" predicate, and the row is not a NoAlign row. Then it will recursively align the rest of the rows without regard to the alignment of the leading rows. If "alignPred" is non-nil, then alignPred (arg, column, maxWidth, width) is called for each column in each row. "column" is the 0-based column number, "maxWidth" is the maximum width of that column in the previous rows being aligned, and "width" is the width of column in the current row. The predicate should return false iff it desires that row shouldn't be aligned with the previous rows. NoAlign rows (~ e) are never aligned with previous or succeeding rows. The expression "e" is printed without any leading newline; "e" should contain its own leading newline (but not a trailing newline). *) PROCEDURE NoAlign (t: T) RAISES {Wr.Failure}; END Formatter.