27.3 Dynamic Control of the Arrangement of Output

The following functions and macros support precise control of what should be done when a piece of output is too large to fit 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 define these terms.

The first line of figure 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 figure 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 figure 27.1. Logical blocks nested within the output are specified by the macro pprint-logical-block. Conditional newline positions are specified by calls on pprint-newline. Each conditional newline defines 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 figure 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 figure 27.1, the first 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 first conditional newline.



Изображение 27.1: Example of Logical Blocks, Conditional Newlines, and Sections

                 <-1—<–<–2—3->–4–>->
                 000000000000000000000000000
                 11 111111111111111111111111
                           22 222
                              333 3333
                        44444444444444 44444


Whenever possible, the pretty printer displays the entire contents of a section on a single line. However, if the section is too long to fit 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 specifies the style of conditional newline. It must be one of :linear, :fill, :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 satisfied. Otherwise, pprint-newline has no effect. The value nil is always returned.

If kind is :linear, it specifies 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 effect 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 specifies 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 effect in the immediately containing logical block. The effect of this is that miser-style conditional newlines act like linear-style conditional newlines, but only when miser style is in effect. Miser style is in effect 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 :fill, it specifies a ‘fill-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 effect in the immediately containing logical block. If a logical block is broken up into a number of subsections by fill-style conditional newlines, the basic effect is that the logical block is printed with as many subsections as possible on each line. However, if miser style is in effect, fill-style conditional newlines act like linear-style conditional newlines.

If kind is :mandatory, it specifies 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 first 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 prefix has been specified (see pprint-logical-block), that prefix will always be printed no matter how a newline originates.


[Макрос] pprint-logical-block (stream-symbol list[[{:prefix | :per-line-prefix} p | :suffix s]]){form}*

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 undefined what happens if output is attempted outside of this extent to the pretty printing stream created. It is unspecified what happens if, within this extent, any output is sent directly to the underlying destination stream (by calling write-char, for example).

The :suffix, :prefix, and :per-line-prefix arguments must all be expressions that (at run time) evaluate to strings. The :suffix argument s (which defaults to the null string) specifies a suffix that is printed just after the logical block. The :prefix and :per-line-prefix arguments are mutually exclusive. If neither :prefix nor :per-line-prefix is specified, a :prefix of the null string is assumed. The :prefix argument specifies a prefix p that is printed before the beginning of the logical block. The :per-line-prefix specifies a prefix p that is printed before the block and at the beginning of each subsequent line in the block. An error is signaled if :prefix and :per-line-prefix are both used or if a :suffix, :prefix, or :pre-line-prefix 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 prefix and suffix. (If the body is not responsible for printing a list, then the first two tests above can be turned off 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 first 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-effects 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

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 suffix. 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 undefined what happens if pprint-pop is executed outside of the dynamic extent of this pprint-logical-block.


[Макрос] pprint-pop

pprint-pop pops elements one at a time off 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 undefined 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 off 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 suffix.

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 first and third tests above are disabled and pprint-pop always returns nil.


[Function] pprint-indent relative-to n &optional stream

pprint-indent specifies 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 specifies the indentation in ems. If relative-to is :block, the indentation is set to the horizontal position of the first 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 prefix. Changes in indentation caused by pprint-indent do not take effect 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 first 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 effect. The value nil is always returned.


[Function] pprint-tab kind colnum colinc &optional stream

pprint-tab specifies 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 specifies 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 effect. The value nil is always returned.


[Function] pprint-fill stream list &optional colon? atsign?
[Function] pprint-linear stream list &optional colon? atsign?
[Function] pprint-tabular stream list &optional colon? atsign? tabsize

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-fill prints a list with as many elements as possible on each line. The function pprint-tabular is the same as pprint-fill except that it prints the elements so that they line up in columns. This function takes an additional argument tabsize (default 16) that specifies 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.

;;; Pretty printer function for DEFUN forms.

(defun pprint-defun (list)
  (pprint-logical-block (nil list :prefix "(" :suffix ")")
    (write (first list))
    (write-char #\space)
    (pprint-newline :miser)
    (pprint-indent :current 0)
    (write (second list))
    (write-char #\space)
    (pprint-newline :fill)
    (write (third list))
    (pprint-indent :block 1)
    (write-char #\space)
    (pprint-newline :linear)
    (write (fourth list))))

Suppose that one evaluates the following:

(pprint-defun ’(defun prod (x y) (* x y)))

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.

(DEFUN PROD (X Y)
  (* X Y))

If the width is 15, a line break is also inserted at the fill-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.

(DEFUN PROD
       (X Y)
  (* X Y))

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:

(DEFUN
 PROD
 (X Y)
 (* X Y))

As an example of the use of a per-line prefix, consider that evaluating the expression

(pprint-logical-block (nil nil :per-line-prefix ";;; ")
  (pprint-defun ’(defun prod (x y) (* x y))))

produces the output

;;; (DEFUN PROD
;;;        (X Y)
;;;   (* X Y))

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

;;; (DEFUN
;;;  PROD
;;;  (X Y)
;;;  (* X Y))

might appear instead.)

As a more complex (and realistic) example, consider the function pprint-let below. This specifies 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 specifies 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 fill-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.

;;; Pretty printer function for LET forms,
;;; carefully coded to handle malformed binding pairs.

(defun pprint-let (list)
  (pprint-logical-block (nil list :prefix "(" :suffix ")")
    (write (pprint-pop))
    (pprint-exit-if-list-exhausted)
    (write-char #\space)
    (pprint-logical-block
        (nil (pprint-pop) :prefix "(" :suffix ")")
      (pprint-exit-if-list-exhausted)
      (loop (pprint-logical-block
                (nil (pprint-pop) :prefix "(" :suffix ")")
              (pprint-exit-if-list-exhausted)
              (loop (write (pprint-pop))
                    (pprint-exit-if-list-exhausted)
                    (write-char #\space)
                    (pprint-newline :linear)))
            (pprint-exit-if-list-exhausted)
            (write-char #\space)
            (pprint-newline :fill)))
    (pprint-indent :block 1)
    (loop (pprint-exit-if-list-exhausted)
          (write-char #\space)
          (pprint-newline :linear)
          (write (pprint-pop)))))

Suppose that the following is evaluated with *print-level* having the value 4 and *print-circle* having the value t.

(pprint-let ’#1=(let (x (*print-length* (f (g 3)))
                      (z . 2) (k (car y)))
                  (setq x (sqrt z)) #1#))

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.

#1=(LET (X (*PRINT-LENGTH* (F #)) (Z . 2) (K (CAR Y)))
     (SETQ X (SQRT Z))
     #1#)

If the line length is reduced to 35, a line break is inserted at one of the fill-style conditional newlines separating the binding pairs.

#1=(LET (X (*PRINT-PRETTY* (F #))
         (Z . 2) (K (CAR Y)))
     (SETQ X (SQRT Z))
     #1#)

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 first and second binding pairs. In addition, the second binding pair is itself broken across two lines. Clause (b) of the description of fill-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.

(LET (X
      (*PRINT-LENGTH*
       (F #))
      (Z . 2) ...)
  (SETQ X (SQRT Z))
  ...)

The function pprint-tabular could be defined as follows:

(defun pprint-tabular (s list &optional (c? t) a? (size 16))
  (declare (ignore a?))
  (pprint-logical-block
      (s list :prefix (if c? "(" "") :suffix (if c? ")" ""))
    (pprint-exit-if-list-exhausted)
    (loop (write (pprint-pop) :stream s)
          (pprint-exit-if-list-exhausted)
          (write-char #\space s)
          (pprint-tab :section-relative 0 size s)
          (pprint-newline :fill s))))

Evaluating the following with a line length of 25 produces the output shown.

(princ "Roads ")
(pprint-tabular nil ’(elm main maple center) nil nil 8)

Roads ELM     MAIN
      MAPLE   CENTER

The function below prints a vector using #(...) notation.

(defun pprint-vector (v)
  (pprint-logical-block (nil nil :prefix "#(" :suffix ")")
    (let ((end (length v)) (i 0))
      (when (plusp end)
        (loop (pprint-pop)
              (write (aref v i))
              (if (= (incf i) end) (return nil))
              (write-char #\space)
              (pprint-newline :fill))))))

Evaluating the following with a line length of 15 produces the output shown.

(pprint-vector ’#(12 34 567 8 9012 34 567 89 0 1 23))

#(12 34 567 8
  9012 34 567
  89 0 1 23)