4e987026 |
;;; pprint.scm -- xp pretty-printer in Scheme
;;;
;;; author : Sandra Loosemore
;;; date : 29 Oct 1991
;;;
;;;
;;; This code is adapted from the XP pretty printer originally written
;;; in Common Lisp by Dick Waters. Here is the copyright notice attached
;;; to the original XP source file:
;;;
;;;------------------------------------------------------------------------
;;;
;;; Copyright 1989,1990 by the Massachusetts Institute of Technology,
;;; Cambridge, Massachusetts.
;;;
;;; Permission to use, copy, modify, and distribute this software and its
;;; documentation for any purpose and without fee is hereby granted,
;;; provided that this copyright and permission notice appear in all
;;; copies and supporting documentation, and that the name of M.I.T. not
;;; be used in advertising or publicity pertaining to distribution of the
;;; software without specific, written prior permission. M.I.T. makes no
;;; representations about the suitability of this software for any
;;; purpose. It is provided "as is" without express or implied warranty.
;;;
;;; M.I.T. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
;;; ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
;;; M.I.T. BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
;;; ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
;;; WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
;;; ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
;;; SOFTWARE.
;;;
;;;------------------------------------------------------------------------
;;;
;;;=====================================================================
;;; Variables
;;;=====================================================================
;;; External variables. These may be specially bound by user code.
(define *print-escape* '#t)
(define *print-circle* '#f)
(define *print-level* '#f)
(define *print-length* '#f)
(define *print-base* 10)
(define *print-radix* '#f)
(define *print-shared* '#f)
(define *print-pretty* '#f)
(define *print-right-margin* '#f)
(define *print-miser-width* 40)
(define *print-lines* '#f)
(define *default-right-margin* 70)
(define *last-abbreviated-printing*
(lambda maybe-stream
(declare (ignore maybe-stream))
'#f))
(define *print-dispatch* '#f) ; initialized later
(define *print-structure* '#f)
(define *print-structure-slots* '#t)
;;; *** These variables aren't really supported, but they should be.
(define *print-readably* '#f)
(define *print-case* 'upcase)
;;; Internal variables. These are all specially rebound when we initiate
;;; printing to an XP stream.
(define *xp.current-level* 0)
(define *xp.current-length* 0)
(define *xp.abbreviation-happened* '#f)
(define *xp.locating-circularities* '#f)
(define *xp.parents* '())
(define *xp.circularity-hash-table* '#f)
(define *xp.line-limit-abbreviation-exit*
(lambda values
(declare (ignore values))
(error "No line limit abbreviation exit in this extent.")))
;;;=====================================================================
;;; Dispatching
;;;=====================================================================
;;; Since Scheme doesn't have type specifiers or named structures,
;;; the dispatch mechanism defined for the Common Lisp XP won't work
;;; very well. A more general alternative might be to maintain a
;;; sorted list of <priority predicate printer> tuples, but having to
;;; try each of these in sequence could get very slow.
;;;
;;; What I've decided to to instead is to have the value of
;;; *print-dispatch* be a user-defined dispatcher
;;; function: given an object, it should return a function to print it,
;;; or #f. In the latter case, the object is printed in some default
;;; way.
;;;
;;; The standard dispatcher function is defined towards the bottom
;;; of this file. If you are writing your own dispatcher, you should
;;; probably call this function as the fall-through case.
(define (xp.get-printer object)
(funcall (dynamic *print-dispatch*) object))
;;;=====================================================================
;;; Internal data structures
;;;=====================================================================
(define-integrable xp.block-stack-entry-size 1)
(define-integrable xp.prefix-stack-entry-size 5)
(define-integrable xp.queue-entry-size 7)
(define-integrable xp.buffer-entry-size 1)
(define-integrable xp.prefix-entry-size 1)
(define-integrable xp.suffix-entry-size 1)
(define-integrable xp.block-stack-min-size (* 35 xp.block-stack-entry-size))
(define-integrable xp.prefix-stack-min-size (* 30 xp.prefix-stack-entry-size))
(define-integrable xp.queue-min-size (* 75 xp.queue-entry-size))
(define-integrable xp.buffer-min-size 256)
(define-integrable xp.prefix-min-size 256)
(define-integrable xp.suffix-min-size 256)
;;; The xp stream structure.
;;; Fields without defaults are initialized by xp.initialize-xp, below.
(define-struct xp
(prefix xp.)
(predicate xp.xp-structure-p)
(slots
(base-stream (type t) (default '#f))
(linel (type fixnum) (default 0))
(line-limit (type (maybe fixnum)) (default '#f))
(line-no (type fixnum) (default 0))
(char-mode (type (enum #f up down cap0 cap1 capw)) (default '#f))
(char-mode-counter (type fixnum) (default 0))
;; number of logical blocks at qright that are started but not ended.
(depth-in-blocks (type fixnum) (default 0))
;; This stack is pushed and popped in accordance with the way blocks
;; are nested at the moment they are entered into the queue.
(block-stack (type vector) (default (make-vector xp.block-stack-min-size)))
;; Pointer into block-stack vector.
(block-stack-ptr (type fixnum) (default 0))
;; This is a string that builds up the line images that will be printed out.
(buffer (type string) (default (make-string xp.buffer-min-size)))
;; The output character position of the first character in the buffer;
;; nonzero only if a partial line has been output.
(charpos (type fixnum) (default 0))
;; The index in the buffer where the next character is to be inserted.
(buffer-ptr (type fixnum) (default 0))
;; This is used in computing total lengths. It is changed to reflect
;; all shifting and insertion of prefixes so that total length computes
;; things as they would be if they were all on one line.
(buffer-offset (type fixnum) (default 0))
;; The queue of action descriptors. The value is a vector.
(queue (type vector) (default (make-vector xp.queue-min-size)))
;; Index of next queue entry to dequeue.
(qleft (type fixnum) (default 0))
;; Index of last entry queued; queue is empty when (> qleft qright).
(qright (type fixnum) (default 0))
;; This stores the prefix that should be used at the start of the line.
(prefix (type string) (default (make-string xp.buffer-min-size)))
;; This stack is pushed and popped in accordance with the way blocks
;; are nested at the moment things are taken off the queue and printed.
(prefix-stack (type vector) (default (make-vector xp.prefix-stack-min-size)))
;; Index into prefix-stack.
(prefix-stack-ptr (type fixnum) (default 0))
;; This stores the suffixes that have to be pritned to close of the
;; current open blocks. For convenience in popping, the whole suffix
;; is stored in reverse order.
(suffix (type string) (default (make-string xp.buffer-min-size)))
))
(define (xp.make-xp-structure)
(make xp))
;;; Positions within the buffer are kept in three ways:
;;; * Buffer position (eg BUFFER-PTR)
;;; * Line position (eg (+ BUFFER-PTR CHARPOS)).
;;; Indentations are stored in this form.
;;; * Total position if all on one line (eg (+ BUFFER-PTR BUFFER-OFFSET))
;;; Positions are stored in this form.
(define-local-syntax (xp.lp<-bp xp . maybe-ptr)
(let ((ptr (if (not (null? maybe-ptr))
(car maybe-ptr)
`(xp.buffer-ptr ,xp))))
`(+ ,ptr (xp.charpos ,xp))))
(define-local-syntax (xp.tp<-bp xp)
`(+ (xp.buffer-ptr ,xp) (xp.buffer-offset ,xp)))
(define-local-syntax (xp.bp<-lp xp ptr)
`(- ,ptr (xp.charpos ,xp)))
(define-local-syntax (xp.bp<-tp xp ptr)
`(- ,ptr (xp.buffer-offset ,xp)))
(define-local-syntax (xp.lp<-tp xp ptr)
`(xp.lp<-bp ,xp (xp.bp<-tp ,xp ,ptr)))
;;; Define some macros for growing the various stacks in the xp-structure.
(define-local-syntax (xp.check-block-stack-size xp ptr)
`(setf (xp.block-stack ,xp)
(xp.grow-vector (xp.block-stack ,xp) ,ptr xp.block-stack-entry-size)))
(define-local-syntax (xp.check-prefix-size xp ptr)
`(setf (xp.prefix ,xp)
(xp.grow-string (xp.prefix ,xp) ,ptr xp.prefix-entry-size)))
(define-local-syntax (xp.check-prefix-stack-size xp ptr)
`(setf (xp.prefix-stack ,xp)
(xp.grow-vector (xp.prefix-stack ,xp) ,ptr xp.prefix-stack-entry-size)))
(define-local-syntax (xp.check-queue-size xp ptr)
`(setf (xp.queue ,xp)
(xp.grow-vector (xp.queue ,xp) ,ptr xp.queue-entry-size)))
(define-local-syntax (xp.check-buffer-size xp ptr)
`(setf (xp.buffer ,xp)
(xp.grow-string (xp.buffer ,xp) ,ptr xp.buffer-entry-size)))
(define-local-syntax (xp.check-suffix-size xp ptr)
`(setf (xp.suffix ,xp)
(xp.grow-string (xp.suffix ,xp) ,ptr xp.suffix-entry-size)))
(define (xp.grow-vector old ptr entry-size)
(let ((end (vector-length old)))
(if (> ptr (- end entry-size))
(let ((new (make-vector (+ ptr 50))))
(dotimes (i end)
(setf (vector-ref new i) (vector-ref old i)))
new)
old)))
(define (xp.grow-string old ptr entry-size)
(let ((end (string-length old)))
(if (> ptr (- end entry-size))
(let ((new (make-string (+ ptr 50))))
(dotimes (i end)
(setf (string-ref new i) (string-ref old i)))
new)
old)))
;;; Things for manipulating the block stack.
(define-local-syntax (xp.section-start xp)
`(vector-ref (xp.block-stack ,xp) (xp.block-stack-ptr ,xp)))
(define (xp.push-block-stack xp)
(incf (xp.block-stack-ptr xp) xp.block-stack-entry-size)
(xp.check-block-stack-size xp (xp.block-stack-ptr xp)))
(define (xp.pop-block-stack xp)
(decf (xp.block-stack-ptr xp) xp.block-stack-entry-size))
;;; Prefix stack manipulations
(define-local-syntax (xp.prefix-ptr xp)
`(vector-ref (xp.prefix-stack ,xp) (xp.prefix-stack-ptr ,xp)))
(define-local-syntax (xp.suffix-ptr xp)
`(vector-ref (xp.prefix-stack ,xp) (+ (xp.prefix-stack-ptr ,xp) 1)))
(define-local-syntax (non-blank-prefix-ptr xp)
`(vector-ref (xp.prefix-stack ,xp) (+ (xp.prefix-stack-ptr ,xp) 2)))
(define-local-syntax (initial-prefix-ptr xp)
`(vector-ref (xp.prefix-stack ,xp) (+ (xp.prefix-stack-ptr ,xp) 3)))
(define-local-syntax (xp.section-start-line xp)
`(vector-ref (xp.prefix-stack ,xp) (+ (xp.prefix-stack-ptr ,xp) 4)))
(define (xp.push-prefix-stack xp)
(let ((old-prefix 0)
(old-suffix 0)
(old-non-blank 0))
(when (not (negative? (xp.prefix-stack-ptr xp)))
(setf old-prefix (xp.prefix-ptr xp))
(setf old-suffix (xp.suffix-ptr xp))
(setf old-non-blank (non-blank-prefix-ptr xp)))
(incf (xp.prefix-stack-ptr xp) xp.prefix-stack-entry-size)
(xp.check-prefix-stack-size xp (xp.prefix-stack-ptr xp))
(setf (xp.prefix-ptr xp) old-prefix)
(setf (xp.suffix-ptr xp) old-suffix)
(setf (non-blank-prefix-ptr xp) old-non-blank)))
(define (xp.pop-prefix-stack xp)
(decf (xp.prefix-stack-ptr xp) xp.prefix-stack-entry-size))
;;; The queue entries have several parts:
;;; QTYPE one of :NEWLINE/:IND/:START-BLOCK/:END-BLOCK
;;; QKIND :LINEAR/:MISER/:FILL/:MANDATORY or :UNCONDITIONAL/:FRESH
;;; or :BLOCK/:CURRENT
;;; QPOS total position corresponding to this entry
;;; QDEPTH depth in blocks of this entry.
;;; QEND offset to entry marking end of section this entry starts.
;; (NIL until known.)
;;; Only :start-block and non-literal :newline entries can start sections.
;;; QOFFSET offset to :END-BLOCK for :START-BLOCK (NIL until known).
;;; QARG for :IND indentation delta
;;; for :START-BLOCK suffix in the block if any.
;;; or if per-line-prefix then cons of suffix and
;;; per-line-prefix.
;;; for :END-BLOCK suffix for the block if any.
(define-local-syntax (xp.qtype xp index)
`(vector-ref (xp.queue ,xp) ,index))
(define-local-syntax (xp.qkind xp index)
`(vector-ref (xp.queue ,xp) (1+ ,index)))
(define-local-syntax (xp.qpos xp index)
`(vector-ref (xp.queue ,xp) (+ ,index 2)))
(define-local-syntax (xp.qdepth xp index)
`(vector-ref (xp.queue ,xp) (+ ,index 3)))
(define-local-syntax (xp.qend xp index)
`(vector-ref (xp.queue ,xp) (+ ,index 4)))
(define-local-syntax (xp.qoffset xp index)
`(vector-ref (xp.queue ,xp) (+ ,index 5)))
(define-local-syntax (xp.qarg xp index)
`(vector-ref (xp.queue ,xp) (+ ,index 6)))
;;; we shift the queue over rather than using a circular queue because
;;; that works out to be a lot faster in practice. Note, short printout
;;; does not ever cause a shift, and even in long printout, the queue is
;;; shifted left for free every time it happens to empty out.
(define (xp.enqueue xp type kind . maybe-arg)
(incf (xp.qright xp) xp.queue-entry-size)
(when (> (xp.qright xp) (- xp.queue-min-size xp.queue-entry-size))
(vector-replace (xp.queue xp) (xp.queue xp) 0 (xp.qleft xp) (xp.qright xp))
(setf (xp.qright xp) (- (xp.qright xp) (xp.qleft xp)))
(setf (xp.qleft xp) 0))
(xp.check-queue-size xp (xp.qright xp))
(setf (xp.qtype xp (xp.qright xp)) type)
(setf (xp.qkind xp (xp.qright xp)) kind)
(setf (xp.qpos xp (xp.qright xp)) (xp.tp<-bp xp))
(setf (xp.qdepth xp (xp.qright xp)) (xp.depth-in-blocks xp))
(setf (xp.qend xp (xp.qright xp)) '#f)
(setf (xp.qoffset xp (xp.qright xp)) '#f)
(setf (xp.qarg xp (xp.qright xp)) (car maybe-arg)))
(define-local-syntax (xp.qnext index) `(+ ,index xp.queue-entry-size))
;;; Print routine for xp structures
;;; *** this is broken, it uses unimplemented format options.
(define *xp.describe-xp-streams-fully* '#f)
(define (xp.describe-xp xp . maybe-stream)
(let ((s (if (not (null? maybe-stream))
(car maybe-stream)
(current-output-port))))
(format s "#<XP stream ")
(if (not (xp.base-stream xp))
(format s "not currently in use")
(begin
(format s "outputting to ~S" (xp.base-stream xp))
(format s "~&buffer= ~S"
(substring (xp.buffer xp) 0 (max (xp.buffer-ptr xp) 0)))
(if (not (dynamic *xp.describe-xp-streams-fully*))
(format s " ...")
(begin
(format s "~& pos _123456789_123456789_123456789_123456789")
(format s "~&depth-in-blocks= ~D linel= ~D line-no= ~D line-limit= ~D"
(xp.depth-in-blocks xp) (xp.linel xp)
(xp.line-no xp) (xp.line-limit xp))
(when (or (xp.char-mode xp) (not (zero? (xp.char-mode-counter xp))))
(format s "~&char-mode= ~S char-mode-counter= ~D"
(xp.char-mode xp) (xp.char-mode-counter xp)))
(unless (negative? (xp.block-stack-ptr xp))
(format s "~§ion-start")
(do ((save (xp.block-stack-ptr xp)))
((negative? (xp.block-stack-ptr xp))
(setf (xp.block-stack-ptr xp) save))
(format s " ~D" (xp.section-start xp))
(xp.pop-block-stack xp)))
(format s "~&linel= ~D charpos= ~D buffer-ptr= ~D buffer-offset= ~D"
(xp.linel xp) (xp.charpos xp)
(xp.buffer-ptr xp) (xp.buffer-offset xp))
(unless (negative? (xp.prefix-stack-ptr xp))
(format s "~&prefix= ~S"
(substring (xp.prefix xp) 0 (max (xp.prefix-ptr xp) 0)))
(format s "~&suffix= ~S"
(substring (xp.suffix xp) 0 (max (xp.suffix-ptr xp) 0))))
(unless (> (xp.qleft xp) (xp.qright xp))
(format s "~&ptr type kind pos depth end offset arg")
(do ((p (xp.qleft xp) (xp.qnext p)))
((> p (xp.qright xp)))
(format s "~&~4A~13A~15A~4A~6A~4A~7A~A"
(/ (- p (xp.qleft xp)) xp.queue-entry-size)
(xp.qtype xp p)
(if (memq (xp.qtype xp p) '(newline ind))
(xp.qkind xp p)
"")
(xp.bp<-tp xp (xp.qpos xp p))
(xp.qdepth xp p)
(if (not (memq (xp.qtype xp p)
'(newline start-block)))
""
(and (xp.qend xp p)
(/ (- (+ p (xp.qend xp p)) (xp.qleft xp))
xp.queue-entry-size)))
(if (not (eq? (xp.qtype xp p) 'start-block))
""
(and (xp.qoffset xp p)
(/ (- (+ p (xp.qoffset xp p)) (xp.qleft xp))
xp.queue-entry-size)))
(if (not (memq (xp.qtype xp p)
'(ind start-block end-block)))
""
(xp.qarg xp p)))))
(unless (negative? (xp.prefix-stack-ptr xp))
(format s "~&initial-prefix-ptr prefix-ptr suffix-ptr non-blank start-line")
(do ((save (xp.prefix-stack-ptr xp)))
((negative? (xp.prefix-stack-ptr xp))
(setf (xp.prefix-stack-ptr xp) save))
(format s "~& ~19A~11A~11A~10A~A"
(initial-prefix-ptr xp)
(xp.prefix-ptr xp)
(xp.suffix-ptr xp)
(non-blank-prefix-ptr xp)
(xp.section-start-line xp))
(xp.pop-prefix-stack xp)))))))
(format s ">")))
;;; Allocation of XP structures
;;; This maintains a list of XP structures. We save them
;;; so that we don't have to create new ones all of the time.
;;; We have separate objects so that many can be in use at once
;;; (e.g. for printing to multiple streams).
(define xp.free-xps '())
(define (xp.get-pretty-print-stream stream)
(xp.initialize-xp
(if (not (null? xp.free-xps))
(pop xp.free-xps)
(xp.make-xp-structure))
stream))
;;; If you call this, the xp-stream gets efficiently recycled.
(define (xp.free-pretty-print-stream xp)
(setf (xp.base-stream xp) '#f)
(if (not (memq xp xp.free-xps))
(push xp xp.free-xps)))
;;; This is called to initialize things when you start pretty printing.
(define (xp.initialize-xp xp stream)
(setf (xp.base-stream xp) stream)
(setf (xp.linel xp)
(max 0
(cond ((dynamic *print-right-margin*))
((internal-output-width stream))
(else (dynamic *default-right-margin*)))))
(setf (xp.line-limit xp) (dynamic *print-lines*))
(setf (xp.line-no xp) 1)
(setf (xp.char-mode xp) '#f)
(setf (xp.char-mode-counter xp) 0)
(setf (xp.depth-in-blocks xp) 0)
(setf (xp.block-stack-ptr xp) 0)
(setf (xp.charpos xp) (or (internal-output-position stream) 0))
(setf (xp.section-start xp) 0)
(setf (xp.buffer-ptr xp) 0)
(setf (xp.buffer-offset xp) (xp.charpos xp))
(setf (xp.qleft xp) 0)
(setf (xp.qright xp) (- xp.queue-entry-size))
(setf (xp.prefix-stack-ptr xp) (- xp.prefix-stack-entry-size))
xp)
;;; The char-mode stuff is a bit tricky.
;;; one can be in one of the following modes:
;;; NIL no changes to characters output.
;;; :UP CHAR-UPCASE used.
;;; :DOWN CHAR-DOWNCASE used.
;;; :CAP0 capitalize next alphanumeric letter then switch to :DOWN.
;;; :CAP1 capitalize next alphanumeric letter then switch to :CAPW
;;; :CAPW downcase letters. When a word break letter found, switch to :CAP1.
;;; It is possible for ~(~) to be nested in a format string, but note that
;;; each mode specifies what should happen to every letter. Therefore, inner
;;; nested modes never have any effect. You can just ignore them.
(define (xp.push-char-mode xp new-mode)
(if (zero? (xp.char-mode-counter xp))
(setf (xp.char-mode xp) new-mode))
(incf (xp.char-mode-counter xp)))
(define (xp.pop-char-mode xp)
(decf (xp.char-mode-counter xp))
(if (zero? (xp.char-mode-counter xp))
(setf (xp.char-mode xp) '#f)))
;;; Assumes is only called when char-mode is non-nil
(define (xp.handle-char-mode xp char)
(case (xp.char-mode xp)
((CAP0)
(cond ((not (or (char-alphabetic? char) (char-numeric? char))) char)
(else (setf (xp.char-mode xp) 'DOWN) (char-upcase char))))
((CAP1)
(cond ((not (or (char-alphabetic? char) (char-numeric? char))) char)
(else (setf (xp.char-mode xp) 'CAPW) (char-upcase char))))
((CAPW)
(cond ((or (char-alphabetic? char) (char-numeric? char))
(char-downcase char))
(else (setf (xp.char-mode xp) 'CAP1) char)))
((UP)
(char-upcase char))
(else
(char-downcase char)))) ;DOWN
;;; All characters output are passed through the handler above. However,
;;; it must be noted that on-each-line prefixes are only processed in the
;;; context of the first place they appear. They stay the same later no
;;; matter what. Also non-literal newlines do not count as word breaks.
;;; This handles the basic outputting of characters. note + suffix means that
;;; the stream is known to be an XP stream, all inputs are mandatory, and no
;;; error checking has to be done. Suffix ++ additionally means that the
;;; output is guaranteed not to contain a newline char.
(define (xp.write-char+ char xp)
(if (eqv? char #\newline)
(xp.pprint-newline+ 'unconditional xp)
(xp.write-char++ char xp)))
(define (xp.write-string+ mystring xp start end)
(let ((next-newline (string-position #\newline mystring start end)))
(if next-newline
(begin
(xp.write-string++ mystring xp start next-newline)
(xp.pprint-newline+ 'unconditional xp)
(xp.write-string+ mystring xp (1+ next-newline) end))
(xp.write-string++ mystring xp start end))))
;;; note this checks (> BUFFER-PTR LINEL) instead of (> (xp.lp<-bp) LINEL)
;;; this is important so that when things are longer than a line they
;;; end up getting printed in chunks of size LINEL.
(define (xp.write-char++ char xp)
(when (> (xp.buffer-ptr xp) (xp.linel xp))
(xp.force-some-output xp))
(let ((new-buffer-end (1+ (xp.buffer-ptr xp))))
(xp.check-buffer-size xp new-buffer-end)
(if (xp.char-mode xp) (setf char (xp.handle-char-mode xp char)))
(setf (string-ref (xp.buffer xp) (xp.buffer-ptr xp)) char)
(setf (xp.buffer-ptr xp) new-buffer-end)))
(define (xp.force-some-output xp)
(xp.attempt-to-output xp '#f '#f)
(when (> (xp.buffer-ptr xp) (xp.linel xp)) ;only if printing off end of line
(xp.attempt-to-output xp '#t '#t)))
(define (xp.write-string++ mystring xp start end)
(when (> (xp.buffer-ptr xp) (xp.linel xp))
(xp.force-some-output xp))
(xp.write-string+++ mystring xp start end))
;;; never forces output; therefore safe to call from within xp.output-line.
(define (xp.write-string+++ mystring xp start end)
(let ((new-buffer-end (+ (xp.buffer-ptr xp) (- end start))))
(xp.check-buffer-size xp new-buffer-end)
(do ((buffer (xp.buffer xp))
(i (xp.buffer-ptr xp) (1+ i))
(j start (1+ j)))
((= j end))
(let ((char (string-ref mystring j)))
(if (xp.char-mode xp) (setf char (xp.handle-char-mode xp char)))
(setf (string-ref buffer i) char)))
(setf (xp.buffer-ptr xp) new-buffer-end)))
(define (xp.pprint-tab+ kind colnum colinc xp)
(let ((indented? '#f)
(relative? '#f))
(case kind
((section) (setf indented? '#t))
((line-relative) (setf relative? '#t))
((section-relative) (setf indented? '#t) (setf relative? '#t)))
(let* ((current
(if (not indented?)
(xp.lp<-bp xp)
(- (xp.tp<-bp xp) (xp.section-start xp))))
(new
(if (zero? colinc)
(if relative? (+ current colnum) (max colnum current))
(cond (relative?
(* colinc
(quotient (+ current colnum colinc -1) colinc)))
((> colnum current) colnum)
(else
(+ colnum
(* colinc
(quotient (+ current (- colnum) colinc)
colinc)))))))
(end (- new current)))
(when (positive? end)
(if (xp.char-mode xp) (xp.handle-char-mode xp #\space))
(let ((end (+ (xp.buffer-ptr xp) end)))
(xp.check-buffer-size xp end)
(string-fill (xp.buffer xp) #\space (xp.buffer-ptr xp) end)
(setf (xp.buffer-ptr xp) end))))))
;;; note following is smallest number >= x that is a multiple of colinc
;;; (* colinc (quotient (+ x (1- colinc)) colinc))
(define (xp.pprint-newline+ kind xp)
(xp.enqueue xp 'newline kind)
(do ((ptr (xp.qleft xp) (xp.qnext ptr))) ;find sections we are ending
((not (< ptr (xp.qright xp)))) ;all but last
(when (and (not (xp.qend xp ptr))
(not (> (xp.depth-in-blocks xp) (xp.qdepth xp ptr)))
(memq (xp.qtype xp ptr) '(newline start-block)))
(setf (xp.qend xp ptr) (- (xp.qright xp) ptr))))
(setf (xp.section-start xp) (xp.tp<-bp xp))
(when (and (memq kind '(fresh unconditional)) (xp.char-mode xp))
(xp.handle-char-mode xp #\newline))
(when (memq kind '(fresh unconditional mandatory))
(xp.attempt-to-output xp '#t '#f)))
(define (xp.start-block xp prefix-string on-each-line? suffix-string)
(xp.write-prefix-suffix prefix-string xp)
(if (and (xp.char-mode xp) on-each-line?)
(setf prefix-string
(substring (xp.buffer xp)
(- (xp.buffer-ptr xp) (string-length prefix-string))
(xp.buffer-ptr xp))))
(xp.push-block-stack xp)
(xp.enqueue xp 'start-block '#f
(if on-each-line? (cons suffix-string prefix-string) suffix-string))
(incf (xp.depth-in-blocks xp)) ;must be after enqueue
(setf (xp.section-start xp) (xp.tp<-bp xp)))
(define (xp.end-block xp suffix)
(unless (and (dynamic *xp.abbreviation-happened*)
(eqv? (dynamic *xp.abbreviation-happened*)
(dynamic *print-lines*)))
(xp.write-prefix-suffix suffix xp)
(decf (xp.depth-in-blocks xp))
(xp.enqueue xp 'end-block '#f suffix)
(block foundit
(do ((ptr (xp.qleft xp) (xp.qnext ptr))) ;look for start of block we are ending
((not (< ptr (xp.qright xp)))) ;all but last
(when (and (= (xp.depth-in-blocks xp) (xp.qdepth xp ptr))
(eq? (xp.qtype xp ptr) 'start-block)
(not (xp.qoffset xp ptr)))
(setf (xp.qoffset xp ptr) (- (xp.qright xp) ptr))
(return-from foundit '#f))) ;can only be 1
)
(xp.pop-block-stack xp)))
(define (xp.write-prefix-suffix mystring xp)
(when mystring
(xp.write-string++ mystring xp 0 (string-length mystring))))
(define (xp.pprint-indent+ kind n xp)
(xp.enqueue xp 'ind kind n))
;;; attempt-to-output scans the queue looking for things it can do.
;;; it keeps outputting things until the queue is empty, or it finds
;;; a place where it cannot make a decision yet.
;;; If flush-out? is T and force-newlines? is NIL then the buffer,
;;; prefix-stack, and queue will be in an inconsistent state after the call.
;;; You better not call it this way except as the last act of outputting.
(define-local-syntax (xp.maybe-too-large xp Qentry)
`(let ((limit (xp.linel ,xp)))
(when (eqv? (xp.line-limit ,xp) (xp.line-no ,xp)) ;prevents suffix overflow
(decf limit 2) ;3 for " .." minus 1 for space (heuristic)
(when (not (negative? (xp.prefix-stack-ptr ,xp)))
(decf limit (xp.suffix-ptr ,xp))))
(cond ((xp.qend ,xp ,Qentry)
(> (xp.lp<-tp ,xp (xp.qpos ,xp (+ ,Qentry (xp.qend ,xp ,Qentry)))) limit))
((or force-newlines? (> (xp.lp<-bp ,xp) limit))
'#t)
(else ;wait until later to decide.
(return-from attempt-to-output '#f)))))
(define-local-syntax (xp.misering? xp)
`(and (dynamic *print-miser-width*)
(<= (- (xp.linel ,xp) (initial-prefix-ptr ,xp))
(dynamic *print-miser-width*))))
(define (xp.attempt-to-output xp force-newlines? flush-out?)
(block attempt-to-output
(do ()
((> (xp.qleft xp) (xp.qright xp))
(setf (xp.qleft xp) 0)
(setf (xp.qright xp) (- xp.queue-entry-size))) ;saves shifting
(case (xp.qtype xp (xp.qleft xp))
((ind)
(unless (xp.misering? xp)
(xp.set-indentation-prefix
xp
(case (xp.qkind xp (xp.qleft xp))
((block)
(+ (initial-prefix-ptr xp) (xp.qarg xp (xp.qleft xp))))
(else ; current
(+ (xp.lp<-tp xp (xp.qpos xp (xp.qleft xp)))
(xp.qarg xp (xp.qleft xp)))))))
(setf (xp.qleft xp) (xp.qnext (xp.qleft xp))))
((start-block)
(cond ((xp.maybe-too-large xp (xp.qleft xp))
(xp.push-prefix-stack xp)
(setf (initial-prefix-ptr xp) (xp.prefix-ptr xp))
(xp.set-indentation-prefix
xp (xp.lp<-tp xp (xp.qpos xp (xp.qleft xp))))
(let ((arg (xp.qarg xp (xp.qleft xp))))
(when (pair? arg) (xp.set-prefix xp (cdr arg)))
(setf (initial-prefix-ptr xp) (xp.prefix-ptr xp))
(cond ((not (list? arg)) (xp.set-suffix xp arg))
((car arg) (xp.set-suffix xp (car arg)))))
(setf (xp.section-start-line xp) (xp.line-no xp)))
(else (incf (xp.qleft xp) (xp.qoffset xp (xp.qleft xp)))))
(setf (xp.qleft xp) (xp.qnext (xp.qleft xp))))
((end-block)
(xp.pop-prefix-stack xp)
(setf (xp.qleft xp) (xp.qnext (xp.qleft xp))))
(else ; newline
(when (case (xp.qkind xp (xp.qleft xp))
((fresh) (not (zero? (xp.lp<-bp xp))))
((miser) (xp.misering? xp))
((fill) (or (xp.misering? xp)
(> (xp.line-no xp) (xp.section-start-line xp))
(xp.maybe-too-large xp (xp.qleft xp))))
(else '#t)) ;(linear unconditional mandatory)
(xp.output-line xp (xp.qleft xp))
(xp.setup-for-next-line xp (xp.qleft xp)))
(setf (xp.qleft xp) (xp.qnext (xp.qleft xp)))))))
(when flush-out? (xp.flush xp)))
;;; this can only be called last!
(define (xp.flush xp)
(unless (dynamic *xp.locating-circularities*)
(internal-write-string (xp.buffer xp) (xp.base-stream xp) 0 (xp.buffer-ptr xp)))
(incf (xp.buffer-offset xp) (xp.buffer-ptr xp))
(incf (xp.charpos xp) (xp.buffer-ptr xp))
(setf (xp.buffer-ptr xp) 0))
;;; This prints out a line of stuff.
(define (xp.output-line xp Qentry)
(let* ((out-point (xp.bp<-tp xp (xp.qpos xp Qentry)))
(last-non-blank (string-position-not-from-end
#\space (xp.buffer xp) 0 out-point))
(end (cond ((memq (xp.qkind xp Qentry) '(fresh unconditional))
out-point)
(last-non-blank (1+ last-non-blank))
(else 0)))
(line-limit-exit (and (xp.line-limit xp)
(not (> (xp.line-limit xp) (xp.line-no xp))))))
(when line-limit-exit
(setf (xp.buffer-ptr xp) end) ;truncate pending output.
(xp.write-string+++ " .." xp 0 3)
(string-nreverse (xp.suffix xp) 0 (xp.suffix-ptr xp))
(xp.write-string+++ (xp.suffix xp) xp 0 (xp.suffix-ptr xp))
(setf (xp.qleft xp) (xp.qnext (xp.qright xp)))
(setf (dynamic *xp.abbreviation-happened*) (dynamic *print-lines*))
(funcall (dynamic *xp.line-limit-abbreviation-exit*) '#t))
(incf (xp.line-no xp))
(unless (dynamic *xp.locating-circularities*)
(internal-write-string (xp.buffer xp) (xp.base-stream xp) 0 end)
(newline (xp.base-stream xp)))))
(define (xp.setup-for-next-line xp Qentry)
(let* ((out-point (xp.bp<-tp xp (xp.qpos xp Qentry)))
(prefix-end
(cond ((memq (xp.qkind xp Qentry) '(unconditional fresh))
(non-blank-prefix-ptr xp))
(else (xp.prefix-ptr xp))))
(change (- prefix-end out-point)))
(setf (xp.charpos xp) 0)
(when (positive? change) ;almost never happens
(xp.check-buffer-size xp (+ (xp.buffer-ptr xp) change)))
(string-replace (xp.buffer xp) (xp.buffer xp)
prefix-end out-point (xp.buffer-ptr xp))
(string-replace (xp.buffer xp) (xp.prefix xp) 0 0 prefix-end)
(incf (xp.buffer-ptr xp) change)
(decf (xp.buffer-offset xp) change)
(when (not (memq (xp.qkind xp Qentry) '(unconditional fresh)))
(setf (xp.section-start-line xp) (xp.line-no xp)))))
(define (xp.set-indentation-prefix xp new-position)
(let ((new-ind (max (non-blank-prefix-ptr xp) new-position)))
(setf (xp.prefix-ptr xp) (initial-prefix-ptr xp))
(xp.check-prefix-size xp new-ind)
(when (> new-ind (xp.prefix-ptr xp))
(string-fill (xp.prefix xp) #\space (xp.prefix-ptr xp) new-ind))
(setf (xp.prefix-ptr xp) new-ind)))
(define (xp.set-prefix xp prefix-string)
(let ((end (string-length prefix-string)))
(string-replace (xp.prefix xp) prefix-string
(- (xp.prefix-ptr xp) end) 0 end))
(setf (non-blank-prefix-ptr xp) (xp.prefix-ptr xp)))
(define (xp.set-suffix xp suffix-string)
(let* ((end (string-length suffix-string))
(new-end (+ (xp.suffix-ptr xp) end)))
(xp.check-suffix-size xp new-end)
(do ((i (1- new-end) (1- i))
(j 0 (1+ j)))
((= j end))
(setf (string-ref (xp.suffix xp) i) (string-ref suffix-string j)))
(setf (xp.suffix-ptr xp) new-end)))
;;;=====================================================================
;;; Basic interface functions
;;;=====================================================================
;;; The internal functions in this file
;;; use the '+' forms of these functions directly (which is faster) because,
;;; they do not need error checking of fancy stream coercion. The '++' forms
;;; additionally assume the thing being output does not contain a newline.
(define (write object . maybe-stream)
(let ((stream (if (not (null? maybe-stream))
(car maybe-stream)
(current-output-port))))
(cond ((xp.xp-structure-p stream)
(xp.write+ object stream))
((xp.get-printer object)
(xp.initiate-xp-printing
(lambda (s o) (xp.write+ o s))
stream
object))
(else
(internal-write object stream)))))
(define (xp.maybe-initiate-xp-printing fn stream . args)
(if (xp.xp-structure-p stream)
(apply fn stream args)
(apply (function xp.initiate-xp-printing) fn stream args)))
(define (xp.initiate-xp-printing fn stream . args)
(dynamic-let ((*xp.abbreviation-happened*
'#f)
(*xp.locating-circularities*
(if (dynamic *print-circle*)
0
'#f))
(*xp.circularity-hash-table*
(if (dynamic *print-circle*)
(make-table)
'#f))
(*xp.parents*
(if (not (dynamic *print-shared*))
(list '#f)
'())) ;*** is this right?
(*xp.current-level*
0)
(*xp.current-length*
0))
(let ((result (xp.xp-print fn stream args)))
(when (dynamic *xp.abbreviation-happened*)
(setf args (list-copy args))
(setf (dynamic *last-abbreviated-printing*)
(lambda maybe-stream
(let ((stream (if (not (null? maybe-stream))
(car maybe-stream)
stream)))
(apply (function xp.maybe-initiate-xp-printing)
fn stream args)))))
result)))
(define (xp.xp-print fn stream args)
(let ((result (xp.do-xp-printing fn stream args)))
(when (dynamic *xp.locating-circularities*)
(setf (dynamic *xp.locating-circularities*) '#f)
(setf (dynamic *xp.abbreviation-happened*) '#f)
(setf (dynamic *xp.parents*) '())
(setf result (xp.do-xp-printing fn stream args)))
result))
(define (xp.do-xp-printing fn stream args)
(let ((xp (xp.get-pretty-print-stream stream))
(result '#f))
(dynamic-let ((*xp.current-level* 0))
(let/cc catch
(dynamic-let ((*xp.line-limit-abbreviation-exit* catch))
(xp.start-block xp '#f '#f '#f)
(setf result (apply fn xp args))
(xp.end-block xp '#f)))
(when (and (dynamic *xp.locating-circularities*)
(zero? (dynamic *xp.locating-circularities*)) ;No circularities.
(= (xp.line-no xp) 1) ;Didn't suppress line.
(zero? (xp.buffer-offset xp))) ;Didn't suppress partial line.
(setf (dynamic *xp.locating-circularities*) '#f)) ;print what you have got.
(when (let/cc catch
(dynamic-let ((*xp.line-limit-abbreviation-exit* catch))
(xp.attempt-to-output xp '#f '#t)
'#f))
(xp.attempt-to-output xp '#t '#t))
(xp.free-pretty-print-stream xp)
result)))
(define (xp.write+ object xp)
(dynamic-let ((*xp.parents* (dynamic *xp.parents*)))
(unless (and (dynamic *xp.circularity-hash-table*)
(eq? (xp.circularity-process xp object '#f) 'subsequent))
(when (and (dynamic *xp.circularity-hash-table*) (pair? object))
;; Must do this to avoid additional circularity detection by
;; pprint-logical-block; otherwise you get stuff like #1=#1#.
(setf object (cons (car object) (cdr object))))
(funcall (or (xp.get-printer object) (function xp.print-default))
object
xp))
object))
(define (xp.print-default object xp)
(let ((stuff (internal-write-to-string object)))
(xp.write-string+ stuff xp 0 (string-length stuff))))
;;; It is vital that this function be called EXACTLY once for each occurrence
;;; of each thing in something being printed.
;;; Returns nil if printing should just continue on.
;;; Either it is not a duplicate, or we are in the first pass and do not
;;; know.
;;; returns :FIRST if object is first occurrence of a DUPLICATE.
;;; (This can only be returned on a second pass.)
;;; After an initial code (printed by this routine on the second pass)
;;; printing should continue on for the object.
;;; returns :SUBSEQUENT if second or later occurrence.
;;; Printing is all taken care of by this routine.
;;; Note many (maybe most) lisp implementations have characters and small
;;; numbers represented in a single word so that the are always eq when
;;; they are equal and the reader takes care of properly sharing them
;;; (just as it does with symbols). Therefore, we do not want circularity
;;; processing applied to them. However, some kinds of numbers
;;; (e.g., bignums) undoubtedly are complex structures that the reader
;;; does not share. However, they cannot have circular pointers in them
;;; and it is therefore probably a waste to do circularity checking on them.
;;; In any case, it is not clear that it easy to tell exactly what kinds of
;;; numbers a given implementation is going to have the reader
;;; automatically share.
(define (xp.circularity-process xp object interior-cdr?)
(unless (or (number? object)
(char? object)
(and (symbol? object) (not (gensym? object))))
(let ((id (table-entry (dynamic *xp.circularity-hash-table*) object)))
(if (dynamic *xp.locating-circularities*)
;; This is the first pass.
(cond ((not id) ;never seen before
(when (not (null? (dynamic *xp.parents*)))
(push object (dynamic *xp.parents*)))
(setf (table-entry (dynamic *xp.circularity-hash-table*) object)
0)
'#f)
((zero? id) ;possible second occurrence
(cond ((or (null? (dynamic *xp.parents*))
(memq object (dynamic *xp.parents*)))
(setf (table-entry
(dynamic *xp.circularity-hash-table*) object)
(incf (dynamic *xp.locating-circularities*)))
'subsequent)
(else '#f)))
(else 'subsequent));third or later occurrence
;; This is the second pass.
(cond ((or (not id) ;never seen before (note ~@* etc. conses)
(zero? id));no duplicates
'#f)
((positive? id) ; first occurrence
(cond (interior-cdr?
(decf (dynamic *xp.current-level*))
(xp.write-string++ ". #" xp 0 3))
(else (xp.write-char++ #\# xp)))
(xp.print-integer id xp)
(xp.write-char++ #\= xp)
(setf (table-entry (dynamic *xp.circularity-hash-table*) object)
(- id))
'first)
(else
(if interior-cdr?
(xp.write-string++ ". #" xp 0 3)
(xp.write-char++ #\# xp))
(xp.print-integer(- id) xp)
(xp.write-char++ #\# xp)
'subsequent))))))
;;; Here are all the standard Common Lisp printing functions.
(define (print object . maybe-stream)
(let ((stream (if (not (null? maybe-stream))
(car maybe-stream)
(current-output-port))))
(dynamic-let ((*print-escape* '#t))
(terpri stream)
(write object stream)
(write-char #\space stream)
object)))
(define (prin1 object . maybe-stream)
(let ((stream (if (not (null? maybe-stream))
(car maybe-stream)
(current-output-port))))
(dynamic-let ((*print-escape* '#t))
(write object stream)
object)))
(define (princ object . maybe-stream)
(let ((stream (if (not (null? maybe-stream))
(car maybe-stream)
(current-output-port))))
(dynamic-let ((*print-escape* '#f))
(write object stream)
object)))
(define (display object . maybe-stream)
(apply (function princ) object maybe-stream))
(define (pprint object . maybe-stream)
(let ((stream (if (not (null? maybe-stream))
(car maybe-stream)
(current-output-port))))
(dynamic-let ((*print-escape* '#t)
(*print-pretty* '#t))
(terpri stream)
(write object stream)
(values))))
(define (prin1-to-string object)
(call-with-output-string
(lambda (stream)
(dynamic-let ((*print-escape* '#t))
(write object stream)))))
(define (princ-to-string object)
(call-with-output-string
(lambda (stream)
(dynamic-let ((*print-escape* '#f))
(write object stream)))))
(define (write-char char . maybe-stream)
(let ((stream (if (not (null? maybe-stream))
(car maybe-stream)
(current-output-port))))
(if (xp.xp-structure-p stream)
(xp.write-char+ char stream)
(internal-write-char char stream))
char))
(define (write-string mystring . maybe-stream-start-end)
(let* ((stream (if (not (null? maybe-stream-start-end))
(car maybe-stream-start-end)
(current-output-port)))
(start (if (not (null? (cdr maybe-stream-start-end)))
(cadr maybe-stream-start-end)
0))
(end (if (not (null? (cddr maybe-stream-start-end)))
(caddr maybe-stream-start-end)
(string-length mystring))))
(if (xp.xp-structure-p stream)
(xp.write-string+ mystring stream start end)
(internal-write-string mystring stream start end))
mystring))
(define (write-line mystring . maybe-stream-start-end)
(let* ((stream (if (not (null? maybe-stream-start-end))
(car maybe-stream-start-end)
(current-output-port)))
(start (if (not (null? (cdr maybe-stream-start-end)))
(cadr maybe-stream-start-end)
0))
(end (if (not (null? (cddr maybe-stream-start-end)))
(caddr maybe-stream-start-end)
(string-length mystring))))
(if (xp.xp-structure-p stream)
(begin
(xp.write-string+ mystring stream start end)
(xp.pprint-newline+ 'unconditional stream))
(begin
(internal-write-string mystring stream start end)
(internal-newline stream)))
mystring))
(define (terpri . maybe-stream)
(let ((stream (if (not (null? maybe-stream))
(car maybe-stream)
(current-output-port))))
(if (xp.xp-structure-p stream)
(xp.pprint-newline+ 'unconditional stream)
(internal-newline stream))
'#f))
(define (newline . maybe-stream)
(apply (function terpri) maybe-stream))
;;; This has to violate the XP data abstraction and fool with internal
;;; stuff, in order to find out the right info to return as the result.
(define (fresh-line . maybe-stream)
(let ((stream (if (not (null? maybe-stream))
(car maybe-stream)
(current-output-port))))
(cond ((xp.xp-structure-p stream)
(xp.attempt-to-output stream '#t '#t) ;ok because we want newline
(when (not (zero? (xp.lp<-bp stream)))
(xp.pprint-newline+ 'fresh stream)
'#t))
(else
(internal-fresh-line stream)))))
;;; Each of these causes the stream to be pessimistic and insert
;;; newlines wherever it might have to, when forcing the partial output
;;; out. This is so that things will be in a consistent state if
;;; output continues to the stream later.
(define (finish-output . maybe-stream)
(let ((stream (if (not (null? maybe-stream))
(car maybe-stream)
(current-output-port))))
(if (xp.xp-structure-p stream)
(xp.attempt-to-output stream '#t '#t)
(internal-finish-output stream))
'#f))
(define (force-output . maybe-stream)
(let ((stream (if (not (null? maybe-stream))
(car maybe-stream)
(current-output-port))))
(if (xp.xp-structure-p stream)
(xp.attempt-to-output stream '#t '#t)
(internal-force-output stream))
'#f))
(define (clear-output . maybe-stream)
(let ((stream (if (not (null? maybe-stream))
(car maybe-stream)
(current-output-port))))
(if (xp.xp-structure-p stream)
(dynamic-let ((*xp.locating-circularities* 0)) ;hack to prevent visible output
(xp.attempt-to-output stream '#t '#t)
(internal-clear-output stream)))
'#f))
;;;=====================================================================
;;; Functional interface to dynamic formatting
;;;=====================================================================
;;; The internal functions in this file, and the (formatter "...") expansions
;;; use the '+' forms of these functions directly (which is faster) because,
;;; they do not need error checking or fancy stream coercion. The '++' forms
;;; additionally assume the thing being output does not contain a newline.
(define-syntax (pprint-logical-block stream-symbol-stuff . body)
(let* ((stream-symbol (car stream-symbol-stuff))
(mylist (cadr stream-symbol-stuff))
(rest (cddr stream-symbol-stuff))
(prefix (if (not (null? rest)) (pop rest) ""))
(suffix (if (not (null? rest)) (pop rest) ""))
(per-line? (if (not (null? rest)) (pop rest) '#f)))
`(xp.maybe-initiate-xp-printing
(lambda (,stream-symbol)
(let ((+l ,mylist)
(+p ,prefix)
(+s ,suffix)
(+x ,stream-symbol))
(xp.pprint-logical-block+ (+x +l +p +s ,per-line? '#t '#f)
,@body
'#f)))
,stream-symbol)))
;;; Assumes var and args must be variables. Other arguments must be literals
;;; or variables.
(define-syntax (xp.pprint-logical-block+ stuff . body)
(let* ((var (pop stuff))
(args (pop stuff))
(prefix (pop stuff))
(suffix (pop stuff))
(per-line? (pop stuff)))
`(unless (xp.check-abbreviation ,var ,args)
(dynamic-let ((*xp.current-level* (1+ (dynamic *xp.current-level*)))
(*xp.current-length* -1)
(*xp.parents* (dynamic *xp.parents*)))
(block logical-block
(if (dynamic *print-pretty*)
(xp.start-block ,var ,prefix ,per-line? ,suffix)
(xp.write-prefix-suffix ,prefix ,var))
(unwind-protect
(begin ,@body)
(if (dynamic *print-pretty*)
(xp.end-block ,var ,suffix)
(xp.write-prefix-suffix ,suffix ,var))))))
))
(define (xp.check-abbreviation xp object)
(cond ((and (dynamic *print-level*)
(>= (dynamic *xp.current-level*)
(dynamic *print-level*)))
(xp.write-char++ #\# XP)
(setf (dynamic *xp.abbreviation-happened*) '#t)
'#t)
((and (dynamic *xp.circularity-hash-table*)
(eq? (xp.circularity-process xp object '#f) 'subsequent))
'#t)
(else '#f)))
(define-syntax (pprint-pop)
`(xp.pprint-pop+ +l +x))
(define-syntax (xp.pprint-pop+ args xp)
`(if (xp.pprint-pop-check+ ,args ,xp)
(return-from logical-block '#f)
(if (null? ,args) '() (pop ,args))))
(define (xp.pprint-pop-check+ args xp)
(incf (dynamic *xp.current-length*))
(cond ((not (or (pair? args) (null? args)))
;; must be first to supersede length abbreviation
(xp.write-string++ ". " xp 0 2)
(xp.write+ args xp)
'#t)
((and (dynamic *print-length*)
(not (< *xp.current-length* (dynamic *print-length*))))
;; must supersede circularity check
(xp.write-string++ "..." xp 0 3)
(setf (dynamic *xp.abbreviation-happened*) '#t)
'#t)
((and (dynamic *xp.circularity-hash-table*)
(not (zero? *xp.current-length*)))
(case (xp.circularity-process xp args '#t)
((first)
(xp.write+ (cons (car args) (cdr args)) xp) '#t)
((subsequent)
'#t)
(else
'#f)))
(else
'#f)))
(define-syntax (pprint-exit-if-list-exhausted)
`(xp.pprint-exit-if-list-exhausted+ +l))
(define-syntax (xp.pprint-exit-if-list-exhausted+ mylist)
`(if (null? ,mylist) (return-from logical-block '#f)))
(define (pprint-newline kind . maybe-stream)
(let ((stream (if (not (null? maybe-stream))
(car maybe-stream)
(current-output-port))))
(when (not (memq kind '(linear miser fill mandatory)))
(error "Invalid KIND argument ~A to PPRINT-NEWLINE" kind))
(when (and (xp.xp-structure-p stream) (dynamic *print-pretty*))
(xp.pprint-newline+ kind stream))
'#f))
(define (pprint-indent relative-to n . maybe-stream)
(let ((stream (if (not (null? maybe-stream))
(car maybe-stream)
(current-output-port))))
(when (not (memq relative-to '(block current)))
(error "Invalid KIND argument ~A to PPRINT-INDENT" relative-to))
(when (and (xp.xp-structure-p stream) (dynamic *print-pretty*))
(xp.pprint-indent+ relative-to n stream))
'#f))
(define (pprint-tab kind colnum colinc . maybe-stream)
(let ((stream (if (not (null? maybe-stream))
(car maybe-stream)
(current-output-port))))
(when (not (memq kind '(line section line-relative section-relative)))
(error "Invalid KIND argument ~A to PPRINT-TAB" kind))
(when (and (xp.xp-structure-p stream) (dynamic *print-pretty*))
(xp.pprint-tab+ kind colnum colinc stream))
'#f))
;;;=====================================================================
;;; Standard print dispatch function
;;;=====================================================================
(define (xp.print-null object xp)
(declare (ignore object))
(xp.write-string+ "()" xp 0 2))
(define (xp.print-true object xp)
(declare (ignore object))
(xp.write-string+ "#t" xp 0 2))
(define (xp.print-false object xp)
(declare (ignore object))
(xp.write-string+ "#f" xp 0 2))
(define (xp.print-symbol object xp)
(if (dynamic *print-escape*)
(xp.print-default object xp)
(let ((mystring (symbol->string object)))
(xp.write-string+ mystring xp 0 (string-length mystring)))))
(define (xp.print-number object xp)
(if (and (integer? object)
(eqv? (dynamic *print-base*) 10)
(not (dynamic *print-radix*)))
(begin
(when (negative? object)
(xp.write-char++ #\- xp)
(setf object (- object)))
(xp.print-integer object xp))
(xp.print-default object xp)))
(define (xp.print-integer n xp)
(let ((quot (quotient n 10))
(rem (remainder n 10)))
(unless (zero? quot)
(xp.print-integer quot xp))
(xp.write-char++ (string-ref "0123456789" rem) xp)))
(define (xp.print-string object xp)
(if (dynamic *print-escape*)
(begin
(xp.write-char++ #\" xp)
(do ((i 0 (1+ i))
(n (string-length object)))
((= i n))
(let ((c (string-ref object i)))
(if (or (char=? c #\") (char=? c #\\))
(xp.write-char++ #\\ xp))
(xp.write-char++ c xp)))
(xp.write-char++ #\" xp))
(xp.write-string+ object xp 0 (string-length object))))
(define (xp.print-character object xp)
(if (dynamic *print-escape*)
(let ((name (char-name object)))
(xp.write-char++ #\# xp)
(xp.write-char++ #\\ xp)
(if name
(xp.write-string++ name xp 0 (string-length name))
(xp.write-char++ object xp)))
(xp.write-char+ object xp)))
(define (xp.print-vector object xp)
(let* ((pretty? (dynamic *print-pretty*))
(end (vector-length object)))
(pprint-logical-block (xp '() "#(" ")")
(do ((i 0 (1+ i)))
((eqv? i end) '#f)
(when (not (eqv? i 0))
(xp.write-char++ #\space xp)
(if pretty?
(xp.pprint-newline+ 'fill xp)))
(pprint-pop)
(xp.write+ (vector-ref object i) xp)
))))
(define (xp.print-table object xp)
(let ((pretty? (dynamic *print-pretty*)))
(pprint-logical-block (xp '() "#<Table" ">")
(table-for-each
(lambda (key value)
(xp.write-char++ #\space xp)
(if pretty?
(xp.pprint-newline+ 'fill xp))
(pprint-pop)
(xp.write+ (cons key value) xp))
object))))
(define (xp.print-pair object xp)
(if (dynamic *print-pretty*)
(xp.pretty-print-list object xp)
(xp.pprint-logical-block+ (xp object "(" ")" '#f '#t '#f)
(do ()
((null? object) '#f)
(xp.write+ (xp.pprint-pop+ object xp) xp)
(when (not (null? object)) (xp.write-char++ #\space xp))))))
(define (xp.print-struct object xp)
(if (dynamic *print-structure*)
(print-structure-default object xp)
(funcall (get-structure-printer (struct-type-descriptor object))
object xp)))
(define (get-structure-printer td)
(or (td-printer td)
(if (eq? (td-name td) 'struct)
(function print-structure-default)
(get-structure-printer (td-parent-type td)))))
(define (print-structure-default object xp)
(let* ((td (struct-type-descriptor object))
(slots (td-slots td))
(pretty? (dynamic *print-pretty*)))
(pprint-logical-block (xp '() "#<Struct " ">")
(prin1 (td-name td) xp)
(when (dynamic *print-structure-slots*)
(dolist (s slots)
(write-char #\space xp)
(if pretty? (pprint-newline 'fill xp))
(pprint-pop)
(prin1 (sd-name s) xp)
(write-char #\space xp)
(write (funcall (sd-getter-function s) object) xp)))
)))
;;; This table can't be initialized until after all the functions
;;; have been defined.
(define *standard-print-dispatch-table*
(list (cons (function null?) (function xp.print-null))
(cons (lambda (x) (eq? x '#t)) (function xp.print-true))
(cons (function not) (function xp.print-false))
(cons (function symbol?) (function xp.print-symbol))
(cons (function number?) (function xp.print-number))
(cons (function pair?) (function xp.print-pair))
(cons (function string?) (function xp.print-string))
(cons (function char?) (function xp.print-character))
(cons (function struct?) (function xp.print-struct))
(cons (function vector?) (function xp.print-vector))
(cons (function table?) (function xp.print-table))))
(define (standard-print-dispatch object)
(standard-print-dispatch-aux
object (dynamic *standard-print-dispatch-table*)))
(define (standard-print-dispatch-aux object table)
(cond ((null? table) (function xp.print-default))
((funcall (car (car table)) object)
(cdr (car table)))
(else
(standard-print-dispatch-aux object (cdr table)))))
(setf (dynamic *print-dispatch*) (function standard-print-dispatch))
;;;=====================================================================
;;; Pretty printing formats for code
;;;=====================================================================
;;; The standard prettyprinters for lists dispatch off the CAR of the list.
(define *xp.pair-dispatch-table* (make-table))
(define (xp.pretty-print-list object xp)
(funcall (or (table-entry (dynamic *xp.pair-dispatch-table*) (car object))
(if (symbol? (car object)) (function xp.fn-call) '#f)
(lambda (object xp)
(pprint-fill xp object)))
object
xp))
;;; Must use pprint-logical-block (no +) in the following three, because they
;;; are exported functions.
;;; *** Note that the argument order on these is backwards; that's the
;;; *** way it is in Common Lisp....
(define (pprint-linear s object . moreargs)
(let* ((colon? (if (not (null? moreargs)) (pop moreargs) '#t))
(atsign? (if (not (null? moreargs)) (pop moreargs) '#f)))
(declare (ignore atsign?))
(pprint-logical-block (s object (if colon? "(" "") (if colon? ")" ""))
(pprint-exit-if-list-exhausted)
(do () ('#f)
(xp.write+ (pprint-pop) s)
(pprint-exit-if-list-exhausted)
(xp.write-char++ #\space s)
(xp.pprint-newline+ 'linear s)))))
(define (pprint-fill s object . moreargs)
(let* ((colon? (if (not (null? moreargs)) (pop moreargs) '#t))
(atsign? (if (not (null? moreargs)) (pop moreargs) '#f)))
(declare (ignore atsign?))
(pprint-logical-block (s object (if colon? "(" "") (if colon? ")" ""))
(pprint-exit-if-list-exhausted)
(do () ('#f)
(xp.write+ (pprint-pop) s)
(pprint-exit-if-list-exhausted)
(xp.write-char++ #\space s)
(xp.pprint-newline+ 'fill s)))))
(define (pprint-tabular s object . moreargs)
(let* ((colon? (if (not (null? moreargs)) (pop moreargs) '#t))
(atsign? (if (not (null? moreargs)) (pop moreargs) '#f))
(tabsize (or (and (not (null? moreargs)) (pop moreargs)) 16)))
(declare (ignore atsign?))
(pprint-logical-block (s object (if colon? "(" "") (if colon? ")" ""))
(pprint-exit-if-list-exhausted)
(do () ('#f)
(xp.write+ (pprint-pop) s)
(pprint-exit-if-list-exhausted)
(xp.write-char++ #\space s)
(xp.pprint-tab+ 'section-relative 0 tabsize s)
(xp.pprint-newline+ 'fill s)))))
(define (xp.fn-call object xp)
;; (formatter "~:<~W~^ ~:I~@_~@{~W~^ ~_~}~:>")
(xp.pprint-logical-block+ (xp object "(" ")" '#f '#t '#f)
(xp.write+ (xp.pprint-pop+ object xp) xp)
(xp.pprint-exit-if-list-exhausted+ object)
(xp.write-char++ #\space xp)
(xp.pprint-indent+ 'current 0 xp)
(xp.pprint-newline+ 'miser xp)
(xp.write+ (xp.pprint-pop+ object xp) xp)
(do ()
((null? object) '#f)
(xp.write-char++ #\space xp)
(xp.pprint-newline+ 'linear xp)
(xp.write+ (xp.pprint-pop+ object xp) xp))))
;;; Although idiosyncratic, I have found this very useful to avoid large
;;; indentations when printing out code.
(define (xp.alternative-fn-call object xp)
(if (> (string-length (symbol->string (car object))) 12)
;; (formatter "~:<~1I~@{~W~^ ~_~}~:>")
(xp.pprint-logical-block+ (xp object "(" ")" '#f '#t '#f)
(xp.pprint-indent+ 'block 1 xp)
(when (not (null? object))
(xp.write+ (xp.pprint-pop+ object xp) xp)
(do ()
((null? object) '#f)
(xp.write-char++ #\space xp)
(xp.pprint-newline+ 'linear xp)
(xp.write+ (xp.pprint-pop+ object xp) xp))))
(xp.fn-call object xp)))
(define (xp.bind-list object xp . args)
(declare (ignore args))
;; (formatter "~:<~@{~:/xp:pprint-fill/~^ ~_~}~:>")
(xp.pprint-logical-block+ (xp object "(" ")" '#f '#t '#f)
(when (not (null? object))
(pprint-fill xp (xp.pprint-pop+ object xp) '#t '#f)
(do ()
((null? object) '#f)
(xp.write-char++ #\space xp)
(xp.pprint-newline+ 'linear xp)
(pprint-fill xp (xp.pprint-pop+ object xp) '#t '#f)))))
(define (xp.fbind-list object xp . args)
(declare (ignore args))
;; (formatter "~:<~@{~:/xp:pprint-fill/~^ ~_~}~:>")
(xp.pprint-logical-block+ (xp object "(" ")" '#f '#t '#f)
(when (not (null? object))
(pprint-fill xp (xp.pprint-pop+ object xp) '#t '#f)
(do ()
((null? object) '#f)
(xp.write-char++ #\space xp)
(xp.pprint-newline+ 'linear xp)
(xp.block-like (xp.pprint-pop+ object xp) xp)))))
(define (xp.block-like object xp . args)
(declare (ignore args))
;; (formatter "~:<~1I~^~W~^ ~@_~W~^~@{ ~_~W~^~}~:>")
(xp.pprint-logical-block+ (xp object "(" ")" '#f '#t '#f)
(xp.pprint-indent+ 'block 1 xp)
(xp.pprint-exit-if-list-exhausted+ object)
(xp.write+ (xp.pprint-pop+ object xp) xp)
(xp.pprint-exit-if-list-exhausted+ object)
(xp.write-char++ #\space xp)
(xp.pprint-newline+ 'miser xp)
(xp.write+ (xp.pprint-pop+ object xp) xp)
(xp.pprint-exit-if-list-exhausted+ object)
(do ()
((null? object) '#f)
(xp.write-char++ #\space xp)
(xp.pprint-newline+ 'linear xp)
(xp.write+ (xp.pprint-pop+ object xp) xp))))
(define (xp.print-fancy-fn-call object xp template)
(xp.pprint-logical-block+ (xp object "(" ")" '#f '#t '#f)
(xp.write+ (xp.pprint-pop+ object xp) xp)
(xp.pprint-indent+ 'current 1 xp)
(do ((i 0 (1+ i))
(in-first-section '#t))
((null? object) '#f)
(xp.write-char++ #\space xp)
(when (eqv? i (car template))
(xp.pprint-indent+ 'block (cadr template) xp)
(setf template (cddr template))
(setf in-first-section '#f))
(pprint-newline (cond ((zero? i) 'miser)
(in-first-section 'fill)
(else 'linear))
xp)
(xp.write+ (xp.pprint-pop+ object xp) xp))))
(define (xp.let-print object xp)
;; (formatter "~:<~1I~W~^ ~@_~/xp:xp.bind-list/~^~@{ ~_~W~^~}~:>")
(xp.pprint-logical-block+ (xp object "(" ")" '#f '#t '#f)
(xp.pprint-indent+ 'block 1 xp)
(xp.write+ (xp.pprint-pop+ object xp) xp)
(xp.pprint-exit-if-list-exhausted+ object)
(xp.write-char++ #\space xp)
(xp.pprint-newline+ 'miser xp)
(xp.bind-list (xp.pprint-pop+ object xp) xp '#f '#f)
(xp.pprint-exit-if-list-exhausted+ object)
(do ()
((null? object) '#f)
(xp.write-char++ #\space xp)
(xp.pprint-newline+ 'linear xp)
(xp.write+ (xp.pprint-pop+ object xp) xp))))
(define (xp.flet-print object xp)
(xp.pprint-logical-block+ (xp object "(" ")" '#f '#t '#f)
(xp.pprint-indent+ 'block 1 xp)
(xp.write+ (xp.pprint-pop+ object xp) xp)
(xp.pprint-exit-if-list-exhausted+ object)
(xp.write-char++ #\space xp)
(xp.pprint-newline+ 'miser xp)
(xp.fbind-list (xp.pprint-pop+ object xp) xp '#f '#f)
(xp.pprint-exit-if-list-exhausted+ object)
(do ()
((null? object) '#f)
(xp.write-char++ #\space xp)
(xp.pprint-newline+ 'linear xp)
(xp.write+ (xp.pprint-pop+ object xp) xp))))
(define (xp.cond-print object xp)
;; (formatter "~:<~W~^ ~:I~@_~@{~:/xp:pprint-linear/~^ ~_~}~:>")
(xp.pprint-logical-block+ (xp object "(" ")" '#f '#t '#f)
(xp.write+ (xp.pprint-pop+ object xp) xp)
(xp.pprint-exit-if-list-exhausted+ object)
(xp.write-char++ #\space xp)
(xp.pprint-indent+ 'current 0 xp)
(xp.pprint-newline+ 'miser xp)
(pprint-linear xp (xp.pprint-pop+ object xp) '#t '#f)
(do ()
((null? object) '#f)
(xp.write-char++ #\space xp)
(xp.pprint-newline+ 'linear xp)
(pprint-linear xp (xp.pprint-pop+ object xp) '#t '#f))))
(define (xp.do-print object xp)
;; (formatter "~:<~W~^ ~:I~@_~/xp:xp.bind-list/~^ ~_~:/xp:pprint-linear/ ~1I~^~@{ ~_~W~^~}~:>")
(xp.pprint-logical-block+ (xp object "(" ")" '#f '#t '#f)
(xp.write+ (xp.pprint-pop+ object xp) xp)
(xp.pprint-exit-if-list-exhausted+ object)
(xp.write-char++ #\space xp)
(xp.pprint-indent+ 'current 0 xp)
(xp.pprint-newline+ 'miser xp)
(xp.bind-list (xp.pprint-pop+ object xp) xp '#f '#f)
(xp.pprint-exit-if-list-exhausted+ object)
(xp.write-char++ #\space xp)
(xp.pprint-newline+ 'linear xp)
(pprint-linear xp (xp.pprint-pop+ object xp) '#t '#f)
(xp.write-char++ #\space xp)
(xp.pprint-indent+ 'block 1 xp)
(do ()
((null? object) '#f)
(xp.write-char++ #\space xp)
(xp.pprint-newline+ 'linear xp)
(xp.write+ (xp.pprint-pop+ object xp) xp))))
(define (xp.mvb-print object xp)
(xp.print-fancy-fn-call object xp '(1 3 2 1)))
(define (xp.setf-print object xp)
;; (formatter "~:<~W~^ ~:I~@_~@{~W~^ ~:_~W~^ ~_~}~:>")
(xp.pprint-logical-block+ (xp object "(" ")" '#f '#t '#f)
(xp.write+ (xp.pprint-pop+ object xp) xp)
(xp.pprint-exit-if-list-exhausted+ object)
(xp.write-char++ #\space xp)
(xp.pprint-indent+ 'current 0 xp)
(xp.pprint-newline+ 'miser xp)
(xp.write+ (xp.pprint-pop+ object xp) xp)
(do ()
((null? object) '#f)
(xp.write-char++ #\space xp)
(xp.pprint-newline+ 'fill xp)
(xp.write+ (xp.pprint-pop+ object xp) xp)
(when (not (null? object))
(xp.write-char++ #\space xp)
(xp.pprint-newline+ 'linear xp)
(xp.write+ (xp.pprint-pop+ object xp) xp)))))
(define (xp.quote-print object xp)
(if (and (pair? (cdr object)) (null? (cddr object)))
(begin
(xp.write-char++ #\' xp)
(xp.write+ (cadr object) xp))
(pprint-fill xp object)))
(define (xp.up-print object xp)
(xp.print-fancy-fn-call object xp '(0 3 1 1)))
;;; Install printers for built-in macros and special forms into the
;;; standard dispatch table.
(define-local-syntax (define-printer symbol function)
`(setf (table-entry (dynamic *xp.pair-dispatch-table*) ',symbol)
(function ,function)))
;;; *** Missing support for backquote here.
(define-printer quote xp.quote-print)
(define-printer lambda xp.block-like)
(define-printer when xp.block-like)
(define-printer unless xp.block-like)
(define-printer cond xp.cond-print)
(define-printer case xp.block-like)
(define-printer setf xp.setf-print)
(define-printer set! xp.setf-print)
(define-printer let xp.let-print)
(define-printer let* xp.let-print)
(define-printer letrec xp.let-print)
(define-printer flet xp.flet-print)
(define-printer labels xp.flet-print)
(define-printer dynamic-let xp.let-print)
(define-printer block xp.block-like)
(define-printer do xp.do-print)
(define-printer dolist xp.block-like)
(define-printer dotimes xp.block-like)
(define-printer multiple-value-bind xp.mvb-print)
(define-printer let/cc xp.block-like)
(define-printer unwind-protect xp.up-print)
(define-printer define xp.block-like)
(define-printer define-syntax xp.block-like)
(define-printer define-local-syntax xp.block-like)
(define-printer pprint-logical-block xp.block-like)
(define-printer xp.pprint-logical-block+ xp.block-like)
;;; Here are some hacks for struct macros.
(define-printer update-slots xp.mvb-print)
(define-printer make xp.block-like)
|