;; Copyright (C) 2002-2004, Yuji Minejima ;; ALL RIGHTS RESERVED. ;; ;; $Id: printer.lisp,v 1.14 2004/03/01 05:18:11 yuji Exp $ ;; ;; Redistribution and use in source and binary forms, with or without ;; modification, are permitted provided that the following conditions ;; are met: ;; ;; * Redistributions of source code must retain the above copyright ;; notice, this list of conditions and the following disclaimer. ;; * Redistributions in binary form must reproduce the above copyright ;; notice, this list of conditions and the following disclaimer in ;; the documentation and/or other materials provided with the ;; distribution. ;; ;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ;; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ;; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ;; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ;; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ;; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ;; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ;; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ;; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ;; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ;;; Commentary: ;; Need to load reader.lisp in advance for the function SYNTAX-TYPE. ;; ;;; printer control variables ;; number (defvar *print-base* 10 "The radix in which the printer will print rationals.") (defvar *print-radix* nil "If true, print a radix specifier when printing a rational number.") ;; symbol (defvar *print-case* :upcase "One of the symbols :upcase, :downcase, or :capitalize.") (defvar *print-gensym* t "If true, print `#:' before apparently uninterned symbols.") ;; container (defvar *print-array* t ; implementation-dependent "If true, arrays are printed in readable #(...), #*, or #nA(...) syntax.") (defvar *print-level* nil "Control how many levels deep a nested object will print.") (defvar *print-length* nil "Control how many elements at a given level are printed.") (defvar *print-circle* nil "If true, detect circularity and sharing in an object being printed.") ;; symbol, string (defvar *print-escape* t "If false, escape characters and package prefixes are not output.") ;; readability ;; *print-readably* ;; true ;; true: *print-escape*, *print-array*, and *print-gensym* ;; false: *print-length*, *print-level*, and *print-lines* (defvar *print-readably* nil "If true, print objects readably.") ;; layout (defvar *print-pretty* t ; implementation-dependent "If true, the pretty printer is used when printing.") ;;(defvar *print-pprint-dispatch* ;; ) (defvar *print-lines* nil "Limit on the number of output lines produced when pretty printing.") (defvar *print-miser-width* nil ; implementation-dependent "Switch to a compact style of output whenever the width available for printing a substructure is less than or equal to this many ems when pretty printing.") (defvar *print-right-margin* nil "Specify the right margin to use when the pretty printer is making layout decisions.") (defmacro with-standard-io-syntax (&rest forms) "Bind all reader/printer control vars to the standard values then eval FORMS." `(let ((*package* (find-package "CL-USER")) (*print-array* t) (*print-base* 10) (*print-case* :upcase) (*print-circle* nil) (*print-escape* t) (*print-gensym* t) (*print-length* nil) (*print-level* nil) (*print-lines* nil) (*print-miser-width* nil) ;;(*print-pprint-dispatch* *standard-print-pprint-dispatch*) (*print-pretty* nil) (*print-radix* nil) (*print-readably* t) (*print-right-margin* nil) (*read-base* 10) (*read-default-float-format* 'single-float) (*read-eval* t) (*read-suppress* nil) (*readtable* (copy-readtable nil))) ,@forms)) (defgeneric print-object (object stream)) (defun write (object &key ((:array *print-array*) *print-array*) ((:base *print-base*) *print-base*) ((:case *print-case*) *print-case*) ((:circle *print-circle*) *print-circle*) ((:escape *print-escape*) *print-escape*) ((:gensym *print-gensym*) *print-gensym*) ((:length *print-length*) *print-length*) ((:level *print-level*) *print-level*) ((:lines *print-lines*) *print-lines*) ((:miser-width *print-miser-width*) *print-miser-width*) ((:pprint-dispatch *print-pprint-dispatch*) *print-pprint-dispatch*) ((:pretty *print-pretty*) *print-pretty*) ((:radix *print-radix*) *print-radix*) ((:readably *print-readably*) *print-readably*) ((:right-margin *print-right-margin*) *print-right-margin*) (stream *standard-output*)) ;; http://www.lispworks.com/reference/HyperSpec/Body/22_ab.htm ;; 22.1.2 Printer Dispatching ;; The Lisp printer makes its determination of how to print an object as ;; follows: If the value of *print-pretty* is true, printing is controlled ;; by the current pprint dispatch table; see Section 22.2.1.4 (Pretty Print ;; Dispatch Tables). ;; Otherwise (if the value of *print-pretty* is false), the object's ;; print-object method is used; ;; see Section 22.1.3 (Default Print-Object Methods). (if *print-pretty* (print-object-prettily object stream) (print-object object stream)) object) (defun write-to-string (object &key ((:array *print-array*) *print-array*) ((:base *print-base*) *print-base*) ((:case *print-case*) *print-case*) ((:circle *print-circle*) *print-circle*) ((:escape *print-escape*) *print-escape*) ((:gensym *print-gensym*) *print-gensym*) ((:length *print-length*) *print-length*) ((:level *print-level*) *print-level*) ((:lines *print-lines*) *print-lines*) ((:miser-width *print-miser-width*) *print-miser-width*) ((:pprint-dispatch *print-pprint-dispatch*) *print-pprint-dispatch*) ((:pretty *print-pretty*) *print-pretty*) ((:radix *print-radix*) *print-radix*) ((:readably *print-readably*) *print-readably*) ((:right-margin *print-right-margin*) *print-right-margin*)) (with-output-to-string (stream) (if *print-pretty* (print-object-prettily object stream) (print-object object stream)))) (defun prin1 (object &optional output-stream) (write object :stream output-stream :escape t)) (defun prin1-to-string (object) (write-to-string object :escape t)) (defun princ (object &optional output-stream) (write object :stream output-stream :escape nil :readably nil)) (defun princ-to-string (object) (write-to-string object :escape nil :readably nil)) (defun print (object &optional output-stream) (terpri output-stream) (prin1 object output-stream) (write-char #\Space output-stream) object) (defun pprint (object &optional output-stream) (terpri output-stream) (write object :stream output-stream :pretty t :escape t) (values)) ;; function pprint-dispatch ;; macro pprint-logical-block ;; local macro pprint-pop ;; local macro pprint-exit-if-list-exhausted ;; function pprint-newline ;; function pprint-tab ;; function pprint-fill, pprint-linear, pprint-tabular ;; function pprint-indent (defun printer-escaping-enabled-p () (or *print-escape* *print-readably*)) (defmethod print-object ((object integer) stream) (print-integer object stream)) (defun print-integer (integer stream) (let ((chars "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") digits) (loop with n = (abs integer) do (multiple-value-bind (q r) (floor n *print-base*) (push (char chars r) digits) (setq n q)) until (zerop n)) (when *print-radix* (case *print-base* (2 (write-string "#b" stream)) (8 (write-string "#o" stream)) (16 (write-string "#x" stream)) (10 nil) (t (write-char #\# stream) (let ((base *print-base*) (*print-base* 10) (*print-radix* nil)) (print-integer base stream)) (write-char #\r stream)))) (write-string (concatenate 'string (when (minusp integer) '(#\-)) digits (when (and *print-radix* (= *print-base* 10)) ".")) stream) integer)) (defmethod print-object ((ratio ratio) stream) ;; http://www.lispworks.com/reference/HyperSpec/Body/v_pr_bas.htm ;; For integers, base ten is indicated by a trailing decimal point instead ;; of a leading radix specifier; for ratios, #10r is used. (if (and *print-radix* (= *print-base* 10)) (progn (write-string "#10r" stream) (let ((*print-radix* nil)) (print-integer (numerator ratio) stream))) (print-integer (numerator ratio) stream)) (write-char #\/ stream) (let ((*print-radix* nil)) (print-integer (denominator ratio) stream)) ratio) (defmethod print-object ((complex complex) stream) (write-string "#C(" stream) (print-object (realpart complex) stream) (write-char #\Space stream) (print-object (imagpart complex) stream) (write-char #\)) complex) (defmethod print-object ((character character) stream) (cond ((printer-escaping-enabled-p) (write-string "#\\" stream) (if (and (graphic-char-p character) (not (char= character #\Space))) (write-char character stream) (write-string (char-name character) stream))) (t (write-char character stream))) character) (defun string-invert (str) (cond ((every #'(lambda (c) (or (not (alpha-char-p c)) (upper-case-p c))) str) (map 'string #'char-downcase str)) ((every #'(lambda (c) (or (not (alpha-char-p c)) (lower-case-p c))) str) (map 'string #'char-upcase str)) (t str))) (defun make-str (chars) (make-array (length chars) :element-type 'character :initial-contents chars)) (defun print-symbol-as-is (symbol stream) (let ((name (symbol-name symbol))) (ecase (readtable-case *readtable*) (:upcase (write-string (ecase *print-case* (:upcase name) (:downcase (map 'string #'char-downcase name)) (:capitalize (make-str (loop for c across name and prev = nil then c collecting (if (and (upper-case-p c) prev (alpha-char-p prev)) (char-downcase c) c))))) stream)) (:downcase (write-string (ecase *print-case* (:upcase (map 'string #'char-upcase name)) (:downcase name) (:capitalize (make-str (loop for c across name and prev = nil then c collecting (if (and (lower-case-p c) (or (null prev) (not (alpha-char-p prev)))) (char-upcase c) c))))) stream)) (:preserve (write-string name stream)) (:invert (write-string (string-invert name) stream))) symbol)) (defun print-name-escaping (name stream &key force-escaping) (let ((readtable-case (readtable-case *readtable*))) (if (or force-escaping (loop with standard-table = (copy-readtable nil) for c across name thereis (not (and (eq (syntax-type c standard-table) :constituent) (eq (syntax-type c) :constituent)))) (notevery #'graphic-char-p name) (and (eq readtable-case :upcase) (some 'lower-case-p name)) (and (eq readtable-case :downcase) (some 'upper-case-p name))) (let ((escaped (loop for c across name if (find c '(#\\ #\|)) append (list #\\ c) else collect c))) (write-string (concatenate 'string "|" escaped "|") stream)) (write-string (case readtable-case ((:upcase :downcase) (ecase *print-case* (:upcase (string-upcase name)) (:downcase (string-downcase name)) (:capitalize (string-capitalize name)))) (:invert (cond ((notany #'both-case-p name) name) ((notany #'upper-case-p name) (string-upcase name)) ((notany #'lower-case-p name) (string-downcase name)) (t name))) (t name)) stream)))) (defun print-symbol-escaping (symbol stream) (let* ((name (symbol-name symbol)) (accessible-p (eq symbol (find-symbol name)))) (cond (accessible-p nil) ((symbol-package symbol) (let ((package-name (package-name (symbol-package symbol)))) (unless (string= package-name "KEYWORD") (print-name-escaping package-name stream)) (multiple-value-bind (symbol status) (find-symbol name package-name) (declare (ignore symbol)) (write-string (if (eq status :external) ":" "::") stream)))) ((or *print-readably* *print-gensym*) (write-string "#:" stream)) (t nil)) (print-name-escaping name stream :force-escaping (and accessible-p (every #'(lambda (c) (digit-char-p c *print-base*)) name))) symbol)) (defmethod print-object ((symbol symbol) stream) (funcall (if (printer-escaping-enabled-p) #'print-symbol-escaping #'print-symbol-as-is) symbol stream)) (defvar *shared-object-table* (make-hash-table)) (defvar *shared-object-label* (make-hash-table)) (defvar *shared-object-label-counter* 0) (defvar *current-print-level* 0) (defun print-max-level-p () (and (not *print-readably*) *print-level* (= *current-print-level* *print-level*))) (defun print-max-length-p (n) (and (not *print-readably*) *print-length* (= n *print-length*))) (defun inc-shared-object-reference (object) (if (and (symbolp object) (symbol-package object)) 0 (multiple-value-bind (n present-p) (gethash object *shared-object-table*) (if present-p (progn (when (zerop n) (setf (gethash object *shared-object-label*) (incf *shared-object-label-counter*))) (incf (gethash object *shared-object-table*))) (setf (gethash object *shared-object-table*) 0))))) (defmethod search-shared-object :around ((object t)) (if (zerop *current-print-level*) (progn (setq *shared-object-label* (clrhash *shared-object-label*) *shared-object-table* (clrhash *shared-object-table*) *shared-object-label-counter* 0) (inc-shared-object-reference object) (call-next-method object) (maphash #'(lambda (object n) (if (zerop n) (remhash object *shared-object-table*) (setf (gethash object *shared-object-table*) 0))) *shared-object-table*)) (when (zerop (inc-shared-object-reference object)) (call-next-method object)))) (defun search-shared-element (object) (let ((*current-print-level* (1+ *current-print-level*))) (unless (print-max-level-p) (search-shared-object object)))) (defmethod search-shared-object ((object t))) ; do nothing (defmethod search-shared-object ((list list)) (do ((x list) (l 0 (1+ l))) ((or (print-max-level-p) (print-max-length-p l) (atom x))) (search-shared-element (car x)) (setq x (cdr x)) (when (plusp (inc-shared-object-reference x)) (return)))) (defmethod search-shared-object ((vector vector)) (do ((i 0 (1+ i))) ((or (= i (length vector)) (print-max-level-p) (print-max-length-p i))) (search-shared-element (aref vector i)))) (defmethod search-shared-object ((array array)) (do ((i 0 (1+ i))) ((or (= i (array-total-size array)) (print-max-level-p) (print-max-length-p i))) (search-shared-element (row-major-aref array i)))) (defun print-element (object stream) (let ((*current-print-level* (1+ *current-print-level*))) (multiple-value-bind (n present-p) (gethash object *shared-object-table*) (if (and present-p *print-circle*) (if (zerop n) (progn (print-label object stream) (print-object object stream)) (print-reference object stream)) (print-object object stream))))) (defun print-label (object stream) (multiple-value-bind (n present-p) (gethash object *shared-object-label*) (assert present-p) (write-string "#" stream) (let ((*print-base* 10) (*print-radix* nil)) (print-integer n stream)) (write-string "=" stream) (incf (gethash object *shared-object-table*)))) (defun print-reference (object stream) (multiple-value-bind (n present-p) (gethash object *shared-object-label*) (assert present-p) (write-string "#" stream) (let ((*print-base* 10) (*print-radix* nil)) (print-integer n stream)) (write-string "#" stream))) (defmethod print-object ((list cons) stream) (when (and *print-circle* (zerop *current-print-level*)) (search-shared-object list)) (if (print-max-level-p) (write-string "#" stream) (let ((x list) (l 0)) (multiple-value-bind (n present-p) (gethash x *shared-object-table*) (when (and (zerop *current-print-level*) present-p *print-circle*) (print-label x stream)) (write-string "(" stream) (loop (when (atom x) (when x (write-string " . " stream) (print-element x stream)) (write-string ")" stream) (return)) (when (print-max-length-p l) (write-string "...)" stream) (return)) (print-element (car x) stream) (setq x (cdr x) l (1+ l)) (when (consp x) (write-string " " stream) (multiple-value-setq (n present-p) (gethash x *shared-object-table*)) (when (and present-p *print-circle*) (write-string ". " stream) (if (zerop n) (print-element x stream) (print-reference x stream)) (write-string ")" stream) (return)))))))) (defmethod print-object :around ((array array) stream) (cond ((and (not *print-readably*) (not *print-array*) (not (stringp array))) ;; 22.1.3.4 Printing Strings ;; http://www.lispworks.com/reference/HyperSpec/Body/22_acd.htm ;; The printing of strings is not affected by *print-array*. (print-unreadable-object (array stream :type t :identity t))) ((and (print-max-level-p) (not (stringp array)) (not (bit-vector-p array))) ;; Variable *PRINT-LEVEL*, *PRINT-LENGTH* ;; http://www.lispworks.com/reference/HyperSpec/Body/v_pr_lev.htm ;; *print-level* and *print-length* affect the printing of ;; an any object printed with a list-like syntax. They do not affect ;; the printing of symbols, strings, and bit vectors. (write-string "#" stream)) (t (when (and *print-circle* (zerop *current-print-level*) (not (stringp array)) (not (bit-vector-p array))) (search-shared-object array) (multiple-value-bind (n present-p) (gethash array *shared-object-table*) (declare (ignore n)) (when present-p (print-label array stream)))) (call-next-method array stream)))) (defmethod print-object ((vector vector) stream) (let ((l 0) (length (length vector))) (write-string "#(" stream) (loop (when (= l length) (write-string ")" stream) (return)) (when (print-max-length-p l) (write-string "...)" stream) (return)) (print-element (aref vector l) stream) (setq l (1+ l)) (when (< l length) (write-string " " stream))))) (defmethod print-object ((array array) stream) (let* ((dimensions (array-dimensions array)) (indices (make-list (array-rank array) :initial-element 0))) (labels ((p-array (i-list d-list) (cond ((print-max-level-p) (write-string "#" stream)) ((null i-list) (print-element (apply #'aref array indices) stream)) (t (write-string "(" stream) (do ((i 0 (1+ i))) ((= i (car d-list))) (when (plusp i) (write-string " " stream)) (when (print-max-length-p i) (write-string "..." stream) (return)) (setf (car i-list) i) (if (null (cdr i-list)) (print-element (apply #'aref array indices) stream) (let ((*current-print-level* (1+ *current-print-level*))) (p-array (cdr i-list) (cdr d-list))))) (write-string ")" stream))))) (write-string "#" stream) (let ((*print-base* 10) (*print-radix* nil)) (print-integer (array-rank array) stream)) (write-string "A" stream) (p-array indices dimensions)))) (defmethod print-object ((string string) stream) (let ((escape-p (printer-escaping-enabled-p))) (when escape-p (write-char #\" stream)) (loop for c across string if (and escape-p (member c '(#\" #\\))) do (write-char #\\ stream) do (write-char c stream)) (when escape-p (write-char #\" stream)) string)) (defmethod print-object ((bit-vector bit-vector) stream) (if (or *print-array* *print-readably*) (progn (write-string "#*" stream) (loop for bit across bit-vector do (write-char (if (zerop bit) #\0 #\1) stream))) (print-unreadable-object (bit-vector stream :type t :identity t))) bit-vector) (defmethod print-object ((object t) stream) (print-unreadable-object (object stream :type t :identity t))) (defun print-object-prettily (object stream) (print-object object stream)) ;; format ;; (defun format (destination format-control &rest args) ;; (apply (if (stringp format-control) (formatter format-control) format-control) ;; destination args)) ;; ;; ;; (defmacro formatter (control-string) ;; ;; )