[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

PRETTY-PRINT-INTERFACE, version 3 (supersedes 2 sent an hour ago!)

Version 3 is changed from version 1 as follows:
adds a functional interface to supplement the interface through FORMAT,
and reflects comments by Barrett and Pierson.

The document attached to version 1 has been omitted here, as the
mailer choked on it.  It should logically be inserted before the
functional interface attached here.


References:	Description of XP by Dick Waters (attached)
		*PRINT-PRETTY* (CLtL p. 371)
		WRITE (CLtL p. 382)
		PPRINT (CLtL p. 383)
		FORMAT (CLtL pp. 385-407)
		FORMAT ~T directive (CLtL pp. 398-399)
		FORMAT ~< directive (CLtL pp. 404-406)

Related issues: 


Edit history:	Version 1, 24-Feb-89 by Steele
		Version 2, 15-Mar-89 by Steele and Waters
		Version 3, 15-Mar-89 by Steele

Problem description:

At present Common Lisp provides no specification whatsoever of how
pretty-printing is to be accomplished, and no way for the user to control
it.  In particular, there is no protocol by which a user can write a
print-function for a structure, or a method for PRINT-OBJECT, that will
interact smoothly with the built-in pretty-printer in a portable manner.


Adopt the interfaces and protocols of the XP pretty-printer by Dick Waters,
described in full in the attached 12-page document.  Here is a very brief
summary of the proposal.

New variables:	*PRINT-DISPATCH*



New FORMAT directives:	~W  ~_  ~I  ~:T  ~/name/  ~<...~:>

New # reader macro:  #"..."

The function WRITE is extended to accept additional keyword arguments
:DISPATCH, :RIGHT-MARGIN, :LINES, and :MISER-WIDTH corresponding to the
first four of the new variables.

Finally, wherever in the attached document it says that certain constructs
support depth abbreviation and circularity detection, it should be noted
that this is so a fortiori, because *all* printing operations support them
properly.  Therefore, while the statements are correct, the possibly
misleading implication that they are the only way to achieve such
detection should be rectified if the text is taken over into the standard.

Examples:	See attached document.


There ought to be a good user interface to the pretty printer.
This is the only proposal for which there is a portable implementation
that has seen extensive use and is being made freely available.

Current practice:

XP son of PP son of GPRINT son of PRINT* is the latest in a line of pretty
printers that goes back 13 years.  All of these printers use essentially
the same basic algorithm and conceptual interface.  Further, except for
PRINT*, which was implemented solely to satisfy the author's personal
needs, each of these printers has had extensive use.  XP has been in
experimental use as the pretty printer in CMU Common Lisp for 6 months.  PP
has been the pretty printer in DEC Common Lisp for the past 3 years.  Prior
to three years ago, GPRINT was used for 2 years as the pretty printer in
DEC Common Lisp.  In addition, GPRINT has been the pretty printer in
various generations of Symbolics Lisp for upwards of 5 years.
(See Waters R.C., "User Format Control in a Lisp Prettyprinter", ACM TOPLAS,
5(4):513--531, October 1983.)

Cost to Implementors:

A fair amount of effort (perhaps a few man-weeks at most).
Source code for XP is available to all comers from Dick Waters, and
the system is documented in great detail:

Waters, Richard C., "XP: A Common Lisp Pretty Printing System",
Artificial Intelligence Laboratory Technical Memo 1102,
Massachusetts Institute of Technology, Cambridge MA, March 1989.

Cost to Users:  None (I think).  This is an upward-compatible extension.

Cost of non-adoption:  Continued inability for user print-functions
to interact with the pretty-printer in a useful and portable manner.

Performance impact:  XP is claimed to be quite fast.

Benefits:  User control of pretty-printing in a portable manner.


Using ~<...~:> may strike some as uncomfortably close in the syntactic
space of FORMAT directives to the existing ~<...~>.  However, it is very
unlikely that both of these directives (pretty-print logical block and
columnar justification, respectively) will be used in the same call to
FORMAT.  Previous versions of XP used ~!...~. instead of ~<...~:> but this
made FORMAT strings very difficult to read; it is preferable to have
a directive that looks like matching brackets of some sort.

Dan Pierson comments:  You might mention that some people will undoubtedly
find piling more hair on FORMAT ugly (of course these same people may
well find FORMAT in general ugly :-)).


Zetalisp used ~:T to mean pixelwise tabulation, so the use of ~:T
suggested here may be a problem.  If so, another suggestion for naming
this directive would be appropriate.

The ~/.../ directive is already in Zetalisp, and is not an idea new
to this proposal.

Guy Steele and Dick Waters strongly support this proposal.  (As an example,
Guy Steele has a portable simulator for Connection Machine Lisp, and would
like very much to have xappings and xectors pretty-print properly.)

Dan Pierson comments: You can add me to the list of strong supporters of
this proposal.  While the proposal is long and complex, it is supported by
a long history of usage in several different Lisp environments.  Unlike
some earlier members of this family, this version fits cleanly enough into
the rest of Common Lisp to warrant standardization.

The utility of *PRINT-LINES* becomes more obvious if it is pointed out
that Dick's pretty printers are implemented to print each line as it
is computed.  This means that a small value for *PRINT-LINES* saves
significant time as well as output medium space.  In fact, many people
find that a very pleasant REP loop is created by setting *PRINT-LINES*
to a value from 1-4, *PRINT-PRETTY* to T, and defining a short-name
function (say (PP*)) that funcalls *LAST-ABBREVIATED-PRINTING* with
abbreviation bound off.  This is almost as fast and compact as, and
MUCH more readable than, a non-pretty-printing REP loop.

The advantages of compiled format strings (format functions) should be
brought out as benefits in their own right.  The current proposal just
mentions them as a minor feature of XP.

At first this struck me a very cute end run around the failure of
STREAM-INFO, then I realized that one of the problems with STREAM-INFO
may have been that it was a standard at the wrong level.  STREAM-INFO
permitted people to use XP, but not to count on it.  This proposal
makes it possible to write portable code whose new data structures and
language elements print correctly in whatever Common Lisp environment
they're run in.  [End of comments by Pierson]
                  Functional Interface  

The primary interface to operations for dynamically determining the
arrangement of output is provided through FORMAT.  This is done,
because FORMAT strings are typically the most convenient way of
interacting with pretty printing.  However, these operations have
nothing inherently to do with FORMAT per se.  In particular, they can
also be accessed via the six functions and macros below.

WITHIN-LOGICAL-BLOCK (&KEY :STREAM :VAR :ARG                     [Macro]
                           :PREFIX :PER-LINE-PREFIX :SUFFIX)
                      &BODY BODY

In the manner of ~<...~:>, this macro causes printing to be
grouped into a logical block.  The value NIL is always returned.

:STREAM specifies the stream the logical block is to be printed on.
:STREAM defaults to *STANDARD-OUTPUT* and follows the standard
conventions for stream arguments to output functions---NIL stands for

:VAR (which defaults to *STANDARD-OUTPUT*) must be a symbol other than
T or NIL.  :VAR is bound to a special kind of stream that supports
dynamic decisions about the arrangement of output.

The BODY can contain any arbitrary Lisp forms.  All the standard
printing functions (e.g., WRITE, PRINC, TERPRI) can be used to print
output into :VAR.  All and only the output sent to :VAR is treated as
being in the logical block.  It is an error for the BODY to send any
output directly to :STREAM.

:SUFFIX (which defaults to the null string) specifies a suffix that is
printed just after the logical block.  :PREFIX specifies a prefix to be
printed before the beginning of the logical block.  :PER-LINE-PREFIX
specifies a prefix that is printed before the block and at the
beginning of each new line in the block.  It is an error for :PREFIX
and :PRE-LINE-PREFIX to both be used. If neither is used, a :PREFIX of
the null string is assumed.

:ARG (which defaults to NIL) is interpreted as being a list that BODY
is responsible for printing.  If :ARG is not a list, it is printed
using WRITE.  If *PRINT-CIRCLE* is not NIL and :ARG is a circular
reference to a cons, then an appropriate #n# marker is printed.  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.
If either of the three conditions above occures, the special output is
printed on :STREAM and the BODY is skipped along with the printing of
the prefix and suffix.


CONDITIONAL-NEWLINE is the functional equivalent of ~_.  STREAM (which
defaults to *STANDARD-OUTPUT*) follows the standard conventions for
stream arguments to printing functions.  The KIND argument specifies
the style of conditional newline.  It must be one of :LINEAR, :FILL,
:MISER, or :MANDATORY.  If STREAM is a special stream bound by
WITHIN-LOGICAL-BLOCK, a conditional newline is sent to it.  Otherwise,
CONDITIONAL-NEWLINE has no effect.  The value NIL is always returned.


LOGICAL-BLOCK-INDENT is the functional equivalent of ~I, STREAM
argument (which defaults to *STANDARD-OUTPUT*) follows the standard
conventions for stream arguments to printing functions.  N specifies
the amount of indentation.  If KIND is :FROM-START, this indentation is
relative to the start of the enclosing block (as for ~I).  If KIND is
:FROM-POSITION, the indentation is relative to the current output
position (as for ~:I).  It is an error for KIND to take on any other
value.  If STREAM is a special stream bound by WITHIN-LOGICAL-BLOCK,
LOGICAL-BLOCK-INDENT sets the indentation in the innermost enclosing
logical block.  Otherwise, LOGICAL-BLOCK-INDENT has no effect.  The
value NIL is always returned.


LOGICAL-BLOCK-TAB is the functional equivalent of ~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 numeric parameters to ~T.  The KIND
argument specifies the style of tabbing.  It must be one of :LINE (tab
using ~T), :BLOCK (tab using ~:T), :LINE-RELATIVE (tab using ~@T), or
:BLOCK-RELATIVE (tab using ~:@T).  If STREAM is a special stream bound
by WITHIN-LOGICAL-BLOCK, tabbing is performed.  Otherwise,
LOGICAL-BLOCK-TAB has no effect.  The value NIL is always returned.


LOGICAL-BLOCK-POP is identical to POP except that it supports
*PRINT-LENGTH* and *PRINT-CIRCLE*.  It is an error to use
LOGICAL-BLOCK-POP anywhere other than syntactically nested within a

ARGS must be a symbol or expression acceptable to POP.  STREAM (which
defaults to *STANDARD-OUTPUT*) follows the standard conventions for
stream arguments to printing functions.  If STREAM is a special stream
bound by WITHIN-LOGICAL-BLOCK, then LOGICAL-BLOCK-POP performs the
special operations described below.  Otherwise, LOGICAL-BLOCK-POP is
identical to POP.

Each time LOGICAL-BLOCK-POP is called, it performs three tests.  if
ARGS is not a cons, ". " is printed followed by ARGS.  If
*PRINT-LENGTH* is NIL and LOGICAL-BLOCK-POP has already been called
*PRINT-LENGTH* times within the immediately containing logical block,
"..." is printed. If *PRINT-CIRCLE* is not NIL, and ARGS is a circular
reference, then ". " is printed followed by an appropriate #n# marker.
If either of the three conditions above occurs, the special output is
printed on :STREAM and the execution of the immediately containing
WITHIN-LOGICAL-BLOCK is terminated except for the printing of the
suffix.  Otherwise, LOGICAL-BLOCK-POP pops the top value off of ARGS
and returns this value.

LOGICAL-BLOCK-COUNT is identical to LOGICAL-BLOCK-POP except that it
does not take an ARGS argument, always returns NIL, and only performs
the second test discussed above.  It is useful when the components of a
non-list are being printed.

Using the functions above, TABULAR-STYLE could be defined as follows.
    (defun tabular-style (stream list &optional (colon? T) atsign? 
						(tabsize nil))
	(declare (ignore atsign?))
      (if (null tabsize) (setq tabsize 16))
      (within-logical-block (:var s :stream stream :arg list
			     :prefix (if colon? "(" "")
			     :suffix (if colon? ")" ""))
       (when list
	 (loop (write (logical-block-pop list s) :stream s)
	       (if (null list) (return nil))
	       (write-char #\space s)
	       (logical-block-tab :block-relative 0 tabsize s)
	       (conditional-newline :fill s)))))

The function below prints a vector using #(...) notation.
    (defun print-vector (v *standard-output*)
      (within-logical-block (:prefix "#(" :suffix ")")
	(let ((end (length v)) (i 0))
	  (when (plusp end)
	    (loop (logical-block-count)
		  (write (aref v i))
		  (if (= (incf i) end) (return nil))
		  (write-char #\space)
		  (conditional-newline :fill))))))

[End of attached document]