Forum: Cleanup Issue: PPRINT-LOGICAL-BLOCK References: CLtL2, pp 752-755. Related issues: PRETTY-PRINT-INTERFACE Category: CHANGE ADDITION Edit history: Version 1, 12/11/91 by William Lott and Rob MacLachlan Problem description: The current specification for PPRINT-LOGICAL-BLOCK appears to be seriously broken. Specifically, there is no way PPRINT-LOGICAL-BLOCK can be used to pretty-print non-list objects and check these objects for circular/shared references. If the LIST argument to PPRINT-LOGICAL-BLOCK is not a list, then WRITE is used to print the object. If you know you are printing something that is not a list, then you have to specify NIL for the LIST argument effectively turning off the LISTP test. But it also turns off the circularity detection. You cannot get one without the other. In fact, there is no way to seperate the several different features of PPRINT-LOGICAL-BLOCK. For example, there is no way to initiate a new logical block without also checking for depth abbreviation. Proposal (PPRINT-LOGICAL-BLOCK:ADDITIONAL-KEYWORDS): Extend PPRINT-LOGICAL-BLOCK with the following additional keywords: ALLOW-ATOMS - If NIL (the default) then before the body is executed the object is checked to see if it is a list or not. If not, WRITE is used to print the object instead of the body. If ALLOW-ATOMS is T, then the body is executed irrespective of the type of the object. DESCEND - If non-NIL (the default) then before the body is executed the notion of the current level is increased and the system checks to see if depth abbreviation should be performed (based on the current depth and the values of *PRINT-LEVEL* and *PRINT-READABLY*). If depth abbreviation should be performed, then ``#'' is printed and the body is ignored. If DESCEND is NIL, the current level is left alone. CHECK-FOR-CIRCULARITY - If non-NIL (the default), then before the body is executed circularity detection is performed. If *PRINT-CIRCLE* is non-NIL circularity abbreviations are performed. If this is the only reference to this object, then nothing special is done. If this is the first of several references, then ``#n='' is printed and the body is still executed. If this is the second or latter reference to the object, then ``#n#'' is printed, and the body is skipped. If the object is NIL, then nothing special is done, irrespective of how many references to NIL there are. INITIATE-PRETTY-PRINTING - If non-NIL (the default), pretty-printing is initiated if it isn't already in effect for the duration of the body. Pretty printing is in effect if the stream argument is the result of some dynamically nesting use of PPRINT-LOGICAL-BLOCK for which INITIATE-PRETTY-PRINTING was true. If pretty printing is not in effect and INITIATE-PRETTY-PRINTING is NIL, then the prefix (or per-line prefix) and suffix are still printed before and after the body in executed and the stream argument is left as is. Abbreviations, etc. are tested for in the order listed above. PPRINT-EXIT-IF-LIST-EXHAUSTED can only be used in the body if ALLOW-ATOMS is NIL. It is an error if PPRINT-EXIT-IF-LIST-EXHAUSTED is used in the body of a PPRINT-LOGICAL-BLOCK for which ALLOW-ATOMS is not NIL. PPRINT-POP is also affected by ALLOW-ATOMS. If ALLOW-ATOMS is non-NIL, then the only thing PPRINT-POP does is check for length abbreviations. If ALLOW-ATOMS is NIL, then PPRINT-POP does everything it currently does: it checks to see if the remainder of the list is a non-NIL atom, checks for length abbreviations, and checks to see if the remainder is a circular reference. If PPRINT-POP is called more times than the length of the list, it quietly returns NIL. Examples: ;; This is the best you can do with the current definition: (defun old-pprint-vector (stream vector) (pprint-logical-block (stream nil :prefix "#(" :suffix ")") (dotimes (i (length vector)) (unless (zerop i) (write-char #\space stream) (pprint-newline :fill stream)) (pprint-pop) (write (aref vector i) :stream stream)))) => OLD-PPRINT-VECTOR (defvar *old-pp-dispatch* (copy-pprint-dispatch)) => *OLD-PP-DISPATCH* (set-pprint-dispatch 'simple-vector #'old-pprint-vector 0 *old-pp-dispatch*) => NIL (let ((*print-circle* t) (*print-level* 5) (*print-pprint-dispatch* *old-pp-dispatch*) (vector (make-array 3 :initial-element nil))) (setf (aref vector 1) vector) (prin1-to-string vector)) => "#(NIL #(NIL #(NIL #(NIL #(NIL # NIL) NIL) NIL) NIL) NIL)" ;; Note the lack of #n= and #n#. ;;; With the new keywords, you can do significantly better: (defun new-pprint-vector (stream vector) (pprint-logical-block (stream vector :allow-atoms t :prefix "#(" :suffix ")") (dotimes (i (length vector)) (unless (zerop i) (write-char #\space stream) (pprint-newline :fill stream)) (pprint-pop) (write (aref vector i) :stream stream)))) => NEW-PPRINT-VECTOR (defvar *new-pp-dispatch* (copy-pprint-dispatch)) => *NEW-PP-DISPATCH* (set-pprint-dispatch 'simple-vector #'new-pprint-vector 0 *new-pp-dispatch*) => NIL (let ((*print-circle* t) (*print-level* 5) (*print-pprint-dispatch* *new-pp-dispatch*) (vector (make-array 3 :initial-element nil))) (setf (aref vector 1) vector) (prin1-to-string vector)) "#1=#(NIL #1# NIL)" Test Cases: Same as the examples. Rationale: The current interface presented by PPRINT-LOGICAL-BLOCK does not allow the separate features to be used independently. This makes many desirable things (like pretty-print circular vectors) impossible to do portably which seriously depreciates the value of the pretty printer. Current practice: CMU Common Lisp implements this proposal. Nobody else does. Cost to Implementors: Very small. About two hours of work would suffice to add this functionality to XP. Cost to Users: The default values of the new keywords have been selected to result in identical behavior for code that does not use any of the new keywords. Cost of non-adoption: There will be no portable way to include circularity detection in user defined print methods for non-list objects. Performance impact: Very small, if any. Benefits: Users will have more control over the various aspects of printing. Esthetics: The various features of PPRINT-LOGICAL-BLOCK are no longer hard wired together. Discussion: Offering the additional keywords allows each of the different features of PPRINT-LOGICAL-BLOCK to be used independently.