; PP.LSP -- a pretty-printer for XLISP. ; Adapted by Jim Chapman (Bix: jchapman) from a program written originally ; for IQLISP by Don Cohen. Copyright (c) 1984, Don Cohen; (c) 1987, Jim ; Chapman. Permission for non-commercial use and distribution is hereby ; granted. Modified for XLISP 2.0 by David Betz. ; In addition to the pretty-printer itself, this file contains a few functions ; that illustrate some simple but useful applications. ; The basic function accepts two arguments: ; (PP OBJECT STREAM) ; where OBJECT is any Lisp expression, and STREAM optionally specifies the ; output (default is *standard-output*). ; PP-FILE pretty-prints an entire file. It is what I used to produce this ; file (before adding the comments manually). The syntax is: ; (PP-FILE "filename" STREAM) ; where the file name must be a string or quoted, and STREAM, again, is the ; optional output destination. ; PP-DEF works just like PP, except its first argument is assumed to be the ; name of a function or macro, which is translated back into the original ; DEFUN or DEFMACRO form before printing. ; MISCELLANEOUS USAGE AND CUSTOMIZATION NOTES: ; 1. The program uses tabs whenever possible for indentation. ; This greatly reduces the cost of the blank space. If your output ; device doesn't support tabs, set TABSIZE to NIL -- which is what I ; did when I pretty-printed this file, because of uncertainty ; about the result after uploading. ; 2. Printmacros are used to handle special forms. A printmacro is not ; really a macro, just an ordinary lambda form that is stored on the ; target symbol's property list. The default printer handles the form ; if there is no printmacro or if the printmacro returns NIL. ; 3. Note that all the pretty-printer subfunctions, including the ; the printmacros, return the current column position. ; 4. Miser mode is not fully implemented in this version, mainly because ; lookahead was too slow. The idea is, if the "normal" way of ; printing the current expression would exceed the right margin, then ; use a mode that conserves horizontal space. ; 5. When PP gets to the last 8th of the line and has more to print than ; fits on the line, it starts near the left margin. This is not ; wonderful, but neither are the alternatives. If you have a better ; idea, go for it. ; 6. Storage requirements are about 1450 cells to load. ; 7. I tested this with XLISP 1.7 on an Amiga. ; 8. TAA modified to support prettyprinting arrays. Fixed bug printing ; (NIL ...). ; 9. TAA modified to support prettyprinting of structures, and some code ; cleanup. Also added PP-PAIR-FORM to handle setq like structures ; more nicely. ; 10. TAA: It should be noted that you can't pretty print circular lists, ; nor can you successfully read back the following: ; * uninterned symbols, for instance those generated with gensym ; as part of automatically generated code ; * closures, since their environment cannot be reconstructed. These ; are not even expanded. ; * subrs, fsubrs, and streams cannot be represented ; 11. TAA modified so that non-class objects are shown by sending the ; message :storeon (see classes.lsp), printing #. before the expression ; making it an object literal. ; 11. TAA modified so that *print-level* and *print-length* are bound to NIL ; during the course of execution. #+:packages (unless (find-package "TOOLS") (make-package "TOOLS" :use '("XLISP"))) (in-package "TOOLS") (export '(tabsize maxsize miser-size pp-file pp-def pp)) ;(DEFUN SYM-FUNCTION (X) ;for Xlisp 1.7 ; (CAR (SYMBOL-VALUE X))) (defun sym-function (x) ;for Xlisp 2.0 (get-lambda-expression (symbol-function x))) (defvar tabsize 8) ;set this to NIL for no tabs (defvar maxsize 60) ;for readability, PP tries not to print more ;than this many characters on a line (defvar miser-size 2) ;the indentation in miser mode (defvar min-miser-car 4) ;used for deciding when to use miser mode (defvar max-normal-car 9) ;ditto (defconstant pp-lpar "(") ; self evident (defconstant pp-rpar ")") (defconstant pp-space " ") (defconstant pp-immed "#.") ; The following function prints a file (defun pp-file (filename &optional streamout) (or streamout (setq streamout *standard-output*)) (princ "; Listing of " streamout) (princ filename streamout) (terpri streamout) (terpri streamout) (do* ((fp (open filename)) (expr (read fp nil) (read fp nil))) ((null expr) (close fp)) (pp expr streamout) (terpri streamout))) ; Print a lambda or macro form as a DEFUN or DEFMACRO: (defmacro pp-def (who &optional stream) `(pp (make-def ,who) ,stream)) (defmacro make-def (name &aux expr type) (setq expr (sym-function name)) (setq type (cadr (assoc (car expr) '((lambda defun) (macro defmacro))))) (list 'quote (append (list type name) (cdr expr)))) ; The pretty-printer high level function: (defun pp (x &optional stream) (let (*print-level* *print-length*) ; set special vars to NIL (or stream (setq stream *standard-output*)) (pp1 x stream 1 80) (terpri stream) t)) ; print X on STREAM, current cursor is CURPOS, and right margin is RMARGIN (defun pp1 (x stream curpos rmargin &aux (anarray (arrayp x)) (astruct (typep x '(and struct (not random-state)))) size position width) (cond (anarray (setq x (coerce x 'cons))) ((and (objectp x) (not (classp x))) (princ pp-immed stream) ; immediate execute literal (setq curpos (+ curpos 2)) (setq x (send x :storeon)))) (cond (astruct (pp-astruct x stream curpos rmargin)) ((not (consp x))(prin1 x stream) (+ curpos (flatsize x))) ((printmacrop x stream curpos rmargin)) ((and (> (flatsize x) (- rmargin curpos)) (< (* 8 (- rmargin curpos)) rmargin)) (setq size (+ (/ rmargin 8) (- curpos rmargin))) (pp-moveto stream curpos size) (setq position (pp1 x stream size rmargin)) (pp-moveto stream position size)) (t (when anarray (princ "#" stream) (setq curpos (1+ curpos))) (princ pp-lpar stream) (setq position (pp1 (car x) stream (1+ curpos) rmargin)) (cond ((and (>= (setq width (- rmargin position)) (setq size (flatsize (cdr x)))) (<= size maxsize)) (pp-rest-across (cdr x) stream position rmargin)) ((consp (car x)) (pp-moveto stream position curpos) (pp-rest (cdr x) stream curpos rmargin)) ((> (- position curpos) max-normal-car) (pp-moveto stream position (+ curpos miser-size)) (pp-rest (cdr x) stream (+ curpos miser-size) rmargin)) (t (pp-rest (cdr x) stream position rmargin)))))) ; PP-MOVETO controls indentating and tabbing. ; If CUR > GOAL then goes to new line first. ; will space to GOAL (defun pp-moveto (stream curpos goalpos &aux i) (cond ((> curpos goalpos) (terpri stream) (setq curpos 1) (if tabsize (do nil ((< (- goalpos curpos) tabsize)) (princ "\t" stream) (setq curpos (+ curpos tabsize)))))) (dotimes (i (- goalpos curpos)) (princ pp-space stream)) goalpos) ; can print the rest of the list without new lines (defun pp-rest-across (x stream curpos rmargin &aux position) (setq position curpos) (prog nil lp (cond ((null x) (princ pp-rpar stream) (return (1+ position))) ((not (consp x)) (princ " . " stream) (prin1 x stream) (princ pp-rpar stream) (return (+ 4 position (flatsize x)))) (t (princ pp-space stream) (setq position (pp1 (car x) stream (1+ position) rmargin)) (setq x (cdr x)) (go lp))))) ; Can print the rest of the list, but must use new lines for each element (defun pp-rest (x stream curpos rmargin &aux position pos2) (setq position curpos) (prog nil lp (cond ((null x) (princ pp-rpar stream) (return (1+ position))) ((not (consp x)) (and (> (flatsize x) (- (- rmargin position) 3)) (setq position (pp-moveto stream position curpos))) (princ " . " stream) (prin1 x stream) (princ pp-rpar stream) (return (+ position 4 (flatsize x)))) ((and (not (typep (car x) '(or list array struct))) (<= (setq pos2 (+ 1 position (flatsize (car x)))) rmargin) (<= pos2 (+ curpos maxsize))) (princ pp-space stream) (prin1 (car x) stream) (setq position pos2)) (t (pp-moveto stream position (1+ curpos)) (setq position (pp1 (car x) stream (1+ curpos) rmargin)))) (cond ((and (consp (car x)) (cdr x)) (setq position (pp-moveto stream position curpos)))) (setq x (cdr x)) (go lp))) ; Handles structures by printing in form: ; #S(structtype :slot val ; ... ; :slot val) ; ; code does not check for defaults. (defun pp-astruct (x stream pos rmar &aux cur snames args) (setq cur pos snames (mapcar #'car (get (type-of x) '*struct-slots*)) args (mapcan #'(lambda (p) (list p (apply (intern (strcat (string (type-of x)) "-" (string p))) (list x)))) snames)) (princ "#s" stream) (if (and (>= (- rmar pos) (+ 2 (flatsize x))) (<= (flatsize x) maxsize)) (pp1 (cons (type-of x) args) stream (+ 2 pos) rmar) (prog () (princ pp-lpar stream) (prin1 (type-of x) stream) (princ pp-space stream) (setq pos (setq cur (+ pos 4 (flatsize (type-of x))))) lp (prin1 (first args) stream) (princ pp-space stream) (setq cur (pp1 (second args) stream (+ pos 1 (flatsize (first args))) rmar)) (setq args (cddr args)) (when (null args) (princ pp-rpar stream) (return-from pp-astruct (1+ cur))) (pp-moveto stream cur pos) (go lp)))) ; PRINTMACROP is the printmacro interface routine. Note that the ; called function has the same argument list as PP1. It may either ; decide not to handle the form, by returning NIL (and not printing) ; or it may print the form and return the resulting position. (defun printmacrop (x stream curpos rmargin &aux macro) (and (symbolp (car x)) (car x) ; must not be NIL (TAA fix) (setq macro (get (car x) 'printmacro)) (apply macro (list x stream curpos rmargin)))) ; The remaining forms define various printmacros. ; Printing format (xxx xxx ; ) (defun pp-binding-form (x stream pos rmar &aux cur) (setq cur pos) (cond ((and (>= (- rmar pos) (flatsize x)) (<= (flatsize x) maxsize)) nil) ((> (length x) 2) (princ pp-lpar stream) (prin1 (car x) stream) (princ pp-space stream) (setq cur (pp1 (cadr x) stream (+ 2 pos (flatsize (car x))) rmar)) (pp-moveto stream cur (+ pos 1)) (pp-rest (cddr x) stream (+ pos 1) rmar)))) ; Format (xxxx xxx xxx ;... ; xxx xxx) (defun pp-pair-form (x stream pos rmar &aux cur) (setq cur pos) (cond ((and (>= (- rmar pos) (flatsize x)) (<= (flatsize x) maxsize)) nil) ((> (length x) 1) (princ pp-lpar stream) (prin1 (first x) stream) (princ pp-space stream) (setq pos (setq cur (+ pos 2 (flatsize (first x))))) (setq x (rest x)) (loop (pp-moveto stream cur pos) (setq cur (pp1 (first x) stream pos rmar)) (princ pp-space stream) (setq x (rest x)) (setq cur (pp1 (first x) stream (1+ cur) rmar)) (when (null (setq x (rest x))) (princ pp-rpar stream) (return-from pp-pair-form (1+ cur))))))) ; format (xxx xxx ; xxx ; ) (defun pp-do-form (x stream pos rmar &aux cur pos2) (setq cur pos) (cond ((and (>= (- rmar pos) (flatsize x)) (<= (flatsize x) maxsize)) nil) ((> (length x) 2) (princ pp-lpar stream) (prin1 (car x) stream) (princ pp-space stream) (setq pos2 (+ 2 pos (flatsize (car x)))) (setq cur (pp1 (cadr x) stream pos2 rmar)) (pp-moveto stream cur pos2) (setq cur (pp1 (caddr x) stream pos2 rmar)) (pp-moveto stream cur (+ pos 1)) (pp-rest (cdddr x) stream (+ pos 1) rmar)))) ; format (xxx xxx xxx ; ) (defun pp-defining-form (x stream pos rmar &aux cur) (setq cur pos) (cond ((and (>= (- rmar pos) (flatsize x)) (<= (flatsize x) maxsize)) nil) ((> (length x) 3) (princ pp-lpar stream) (prin1 (car x) stream) (princ pp-space stream) (prin1 (cadr x) stream) (princ pp-space stream) (setq cur (pp1 (caddr x) stream (+ 3 pos (flatsize (car x)) (flatsize (cadr x))) rmar)) (pp-moveto stream cur (+ 3 pos)) (pp-rest (cdddr x) stream (+ 3 pos) rmar)))) (putprop 'quote '(lambda (x stream pos rmargin) (cond ((and (cdr x) (null (cddr x))) (princ "'" stream) (pp1 (cadr x) stream (1+ pos) rmargin)))) 'printmacro) (putprop 'backquote '(lambda (x stream pos rmargin) (cond ((and (cdr x) (null (cddr x))) (princ "`" stream) (pp1 (cadr x) stream (1+ pos) rmargin)))) 'printmacro) (putprop 'comma '(lambda (x stream pos rmargin) (cond ((and (cdr x) (null (cddr x))) (princ "," stream) (pp1 (cadr x) stream (1+ pos) rmargin)))) 'printmacro) (putprop 'comma-at '(lambda (x stream pos rmargin) (cond ((and (cdr x) (null (cddr x))) (princ ",@" stream) (pp1 (cadr x) stream (+ pos 2) rmargin)))) 'printmacro) (putprop 'function '(lambda (x stream pos rmargin) (cond ((and (cdr x) (null (cddr x))) (princ "#'" stream) (pp1 (cadr x) stream (+ pos 2) rmargin)))) 'printmacro) (putprop 'prog 'pp-binding-form 'printmacro) (putprop 'prog* 'pp-binding-form 'printmacro) (putprop 'let 'pp-binding-form 'printmacro) (putprop 'let* 'pp-binding-form 'printmacro) (putprop 'lambda 'pp-binding-form 'printmacro) (putprop 'macro 'pp-binding-form 'printmacro) (putprop 'do 'pp-do-form 'printmacro) (putprop 'do* 'pp-do-form 'printmacro) (putprop 'defun 'pp-defining-form 'printmacro) (putprop 'defmacro 'pp-defining-form 'printmacro) (putprop 'setq 'pp-pair-form 'printmacro) (putprop 'setf 'pp-pair-form 'printmacro) (putprop 'psetq 'pp-pair-form 'printmacro) (putprop 'send 'pp-defining-form 'printmacro)