The following functions and macros support precise control of what should be done when a piece of output is too large to ﬁt in the space available. Three concepts underlie the way these operations work: logical blocks, conditional newlines, and sections. Before proceeding further, it is important to deﬁne these terms.
The ﬁrst line of ﬁgure 27.1 shows a schematic piece of output. The characters in the output are represented by hyphens. The positions of conditional newlines are indicated by digits. The beginnings and ends of logical blocks are indicated in the ﬁgure by “<” and “>” respectively.
The output as a whole is a logical block and the outermost section. This section is indicated by the 0’s on the second line of ﬁgure 27.1. Logical blocks nested within the output are speciﬁed by the macro pprint-logical-block. Conditional newline positions are speciﬁed by calls on pprint-newline. Each conditional newline deﬁnes two sections (one before it and one after it) and is associated with a third (the section immediately containing it).
The section after a conditional newline consists of all the output up to, but not including, (a) the next conditional newline immediately contained in the same logical block; or if (a) is not applicable, (b) the next newline that is at a lesser level of nesting in logical blocks; or if (b) is not applicable, (c) the end of the output.
The section before a conditional newline consists of all the output back to, but not including, (a) the previous conditional newline that is immediately contained in the same logical block; or if (a) is not applicable, (b) the beginning of the immediately containing logical block. The last four lines in ﬁgure 27.1 indicate the sections before and after the four conditional newlines.
The section immediately containing a conditional newline is the shortest section that contains the conditional newline in question. In ﬁgure 27.1, the ﬁrst conditional newline is immediately contained in the section marked with 0’s, the second and third conditional newlines are immediately contained in the section before the fourth conditional newline, and the fourth conditional newline is immediately contained in the section after the ﬁrst conditional newline.
Whenever possible, the pretty printer displays the entire contents of a section on a single line. However, if the section is too long to ﬁt in the space available, line breaks are inserted at conditional newline positions within the section.
[Function]pprint-newline kind &optional stream
The stream (which defaults to *standard-output*) follows the standard conventions for stream arguments to printing functions (that is, nil stands for *standard-output* and t stands for *terminal-io*). The kind argument speciﬁes the style of conditional newline. It must be one of :linear, :ﬁll, :miser, or :mandatory. An error is signaled if any other value is supplied. If stream is a pretty printing stream created by pprint-logical-block, a line break is inserted in the output when the appropriate condition below is satisﬁed. Otherwise, pprint-newline has no eﬀect. The value nil is always returned.
If kind is :linear, it speciﬁes a ‘linear-style’ conditional newline. A line break is inserted if and only if the immediately containing section cannot be printed on one line. The eﬀect of this is that line breaks are either inserted at every linear-style conditional newline in a logical block or at none of them.
If kind is :miser, it speciﬁes a ‘miser-style’ conditional newline. A line break is inserted if and only if the immediately containing section cannot be printed on one line and miser style is in eﬀect in the immediately containing logical block. The eﬀect of this is that miser-style conditional newlines act like linear-style conditional newlines, but only when miser style is in eﬀect. Miser style is in eﬀect for a logical block if and only if the starting position of the logical block is less than or equal to *print-miser-width* from the right margin.
If kind is :ﬁll, it speciﬁes a ‘ﬁll-style’ conditional newline. A line break is inserted if and only if either (a) the following section cannot be printed on the end of the current line, (b) the preceding section was not printed on a single line, or (c) the immediately containing section cannot be printed on one line and miser style is in eﬀect in the immediately containing logical block. If a logical block is broken up into a number of subsections by ﬁll-style conditional newlines, the basic eﬀect is that the logical block is printed with as many subsections as possible on each line. However, if miser style is in eﬀect, ﬁll-style conditional newlines act like linear-style conditional newlines.
If kind is :mandatory, it speciﬁes a ‘mandatory-style’ conditional newline. A line break is always inserted. This implies that none of the containing sections can be printed on a single line and will therefore trigger the insertion of line breaks at linear-style conditional newlines in these sections.
When a line break is inserted by any type of conditional newline, any blanks that immediately precede the conditional newline are omitted from the output and indentation is introduced at the beginning of the next line. By default, the indentation causes the following line to begin in the same horizontal position as the ﬁrst character in the immediately containing logical block. (The indentation can be changed via pprint-indent.)
There are a variety of ways unconditional newlines can be introduced into the output (for example, via terpri or by printing a string containing a newline character). As with mandatory conditional newlines, this prevents any of the containing sections from being printed on one line. In general, when an unconditional newline is encountered, it is printed out without suppression of the preceding blanks and without any indentation following it. However, if a per-line preﬁx has been speciﬁed (see pprint-logical-block), that preﬁx will always be printed no matter how a newline originates.
This macro causes printing to be grouped into a logical block. It returns nil.
The stream-symbol must be a symbol. If it is nil, it is treated the same as if it were *standard-output*. If it is t, it is treated the same as if it were *terminal-io*. The run-time value of stream-symbol must be a stream (or nil standing for *standard-output* or t standing for *terminal-io*). The logical block is printed into this destination stream.
The body (which consists of the forms) can contain any arbitrary Lisp forms. Within the body, stream-symbol (or *standard-output* if stream-symbol is nil, or *terminal-io* if stream-symbol is t) is bound to a “pretty printing” stream that supports decisions about the arrangement of output and then forwards the output to the destination stream. All the standard printing functions (for example, write, princ, terpri) can be used to send output to the pretty printing stream created by pprint-logical-block. All and only the output sent to this pretty printing stream is treated as being in the logical block.
pprint-logical-block and the pretty printing stream it creates have dynamic extent. It is undeﬁned what happens if output is attempted outside of this extent to the pretty printing stream created. It is unspeciﬁed what happens if, within this extent, any output is sent directly to the underlying destination stream (by calling write-char, for example).
The :suﬃx, :preﬁx, and :per-line-preﬁx arguments must all be expressions that (at run time) evaluate to strings. The :suﬃx argument s (which defaults to the null string) speciﬁes a suﬃx that is printed just after the logical block. The :preﬁx and :per-line-preﬁx arguments are mutually exclusive. If neither :preﬁx nor :per-line-preﬁx is speciﬁed, a :preﬁx of the null string is assumed. The :preﬁx argument speciﬁes a preﬁx p that is printed before the beginning of the logical block. The :per-line-preﬁx speciﬁes a preﬁx p that is printed before the block and at the beginning of each subsequent line in the block. An error is signaled if :preﬁx and :per-line-preﬁx are both used or if a :suﬃx, :preﬁx, or :pre-line-preﬁx argument does not evaluate to a string.
The list is interpreted as being a list that the body is responsible for printing. (See pprint-exit-if-list-exhausted and pprint-pop.) If list does not (at run time) evaluate to a list, it is printed using write. (This makes it easier to write printing functions that are robust in the face of malformed arguments.) If *print-circle* (and possibly also *print-shared*) is not nil and list is a circular (or shared) reference to a cons, then an appropriate “#n#” marker is printed. (This makes it easy to write printing functions that provide full support for circularity and sharing abbreviation.) If *print-level* is not nil and the logical block is at a dynamic nesting depth of greater than *print-level* in logical blocks, “#” is printed. (This makes it easy to write printing functions that provide full support for depth abbreviation.)
If any of the three preceding conditions occurs, the indicated output is printed on stream-symbol and the body is skipped along with the printing of the preﬁx and suﬃx. (If the body is not responsible for printing a list, then the ﬁrst two tests above can be turned oﬀ by supplying nil for the list argument.)
In addition to the list argument of pprint-logical-block, the arguments of the standard printing functions such as write, print, pprint, print1, and pprint, as well as the arguments of the standard format directives such as ~A, ~S, (and ~W) are all checked (when necessary) for circularity and sharing. However, such checking is not applied to the arguments of the functions write-line, write-string, and write-char or to the literal text output by format. A consequence of this is that you must use one of the latter functions if you want to print some literal text in the output that is not supposed to be checked for circularity or sharing. (See the examples below.) ___________________________________________________________________
Заметка для реализации: Detection of circularity and sharing is supported by the pretty printer by in essence performing the requested output twice. On the ﬁrst pass, circularities and sharing are detected and the actual outputting of characters is suppressed. On the second pass, the appropriate “#n=” and “#n#” markers are inserted and characters are output.
A consequence of this two-pass approach to the detection of circularity and sharing is that the body of a pprint-logical-block must not perform any side-eﬀects on the surrounding environment. This includes not modifying any variables that are bound outside of its scope. Obeying this restriction is facilitated by using pprint-pop, instead of an ordinary pop when traversing a list being printed by the body of a pprint-logical-block.)
pprint-exit-if-list-exhausted tests whether or not the list argument of pprint-logical-block has been exhausted (see pprint-pop). If this list has been reduced to nil, pprint-exit-if-list-exhausted terminates the execution of the immediately containing pprint-logical-block except for the printing of the suﬃx. Otherwise pprint-exit-if-list-exhausted returns nil. An error message is issued if pprint-exit-if-list-exhausted is used anywhere other than syntactically nested within a call on pprint-logical-block. It is undeﬁned what happens if pprint-pop is executed outside of the dynamic extent of this pprint-logical-block.
pprint-pop pops elements one at a time oﬀ the list argument of pprint-logical-block, taking care to obey *print-length*, *print-circle*, and *print-shared*. An error message is issued if it is used anywhere other than syntactically nested within a call on pprint-logical-block. It is undeﬁned what happens if pprint-pop is executed outside of the dynamic extent of this call on pprint-logical-block.
Each time pprint-pop is called, it pops the next value oﬀ the list argument of pprint-logical-block and returns it. However, before doing this, it performs three tests. If the remaining list is not a list (neither a cons nor nil), “. ” is printed followed by the remaining list. (This makes it easier to write printing functions that are robust in the face of malformed arguments.) If *print-length* is nil and pprint-pop has already been called *print-length* times within the immediately containing logical block, “...” is printed. (This makes it easy to write printing functions that properly handle *print-length*.) If *print-circle* (and possibly also *print-shared*) is not nil, and the remaining list is a circular (or shared) reference, then “. ” is printed followed by an appropriate “#n#” marker. (This catches instances of cdr circularity and sharing in lists.)
If any of the three preceding conditions occurs, the indicated output is printed on the pretty printing stream created by the immediately containing pprint-logical-block and the execution of the immediately containing pprint-logical-block is terminated except for the printing of the suﬃx.
If pprint-logical-block is given a list argument of nil—because it is not processing a list—pprint-pop can still be used to obtain support for *print-length* (see the example function pprint-vector below). In this situation, the ﬁrst and third tests above are disabled and pprint-pop always returns nil.
[Function]pprint-indent relative-to n &optional stream
pprint-indent speciﬁes the indentation to use in a logical block. Stream (which defaults to *standard-output*) follows the standard conventions for stream arguments to printing functions. The argument n speciﬁes the indentation in ems. If relative-to is :block, the indentation is set to the horizontal position of the ﬁrst character in the block plus n ems. If relative-to is :current, the indentation is set to the current output position plus n ems.
The argument n can be negative; however, the total indentation cannot be moved left of the beginning of the line or left of the end of the rightmost per-line preﬁx. Changes in indentation caused by pprint-indent do not take eﬀect until after the next line break. In addition, in miser mode all calls on pprint-indent are ignored, forcing the lines corresponding to the logical block to line up under the ﬁrst character in the block.
An error is signaled if a value other than :block or :current is supplied for relative-to. If stream is a pretty printing stream created by pprint-logical-block, pprint-indent sets the indentation in the innermost dynamically enclosing logical block. Otherwise, pprint-indent has no eﬀect. The value nil is always returned.
[Function]pprint-tab kind colnum colinc &optional stream
pprint-tab speciﬁes tabbing as performed by the standard format directive ~T. Stream (which defaults to *standard-output*) follows the standard conventions for stream arguments to printing functions. The arguments colnum and colinc correspond to the two parameters to ~T and are in terms of ems. The kind argument speciﬁes the style of tabbing. It must be one of :line (tab as by ~T) :section (tab as by ~T, but measuring horizontal positions relative to the start of the dynamically enclosing section), :line-relative (tab as by ~@T), or :section-relative (tab as by ~@T, but measuring horizontal positions relative to the start of the dynamically enclosing section). An error is signaled if any other value is supplied for kind. If stream is a pretty printing stream created by pprint-logical-block, tabbing is performed. Otherwise, pprint-tab has no eﬀect. The value nil is always returned.
[Function]pprint-ﬁll stream list &optional colon? atsign?
These three functions specify particular ways of pretty printing lists. Stream follows the standard conventions for stream arguments to printing functions. Each function prints parentheses around the output if and only if colon? (default t) is not nil. Each function ignores its atsign? argument and returns nil. (These two arguments are included in this way so that these functions can be used via ~/.../ and as set-pprint-dispatch functions as well as directly.) Each function handles abbreviation and the detection of circularity and sharing correctly and uses write to print list when given a non-list argument.
The function pprint-linear prints a list either all on one line or with each element on a separate line. The function pprint-ﬁll prints a list with as many elements as possible on each line. The function pprint-tabular is the same as pprint-ﬁll except that it prints the elements so that they line up in columns. This function takes an additional argument tabsize (default 16) that speciﬁes the column spacing in ems.
As an example of the interaction of logical blocks, conditional newlines, and indentation, consider the function pprint-defun below. This function pretty prints a list whose car is defun in the standard way assuming that the length of the list is exactly 4.
Suppose that one evaluates the following:
If the line width available is greater than or equal to 26, all of the output appears on one line. If the width is reduced to 25, a line break is inserted at the linear-style conditional newline before (* X Y), producing the output shown below. The (pprint-indent :block 1) causes (* X Y) to be printed at a relative indentation of 1 in the logical block.
If the width is 15, a line break is also inserted at the ﬁll-style conditional newline before the argument list. The argument list lines up under the function name because of the call on (pprint-indent :current 0) before the printing of the function name.
If *print-miser-width* were greater than or equal to 14, the output would have been entirely in miser mode. All indentation changes are ignored in miser mode and line breaks are inserted at miser-style conditional newlines. The result would have been as follows:
As an example of the use of a per-line preﬁx, consider that evaluating the expression
produces the output
with a line width of 20 and nil as the value of the printer control variable *print-miser-width*.
(If *print-miser-width* were not nil the output
might appear instead.)
As a more complex (and realistic) example, consider the function pprint-let below. This speciﬁes how to pretty print a let in the standard style. It is more complex than pprint-defun because it has to deal with nested structure. Also, unlike pprint-defun, it contains complete code to print readably any possible list that begins with the symbol let. The outermost pprint-logical-block handles the printing of the input list as a whole and speciﬁes that parentheses should be printed in the output. The second pprint-logical-block handles the list of binding pairs. Each pair in the list is itself printed by the innermost pprint-logical-block. (A loop is used instead of merely decomposing the pair into two elements so that readable output will be produced no matter whether the list corresponding to the pair has one element, two elements, or (being malformed) has more than two elements.) A space and a ﬁll-style conditional newline are placed after each pair except the last. The loop at the end of the topmost pprint-logical-block prints out the forms in the body of the let separated by spaces and linear-style conditional newlines.
Suppose that the following is evaluated with *print-level* having the value 4 and *print-circle* having the value t.
If the line length is greater than or equal to 77, the output produced appears on one line. However, if the line length is 76, line breaks are inserted at the linear-style conditional newlines separating the forms in the body and the output below is produced. Note that the degenerate binding pair X is printed readably even though it fails to be a list; a depth abbreviation marker is printed in place of (G 3); the binding pair (Z . 2) is printed readably even though it is not a proper list; and appropriate circularity markers are printed.
If the line length is reduced to 35, a line break is inserted at one of the ﬁll-style conditional newlines separating the binding pairs.
Suppose that the line length is further reduced to 22 and *print-length* is set to 3. In this situation, line breaks are inserted after both the ﬁrst and second binding pairs. In addition, the second binding pair is itself broken across two lines. Clause (b) of the description of ﬁll-style conditional newlines prevents the binding pair (Z . 2) from being printed at the end of the third line. Note that the length abbreviation hides the circularity from view and therefore the printing of circularity markers disappears.
The function pprint-tabular could be deﬁned as follows:
Evaluating the following with a line length of 25 produces the output shown.
The function below prints a vector using #(...) notation.
Evaluating the following with a line length of 15 produces the output shown.