Apropos

22.2 The Lisp Pretty Printer

22.2.1 Pretty Printer Concepts

The facilities provided by the pretty printer permit programs to redefine the way in which code is displayed, and allow the full power of pretty printing to be applied to complex combinations of data structures.

Whether any given style of output is in fact “pretty” is inherently a somewhat subjective issue. However, since the effect of the pretty printer can be customized by conforming programs, the necessary flexibility is provided for individual programs to achieve an arbitrary degree of aesthetic control.

By providing direct access to the mechanisms within the pretty printer that make dynamic decisions about layout, the macros and functions pprint-logical-block, pprint-newline, and pprint-indent make it possible to specify pretty printing layout rules as a part of any function that produces output. They also make it very easy for the detection of circularity and sharing, and abbreviation based on length and nesting depth to be supported by the function.

The pretty printer is driven entirely by dispatch based on the value of *print-pprint-dispatch*. The function set-pprint-dispatch makes it possible for conforming programs to associate new pretty printing functions with a type.

22.2.1.1 Dynamic Control of the Arrangement of Output

The actions of the pretty printer when a piece of output is too large to fit in the space available can be precisely controlled. 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 22–3 shows a schematic piece of output. Each of the characters in the output is represented by “-”. The positions of conditional newlines are indicated by digits. The beginnings and ends of logical blocks are indicated 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 1. Logical blocks nested within the output are specified by the macro pprint-logical-block. Conditional newline positions are specified by calls to 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 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 22–3, 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.

<-1---<--<--2---3->--4-->-> 
000000000000000000000000000 
11 111111111111111111111111 
          22 222 
             333 3333 
       44444444444444 44444
Figure 22–3. Example of Logical Blocks, Conditional Newlines, and Sections

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.

22.2.1.2 Format Directive Interface

The primary interface to operations for dynamically determining the arrangement of output is provided through the functions and macros of the pretty printer. Figure 22–4 shows the defined names related to pretty printing.

Figure 22–5 identifies a set of format directives which serve as an alternate interface to the same pretty printing operations in a more textually compact form.

~I ~W ~<...~:>
~:T ~/.../ ~_
Figure 22–5. Format directives related to Pretty Printing

22.2.1.3 Compiling Format Strings

A format string is essentially a program in a special-purpose language that performs printing, and that is interpreted by the function format. The formatter macro provides the efficiency of using a compiled function to do that same printing but without losing the textual compactness of format strings.

A format control is either a format string or a function that was returned by the the formatter macro.

22.2.1.4 Pretty Print Dispatch Tables

A pprint dispatch table is a mapping from keys to pairs of values. Each key is a type specifier. The values associated with a key are a “function” (specifically, a function designator or nil) and a “numerical priority” (specifically, a real). Basic insertion and retrieval is done based on the keys with the equality of keys being tested by equal.

When *print-pretty* is true, the current pprint dispatch table (in *print-pprint-dispatch*) controls how objects are printed. The information in this table takes precedence over all other mechanisms for specifying how to print objects. In particular, it has priority over user-defined print-object methods because the current pprint dispatch table is consulted first.

The function is chosen from the current pprint dispatch table by finding the highest priority function that is associated with a type specifier that matches the object; if there is more than one such function, it is implementation-dependent which is used.

However, if there is no information in the table about how to pretty print a particular kind of object, a function is invoked which uses print-object to print the object. The value of *print-pretty* is still true when this function is called, and individual methods for print-object might still elect to produce output in a special format conditional on the value of *print-pretty*.

22.2.1.5 Pretty Printer Margins

A primary goal of pretty printing is to keep the output between a pair of margins. The column where the output begins is taken as the left margin. If the current column cannot be determined at the time output begins, the left margin is assumed to be zero. The right margin is controlled by *print-right-margin*.

22.2.2 Examples of using the Pretty Printer

As an example of the interaction of logical blocks, conditional newlines, and indentation, consider the function simple-pprint-defun below. This function prints out lists whose cars are defun in the standard way assuming that the list has exactly length 4.

(defun simple-pprint-defun (*standard-output* list) 
  (pprint-logical-block (*standard-output* 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:

(simple-pprint-defun *standard-output* '(defun prod (x y) (* x y)))

If the line width available is greater than or equal to 26, then all of the output appears on one line. If the line width available is reduced to 25, a line break is inserted at the linear-style conditional newline before the expression (* x y), producing the output shown. 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 line width available is 15, a line break is also inserted at the fill style conditional newline before the argument list. The call on (pprint-indent :current 0) causes the argument list to line up under the function name.

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

If *print-miser-width* were greater than or equal to 14, the example output above would have been as follows, because all indentation changes are ignored in miser mode and line breaks are inserted at miser-style conditional newlines.

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

As an example of a per-line prefix, consider that evaluating the following produces the output shown with a line width of 20 and *print-miser-width* of nil.

(pprint-logical-block (*standard-output* nil :per-line-prefix ";;; ") 
  (simple-pprint-defun *standard-output* '(defun prod (x y) (* x y)))) 

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

As a more complex (and realistic) example, consider the function pprint-let below. This specifies how to print a let form in the traditional style. It is more complex than the example above, because it has to deal with nested structure. Also, unlike the example above it contains complete code to readably print any possible list that begins with the symbol let. The outermost pprint-logical-block form 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 form handles the list of binding pairs. Each pair in the list is itself printed by the innermost pprint-logical-block. (A loop form is used instead of merely decomposing the pair into two objects 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 form prints out the forms in the body of the let form separated by spaces and linear-style conditional newlines.

(defun pprint-let (*standard-output* 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 one evaluates the following with *print-level* being 4, and *print-circle* being true.

(pprint-let *standard-output* 
            '#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 (see the function pprint-newline) 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 next function prints a vector using “#(...)” notation.

(defun pprint-vector (*standard-output* 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 *standard-output* '#(12 34 567 8 9012 34 567 89 0 1 23)) 

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

As examples of the convenience of specifying pretty printing with format strings, consider that the functions simple-pprint-defun and pprint-let used as examples above can be compactly defined as follows. (The function pprint-vector cannot be defined using format because the data structure it traverses is not a list.)

(defun simple-pprint-defun (*standard-output* list) 
  (format T "~:<~W ~@_~:I~W ~:_~W~1I ~_~W~:>" list)) 

(defun pprint-let (*standard-output* list) 
  (format T "~:<~W~^~:<~@{~:<~@{~W~^~_~}~:>~^~:_~}~:>~1I~@{~^~_~W~}~:>" list))

In the following example, the first form restores *print-pprint-dispatch* to the equivalent of its initial value. The next two forms then set up a special way to pretty print ratios. Note that the more specific type specifier has to be associated with a higher priority.

(setq *print-pprint-dispatch* (copy-pprint-dispatch nil)) 

(set-pprint-dispatch 'ratio 
  #'(lambda (s obj) 
      (format s "#.(/ ~W ~W)" 
                (numerator obj) (denominator obj)))) 

(set-pprint-dispatch '(and ratio (satisfies minusp)) 
  #'(lambda (s obj) 
      (format s "#.(- (/ ~W ~W))" 
              (- (numerator obj)) (denominator obj))) 
  5) 

(pprint '(1/3 -2/3)) 
(#.(/ 1 3) #.(- (/ 2 3)))

The following two forms illustrate the definition of pretty printing functions for types of code. The first form illustrates how to specify the traditional method for printing quoted objects using single-quote. Note the care taken to ensure that data lists that happen to begin with quote will be printed readably. The second form specifies that lists beginning with the symbol my-let should print the same way that lists beginning with let print when the initial pprint dispatch table is in effect.

(set-pprint-dispatch '(cons (member quote)) () 
  #'(lambda (s list) 
      (if (and (consp (cdr list)) (null (cddr list))) 
         (funcall (formatter "'~W") s (cadr list)) 
         (pprint-fill s list)))) 

(set-pprint-dispatch '(cons (member my-let)) 
                     (pprint-dispatch '(let) nil))

The next example specifies a default method for printing lists that do not correspond to function calls. Note that the functions pprint-linear, pprint-fill, and pprint-tabular are all defined with optional colon-p and at-sign-p arguments so that they can be used as pprint dispatch functions as well as ~/.../ functions.

(set-pprint-dispatch '(cons (not (and symbol (satisfies fboundp)))) 
                     #'pprint-fill -5) 

;; Assume a line length of 9 
(pprint '(0 b c d e f g h i j k)) 
(0 b c d 
 e f g h 
 i j k)

This final example shows how to define a pretty printing function for a user defined data structure.

(defstruct family mom kids) 

(set-pprint-dispatch 'family 
  #'(lambda (s f) 
      (funcall (formatter "~@<#<~;~W and ~2I~_~/pprint-fill/~;>~:>") 
              s (family-mom f) (family-kids f))))

The pretty printing function for the structure family specifies how to adjust the layout of the output so that it can fit aesthetically into a variety of line widths. In addition, it obeys the printer control variables *print-level*, *print-length*, *print-lines*, *print-circle* and *print-escape*, and can tolerate several different kinds of malformity in the data structure. The output below shows what is printed out with a right margin of 25, *print-pretty* being true, *print-escape* being false, and a malformed kids list.

(write (list 'principal-family 
             (make-family :mom "Lucy" 
                          :kids '("Mark" "Bob" . "Dan"))) 
       :right-margin 25 :pretty T :escape nil :miser-width nil) 
(PRINCIPAL-FAMILY 
 #<Lucy and 
     Mark Bob . Dan>)

Note that a pretty printing function for a structure is different from the structure’s print-object method. While print-object methods are permanently associated with a structure, pretty printing functions are stored in pprint dispatch tables and can be rapidly changed to reflect different printing needs. If there is no pretty printing function for a structure in the current pprint dispatch table, its print-object method is used instead.

22.2.3 Notes about the Pretty Printer’s Background

For a background reference to the abstract concepts detailed in this section, see XP: A Common Lisp Pretty Printing System. The details of that paper are not binding on this document, but may be helpful in establishing a conceptual basis for understanding this material.