Written in Modula-2+ by John Ellis before the dawn of history Converted to Modula-3 by Bill Kalsow on Oct 1989 Last modified on Sun Feb 23 14:45:05 PST 1992 by meehan modified on Tue Aug 27 01:11:58 1991 by kalsow modified on Thu Jul 25 19:30:30 PDT 1991 by stolfi modified on Thu Jul 25 20:44:22 1991 by muller modified on Mon Apr 22 17:34:37 1991 by nichols@xerox.com
INTERFACEFormatter ;
A Formatter.T is a basic tool for pretty printing,
the printing
of structured objects with appropriate line breaks and indentation.
Index: formatted streams; printing, structured data; pretty printers
IMPORT Text, Wr; TYPE T <: REFANY; (* A Formatter.T is a filter which takes a sequence of /expressions/ and converts them into a sequence of characters, which are sent to an underlying Wr.T. The input expressions consist of character strings intermixed with /formatting operators/ that specify a set of possible line breaks, and the relative alignment of parts in multi-line expressions. The SxFormatter.T chooses a "good" subset of the given line breaks, inserts newlines and padding whitespaces at those points, and writes the result to the underlying writer. *) PROCEDURE New (wr: Wr.T; width: CARDINAL := 75): T RAISES {}; (* Creates a new Formatter.T whose output will go to "wr". The /width/ parameter specifies the nominal maximum line width. *) PROCEDURE UnderlyingWr (t: T): Wr.T RAISES {}; (* Returns the writer that is connected to the output of /t/. *) PROCEDURE Close (t: T) RAISES {Wr.Failure}; (* Flushes all buffered expressions to the underlying writer, with proper line breaks, and releases internal resources. The formatter /t/ should not be used afterwards. *) PROCEDURE Flush (t: T) RAISES {Wr.Failure}; (* Flushes all buffered expressions to the underlying writer, with proper line breaks. Automatically supplies /End/s for all unmatched /Group/s, /Begin/s, and /Align/s (see below). Returns only after the output has made it to the writer. *)******************************************************** TEXTS AND CHARACTERS ********************************************************
The following procedures insert characters and strings into the formatter's input stream:
PROCEDURE PutText (t: T; text: Text.T; raw:= FALSE) RAISES {Wr.Failure}; (* If /raw=TRUE/, sends an expression consisting of the given text, irrespective of its content. If /raw=FALSE/ (the default), breaks the text into substrings before and after each blank or newline, and sends each piece as a separate expression. *) PROCEDURE PutChar (t: T; c: CHAR) RAISES {Wr.Failure}; (* If c='\n', sends a NewLine(t) operator to the formatter; otherwise sends the character /c/ itself. Successive calls to PutChar whose arguments are neither ' ' nor '\n' are compacted into a single text string. *)******************************************************** BREAKS ********************************************************
The following procedures send line breaking operators to the formatter. There are four kinds of line breaks:
Break BreakOpt PartialBreak NewLine UnitedBreak
Precedence decreases from top to bottom. That is, the input expresion
stream is parsed as a sequence of terms
separated by /UnitedBreak/s;
each term
is parsed as a sequence of factors
separated by
/NewLine/s; and so on. These precedences can be overriden
by enclosing expressions between /Group/ and /End/ operators (see
below).
TYPE BreakType = {NonOptimal, OptimalBreak, OptimalNoBreak}; PROCEDURE Break (t: T; offset := 0; type := BreakType.OptimalBreak; freshLine := TRUE) RAISES {Wr.Failure}; (* Specifies an optional line break. Breaks come in three types: NonOptimal: If the expressions /e1, e2, .../ following the /Break/ (up to the next line break or /End/) 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 expressions are then printed. OptimalBreak, OptimalNoBreak: Two possibilities are compared: 1. Printing the expressions /e1, e2, .../ without any preceeding newline. 2. Doing a NewLine(t, offset, freshLine) and then printing those expressions. The option that uses fewer lines without exceeding the line width is chosen. Ties are broken in favor of either 1 (OptimalNoBreak) or 2 (OptimalBreak). In general, optimal breaks are more expensive than non-optimal ones. *) 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}; (* Starts a new line indented /offset/ spaces from the current left margin (see /Begin/). If /freshline/ is true, starts a newline only if /leftMargin + offset < c/, where /c/ is the current column of the current position. *) PROCEDURE UnitedBreak (t: T; offset := 0; freshLine := TRUE) RAISES {Wr.Failure}; (* Inserts a new member of an all-or-none group of line breaks. If all the expressions in the innermost /Group/ or /Begin/ can be printed so that they all fit on the current line, then they are so printed, and every top-level /UnitedBreak/ in that group is ignored. Otherwise, every top-level /UnitedBreak/ in that group is equivalent to a NewLine(t, offset, freshLine). *)******************************************************** GROUPING AND ALIGNMENT ********************************************************
PROCEDURE Group (t: T) RAISES {Wr.Failure}; (* Logical parenthesis: the expressions between a /Group/ and the matching /End/ will be treated as a single expression. *) PROCEDURE Begin (t: T; offset := 0; width: CARDINAL := LAST(CARDINAL)) RAISES {Wr.Failure}; (* The operators /Begin/ and /End/ delimit a structured object, consisting of the enclosed expressions and formatting operators. /Begin/ 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 enclosed expressions will be relative to this new margin. If "offset" = LAST( INTEGER ), then the current left margin is retained. If /width/ < LAST(INTEGER), the enclosed expressions are printed assuming this new nominal line width. The old left margin and line width are restored after the object has been printed. *) PROCEDURE Align ( t: T; columns: CARDINAL; tryOneLine: BOOLEAN := TRUE; offset: INTEGER := 0; alignPred: AlignPred := NIL; ) RAISES {Wr.Failure}; (* The /Align/ operator produces a table with multiple columns, aligned if possible. The expressions between the /Align/ and the matching /End/ describe the rows of the table. Each row should be either a list of expressions delimited by /Group/ and /End/, or a non-aligned row (see NoAlign). The /offset/ parameter specifies the left-margin offset for each row. Each expression in a row will be in a separate column. It is an error if a row has more than /columns/ entries, 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). 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. *) TYPE AlignPred = OBJECT METHODS pred ( column: CARDINAL; maxWidth: CARDINAL; width: CARDINAL ): BOOLEAN; END; (* If the /alignPred/ parameter of /Align/ is non-nil, then alignPred.pred(arg, column, maxWidth, width) is called for each column in each row, where /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. If any of these calls returns FALSE, the entire row is printed without any column alignment, as if it were preceded by a /NoAlign/ operator. *) PROCEDURE NoAlign (t: T) RAISES {Wr.Failure}; (* A /NoAlign/ operator, valid only as a top-level member in an Align/End group, specifies that the following expression /e/ should not be aligned with the previous or succeeding rows, and should not affect their alignment. The expression /e/ is printed without any leading newline. Typically, /e/ will be a group or a raw text containing its own leading newline, but not a trailing newline. *) PROCEDURE Col (t: T; column: INTEGER; relative := FALSE; space: CARDINAL := 0) RAISES {Wr.Failure}; (* If the current position of the formatter is less than "column" (zero-based), emit enough spaces to go to that column. Otherwise, emit "space" spaces. If "relative" is TRUE, interpret "column" as being relative to the current left margin; in this case, it is meaningful for "column" to be negative. *) PROCEDURE End (t: T) RAISES {Wr.Failure}; (* Delineates the end of /Group/, /Begin/, and /Align/ operators. The /Flush/ procedure automatically supplies /End/s for all unmatched /Group/s, /Begin/s, and /Align/s. *) END Formatter.