diff options
| author | Stefan Monnier | 2017-02-23 21:06:54 -0500 |
|---|---|---|
| committer | Stefan Monnier | 2017-02-23 21:06:54 -0500 |
| commit | 407e650413c0296f5873a1399c2306b25f81f310 (patch) | |
| tree | 7ef40c77b1a38cf127c07cf4662497b8170a658b | |
| parent | f6d2ba74f80b9a055a3d8072d49475aec45c2dbe (diff) | |
| download | emacs-407e650413c0296f5873a1399c2306b25f81f310.tar.gz emacs-407e650413c0296f5873a1399c2306b25f81f310.zip | |
* lisp/emacs-lisp/cl-print.el: New file
* lisp/emacs-lisp/nadvice.el (advice--where): New function.
(advice--make-docstring): Use it.
* src/print.c (print_number_index): Don't declare here any more.
(Fprint_preprocess): New function.
* test/lisp/emacs-lisp/cl-print-tests.el: New file.
| -rw-r--r-- | lisp/emacs-lisp/cl-print.el | 196 | ||||
| -rw-r--r-- | lisp/emacs-lisp/nadvice.el | 18 | ||||
| -rw-r--r-- | src/print.c | 32 | ||||
| -rw-r--r-- | test/lisp/emacs-lisp/cl-print-tests.el | 40 |
4 files changed, 271 insertions, 15 deletions
diff --git a/lisp/emacs-lisp/cl-print.el b/lisp/emacs-lisp/cl-print.el new file mode 100644 index 00000000000..b4ceefb9b1d --- /dev/null +++ b/lisp/emacs-lisp/cl-print.el | |||
| @@ -0,0 +1,196 @@ | |||
| 1 | ;;; cl-print.el --- CL-style generic printer facilies -*- lexical-binding: t; -*- | ||
| 2 | |||
| 3 | ;; Copyright (C) 2017 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: Stefan Monnier <monnier@iro.umontreal.ca> | ||
| 6 | ;; Keywords: | ||
| 7 | ;; Version: 1.0 | ||
| 8 | ;; Package-Requires: ((emacs "25")) | ||
| 9 | |||
| 10 | ;; This file is part of GNU Emacs. | ||
| 11 | |||
| 12 | ;; GNU Emacs is free software: you can redistribute it and/or modify | ||
| 13 | ;; it under the terms of the GNU General Public License as published by | ||
| 14 | ;; the Free Software Foundation, either version 3 of the License, or | ||
| 15 | ;; (at your option) any later version. | ||
| 16 | |||
| 17 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 20 | ;; GNU General Public License for more details. | ||
| 21 | |||
| 22 | ;; You should have received a copy of the GNU General Public License | ||
| 23 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. | ||
| 24 | |||
| 25 | ;;; Commentary: | ||
| 26 | |||
| 27 | ;; Customizable print facility. | ||
| 28 | ;; | ||
| 29 | ;; The heart of it is the generic function `cl-print-object' to which you | ||
| 30 | ;; can add any method you like. | ||
| 31 | ;; | ||
| 32 | ;; The main entry point is `cl-prin1'. | ||
| 33 | |||
| 34 | ;;; Code: | ||
| 35 | |||
| 36 | (defvar cl-print-readably nil | ||
| 37 | "If non-nil, try and make sure the result can be `read'.") | ||
| 38 | |||
| 39 | (defvar cl-print--number-table nil) | ||
| 40 | |||
| 41 | ;;;###autoload | ||
| 42 | (cl-defgeneric cl-print-object (object stream) | ||
| 43 | "Dispatcher to print OBJECT on STREAM according to its type. | ||
| 44 | You can add methods to it to customize the output. | ||
| 45 | But if you just want to print something, don't call this directly: | ||
| 46 | call other entry points instead, such as `cl-prin1'." | ||
| 47 | ;; This delegates to the C printer. The C printer will not call us back, so | ||
| 48 | ;; we should only use it for objects which don't have nesting. | ||
| 49 | (prin1 object stream)) | ||
| 50 | |||
| 51 | (cl-defmethod cl-print-object ((object cons) stream) | ||
| 52 | (let ((car (pop object))) | ||
| 53 | (if (and (memq car '(\, quote \` \,@ \,.)) | ||
| 54 | (consp object) | ||
| 55 | (null (cdr object))) | ||
| 56 | (progn | ||
| 57 | (princ (if (eq car 'quote) '\' car) stream) | ||
| 58 | (cl-print-object (car object) stream)) | ||
| 59 | (princ "(" stream) | ||
| 60 | (cl-print-object car stream) | ||
| 61 | (while (and (consp object) | ||
| 62 | (not (and cl-print--number-table | ||
| 63 | (numberp (gethash object cl-print--number-table))))) | ||
| 64 | (princ " " stream) | ||
| 65 | (cl-print-object (pop object) stream)) | ||
| 66 | (when object | ||
| 67 | (princ " . " stream) (cl-print-object object stream)) | ||
| 68 | (princ ")" stream)))) | ||
| 69 | |||
| 70 | (cl-defmethod cl-print-object ((object vector) stream) | ||
| 71 | (princ "[" stream) | ||
| 72 | (dotimes (i (length object)) | ||
| 73 | (unless (zerop i) (princ " " stream)) | ||
| 74 | (cl-print-object (aref object i) stream)) | ||
| 75 | (princ "]" stream)) | ||
| 76 | |||
| 77 | (cl-defmethod cl-print-object ((object compiled-function) stream) | ||
| 78 | (princ "#<compiled-function " stream) | ||
| 79 | (prin1 (help-function-arglist object 'preserve-names) stream) | ||
| 80 | (princ " #<bytecode> >" stream)) | ||
| 81 | |||
| 82 | ;; This belongs in nadvice.el, of course, but some load-ordering issues make it | ||
| 83 | ;; complicated: cl-generic uses macros from cl-macs and cl-macs uses advice-add | ||
| 84 | ;; from nadvice, so nadvice needs to be loaded before cl-generic and hence | ||
| 85 | ;; can't use cl-defmethod. | ||
| 86 | (cl-defmethod cl-print-object :extra "nadvice" | ||
| 87 | ((object compiled-function) stream) | ||
| 88 | (if (not (advice--p object)) | ||
| 89 | (cl-call-next-method) | ||
| 90 | (princ "#<advice-wrapper " stream) | ||
| 91 | (when (fboundp 'advice--where) | ||
| 92 | (princ (advice--where object) stream) | ||
| 93 | (princ " " stream)) | ||
| 94 | (cl-print-object (advice--cdr object) stream) | ||
| 95 | (princ " " stream) | ||
| 96 | (cl-print-object (advice--car object) stream) | ||
| 97 | (let ((props (advice--props object))) | ||
| 98 | (when props | ||
| 99 | (princ " " stream) | ||
| 100 | (cl-print-object props stream))) | ||
| 101 | (princ ">" stream))) | ||
| 102 | |||
| 103 | (cl-defmethod cl-print-object ((object cl-structure-object) stream) | ||
| 104 | (princ "#s(" stream) | ||
| 105 | (let* ((class (symbol-value (aref object 0))) | ||
| 106 | (slots (cl--struct-class-slots class))) | ||
| 107 | (princ (cl--struct-class-name class) stream) | ||
| 108 | (dotimes (i (length slots)) | ||
| 109 | (let ((slot (aref slots i))) | ||
| 110 | (princ " :" stream) | ||
| 111 | (princ (cl--slot-descriptor-name slot) stream) | ||
| 112 | (princ " " stream) | ||
| 113 | (cl-print-object (aref object (1+ i)) stream)))) | ||
| 114 | (princ ")" stream)) | ||
| 115 | |||
| 116 | ;;; Circularity and sharing. | ||
| 117 | |||
| 118 | ;; I don't try to support the `print-continuous-numbering', because | ||
| 119 | ;; I think it's ill defined anyway: if an object appears only once in each call | ||
| 120 | ;; its sharing can't be properly preserved! | ||
| 121 | |||
| 122 | (cl-defmethod cl-print-object :around (object stream) | ||
| 123 | ;; FIXME: Only put such an :around method on types where it's relevant. | ||
| 124 | (let ((n (if cl-print--number-table (gethash object cl-print--number-table)))) | ||
| 125 | (if (not (numberp n)) | ||
| 126 | (cl-call-next-method) | ||
| 127 | (if (> n 0) | ||
| 128 | ;; Already printed. Just print a reference. | ||
| 129 | (progn (princ "#" stream) (princ n stream) (princ "#" stream)) | ||
| 130 | (puthash object (- n) cl-print--number-table) | ||
| 131 | (princ "#" stream) (princ (- n) stream) (princ "=" stream) | ||
| 132 | (cl-call-next-method))))) | ||
| 133 | |||
| 134 | (defvar cl-print--number-index nil) | ||
| 135 | |||
| 136 | (defun cl-print--find-sharing (object table) | ||
| 137 | ;; Avoid recursion: not only because it's too easy to bump into | ||
| 138 | ;; `max-lisp-eval-depth', but also because function calls are fairly slow. | ||
| 139 | ;; At first, I thought using a list for our stack would cause too much | ||
| 140 | ;; garbage to generated, but I didn't notice any such problem in practice. | ||
| 141 | ;; I experimented with using an array instead, but the result was slightly | ||
| 142 | ;; slower and the reduction in GC activity was less than 1% on my test. | ||
| 143 | (let ((stack (list object))) | ||
| 144 | (while stack | ||
| 145 | (let ((object (pop stack))) | ||
| 146 | (unless | ||
| 147 | ;; Skip objects which don't have identity! | ||
| 148 | (or (floatp object) (numberp object) | ||
| 149 | (null object) (if (symbolp object) (intern-soft object))) | ||
| 150 | (let ((n (gethash object table))) | ||
| 151 | (cond | ||
| 152 | ((numberp n)) ;All done. | ||
| 153 | (n ;Already seen, but only once. | ||
| 154 | (let ((n (1+ cl-print--number-index))) | ||
| 155 | (setq cl-print--number-index n) | ||
| 156 | (puthash object (- n) table))) | ||
| 157 | (t | ||
| 158 | (puthash object t table) | ||
| 159 | (pcase object | ||
| 160 | (`(,car . ,cdr) | ||
| 161 | (push cdr stack) | ||
| 162 | (push car stack)) | ||
| 163 | ((pred stringp) | ||
| 164 | ;; We presumably won't print its text-properties. | ||
| 165 | nil) | ||
| 166 | ((or (pred arrayp) (pred byte-code-function-p)) | ||
| 167 | ;; FIXME: Inefficient for char-tables! | ||
| 168 | (dotimes (i (length object)) | ||
| 169 | (push (aref object i) stack)))))))))))) | ||
| 170 | |||
| 171 | (defun cl-print--preprocess (object) | ||
| 172 | (let ((print-number-table (make-hash-table :test 'eq :rehash-size 2.0))) | ||
| 173 | (if (fboundp 'print--preprocess) | ||
| 174 | ;; Use the predefined C version if available. | ||
| 175 | (print--preprocess object) ;Fill print-number-table! | ||
| 176 | (let ((cl-print--number-index 0)) | ||
| 177 | (cl-print--find-sharing object print-number-table))) | ||
| 178 | print-number-table)) | ||
| 179 | |||
| 180 | ;;;###autoload | ||
| 181 | (defun cl-prin1 (object &optional stream) | ||
| 182 | (cond | ||
| 183 | (cl-print-readably (prin1 object stream)) | ||
| 184 | ((not print-circle) (cl-print-object object stream)) | ||
| 185 | (t | ||
| 186 | (let ((cl-print--number-table (cl-print--preprocess object))) | ||
| 187 | (cl-print-object object stream))))) | ||
| 188 | |||
| 189 | ;;;###autoload | ||
| 190 | (defun cl-prin1-to-string (object) | ||
| 191 | (with-temp-buffer | ||
| 192 | (cl-prin1 object (current-buffer)) | ||
| 193 | (buffer-string))) | ||
| 194 | |||
| 195 | (provide 'cl-print) | ||
| 196 | ;;; cl-print.el ends here | ||
diff --git a/lisp/emacs-lisp/nadvice.el b/lisp/emacs-lisp/nadvice.el index 5a100b790f1..fd1cd2c7aaf 100644 --- a/lisp/emacs-lisp/nadvice.el +++ b/lisp/emacs-lisp/nadvice.el | |||
| @@ -72,6 +72,13 @@ Each element has the form (WHERE BYTECODE STACK) where: | |||
| 72 | (setq f (advice--cdr f))) | 72 | (setq f (advice--cdr f))) |
| 73 | f) | 73 | f) |
| 74 | 74 | ||
| 75 | (defun advice--where (f) | ||
| 76 | (let ((bytecode (aref f 1)) | ||
| 77 | (where nil)) | ||
| 78 | (dolist (elem advice--where-alist) | ||
| 79 | (if (eq bytecode (cadr elem)) (setq where (car elem)))) | ||
| 80 | where)) | ||
| 81 | |||
| 75 | (defun advice--make-docstring (function) | 82 | (defun advice--make-docstring (function) |
| 76 | "Build the raw docstring for FUNCTION, presumably advised." | 83 | "Build the raw docstring for FUNCTION, presumably advised." |
| 77 | (let* ((flist (indirect-function function)) | 84 | (let* ((flist (indirect-function function)) |
| @@ -79,16 +86,13 @@ Each element has the form (WHERE BYTECODE STACK) where: | |||
| 79 | (docstring nil)) | 86 | (docstring nil)) |
| 80 | (if (eq 'macro (car-safe flist)) (setq flist (cdr flist))) | 87 | (if (eq 'macro (car-safe flist)) (setq flist (cdr flist))) |
| 81 | (while (advice--p flist) | 88 | (while (advice--p flist) |
| 82 | (let ((bytecode (aref flist 1)) | 89 | (let ((doc (aref flist 4)) |
| 83 | (doc (aref flist 4)) | 90 | (where (advice--where flist))) |
| 84 | (where nil)) | ||
| 85 | ;; Hack attack! For advices installed before calling | 91 | ;; Hack attack! For advices installed before calling |
| 86 | ;; Snarf-documentation, the integer offset into the DOC file will not | 92 | ;; Snarf-documentation, the integer offset into the DOC file will not |
| 87 | ;; be installed in the "core unadvised function" but in the advice | 93 | ;; be installed in the "core unadvised function" but in the advice |
| 88 | ;; object instead! So here we try to undo the damage. | 94 | ;; object instead! So here we try to undo the damage. |
| 89 | (if (integerp doc) (setq docfun flist)) | 95 | (if (integerp doc) (setq docfun flist)) |
| 90 | (dolist (elem advice--where-alist) | ||
| 91 | (if (eq bytecode (cadr elem)) (setq where (car elem)))) | ||
| 92 | (setq docstring | 96 | (setq docstring |
| 93 | (concat | 97 | (concat |
| 94 | docstring | 98 | docstring |
| @@ -502,6 +506,10 @@ of the piece of advice." | |||
| 502 | (setq frame2 (backtrace-frame i #'called-interactively-p)) | 506 | (setq frame2 (backtrace-frame i #'called-interactively-p)) |
| 503 | ;; (message "Advice Frame %d = %S" i frame2) | 507 | ;; (message "Advice Frame %d = %S" i frame2) |
| 504 | (setq i (1+ i))))) | 508 | (setq i (1+ i))))) |
| 509 | ;; FIXME: Adjust this for the new :filter advices, since they use `funcall' | ||
| 510 | ;; rather than `apply'. | ||
| 511 | ;; FIXME: Somehow this doesn't work on (advice-add :before | ||
| 512 | ;; 'call-interactively #'ignore), see bug#3984. | ||
| 505 | (when (and (eq (nth 1 frame2) 'apply) | 513 | (when (and (eq (nth 1 frame2) 'apply) |
| 506 | (progn | 514 | (progn |
| 507 | (funcall get-next-frame) | 515 | (funcall get-next-frame) |
diff --git a/src/print.c b/src/print.c index 8c4bb24555e..d8acf838749 100644 --- a/src/print.c +++ b/src/print.c | |||
| @@ -640,7 +640,7 @@ is used instead. */) | |||
| 640 | return object; | 640 | return object; |
| 641 | } | 641 | } |
| 642 | 642 | ||
| 643 | /* a buffer which is used to hold output being built by prin1-to-string */ | 643 | /* A buffer which is used to hold output being built by prin1-to-string. */ |
| 644 | Lisp_Object Vprin1_to_string_buffer; | 644 | Lisp_Object Vprin1_to_string_buffer; |
| 645 | 645 | ||
| 646 | DEFUN ("prin1-to-string", Fprin1_to_string, Sprin1_to_string, 1, 2, 0, | 646 | DEFUN ("prin1-to-string", Fprin1_to_string, Sprin1_to_string, 1, 2, 0, |
| @@ -1140,14 +1140,14 @@ print (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag) | |||
| 1140 | print_object (obj, printcharfun, escapeflag); | 1140 | print_object (obj, printcharfun, escapeflag); |
| 1141 | } | 1141 | } |
| 1142 | 1142 | ||
| 1143 | #define PRINT_CIRCLE_CANDIDATE_P(obj) \ | 1143 | #define PRINT_CIRCLE_CANDIDATE_P(obj) \ |
| 1144 | (STRINGP (obj) || CONSP (obj) \ | 1144 | (STRINGP (obj) || CONSP (obj) \ |
| 1145 | || (VECTORLIKEP (obj) \ | 1145 | || (VECTORLIKEP (obj) \ |
| 1146 | && (VECTORP (obj) || COMPILEDP (obj) \ | 1146 | && (VECTORP (obj) || COMPILEDP (obj) \ |
| 1147 | || CHAR_TABLE_P (obj) || SUB_CHAR_TABLE_P (obj) \ | 1147 | || CHAR_TABLE_P (obj) || SUB_CHAR_TABLE_P (obj) \ |
| 1148 | || HASH_TABLE_P (obj) || FONTP (obj))) \ | 1148 | || HASH_TABLE_P (obj) || FONTP (obj))) \ |
| 1149 | || (! NILP (Vprint_gensym) \ | 1149 | || (! NILP (Vprint_gensym) \ |
| 1150 | && SYMBOLP (obj) \ | 1150 | && SYMBOLP (obj) \ |
| 1151 | && !SYMBOL_INTERNED_P (obj))) | 1151 | && !SYMBOL_INTERNED_P (obj))) |
| 1152 | 1152 | ||
| 1153 | /* Construct Vprint_number_table according to the structure of OBJ. | 1153 | /* Construct Vprint_number_table according to the structure of OBJ. |
| @@ -1260,6 +1260,16 @@ print_preprocess (Lisp_Object obj) | |||
| 1260 | print_depth--; | 1260 | print_depth--; |
| 1261 | } | 1261 | } |
| 1262 | 1262 | ||
| 1263 | DEFUN ("print--preprocess", Fprint_preprocess, Sprint_preprocess, 1, 1, 0, | ||
| 1264 | doc: /* Extract sharing info from OBJECT needed to print it. | ||
| 1265 | Fills `print-number-table'. */) | ||
| 1266 | (Lisp_Object object) | ||
| 1267 | { | ||
| 1268 | print_number_index = 0; | ||
| 1269 | print_preprocess (object); | ||
| 1270 | return Qnil; | ||
| 1271 | } | ||
| 1272 | |||
| 1263 | static void | 1273 | static void |
| 1264 | print_preprocess_string (INTERVAL interval, Lisp_Object arg) | 1274 | print_preprocess_string (INTERVAL interval, Lisp_Object arg) |
| 1265 | { | 1275 | { |
| @@ -1537,7 +1547,8 @@ print_object (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag) | |||
| 1537 | 1547 | ||
| 1538 | size_byte = SBYTES (name); | 1548 | size_byte = SBYTES (name); |
| 1539 | 1549 | ||
| 1540 | if (! NILP (Vprint_gensym) && !SYMBOL_INTERNED_P (obj)) | 1550 | if (! NILP (Vprint_gensym) |
| 1551 | && !SYMBOL_INTERNED_IN_INITIAL_OBARRAY_P (obj)) | ||
| 1541 | print_c_string ("#:", printcharfun); | 1552 | print_c_string ("#:", printcharfun); |
| 1542 | else if (size_byte == 0) | 1553 | else if (size_byte == 0) |
| 1543 | { | 1554 | { |
| @@ -2344,6 +2355,7 @@ priorities. */); | |||
| 2344 | defsubr (&Sterpri); | 2355 | defsubr (&Sterpri); |
| 2345 | defsubr (&Swrite_char); | 2356 | defsubr (&Swrite_char); |
| 2346 | defsubr (&Sredirect_debugging_output); | 2357 | defsubr (&Sredirect_debugging_output); |
| 2358 | defsubr (&Sprint_preprocess); | ||
| 2347 | 2359 | ||
| 2348 | DEFSYM (Qprint_escape_newlines, "print-escape-newlines"); | 2360 | DEFSYM (Qprint_escape_newlines, "print-escape-newlines"); |
| 2349 | DEFSYM (Qprint_escape_multibyte, "print-escape-multibyte"); | 2361 | DEFSYM (Qprint_escape_multibyte, "print-escape-multibyte"); |
diff --git a/test/lisp/emacs-lisp/cl-print-tests.el b/test/lisp/emacs-lisp/cl-print-tests.el new file mode 100644 index 00000000000..cbc79b0e64a --- /dev/null +++ b/test/lisp/emacs-lisp/cl-print-tests.el | |||
| @@ -0,0 +1,40 @@ | |||
| 1 | ;;; cl-print-tests.el --- Test suite for the cl-print facility. -*- lexical-binding:t -*- | ||
| 2 | |||
| 3 | ;; Copyright (C) 2017 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; This file is part of GNU Emacs. | ||
| 6 | |||
| 7 | ;; GNU Emacs is free software: you can redistribute it and/or modify | ||
| 8 | ;; it under the terms of the GNU General Public License as published by | ||
| 9 | ;; the Free Software Foundation, either version 3 of the License, or | ||
| 10 | ;; (at your option) any later version. | ||
| 11 | |||
| 12 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 15 | ;; GNU General Public License for more details. | ||
| 16 | |||
| 17 | ;; You should have received a copy of the GNU General Public License | ||
| 18 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. | ||
| 19 | |||
| 20 | ;;; Commentary: | ||
| 21 | |||
| 22 | ;;; Code: | ||
| 23 | |||
| 24 | (require 'ert) | ||
| 25 | |||
| 26 | (cl-defstruct cl-print--test a b) | ||
| 27 | |||
| 28 | (ert-deftest cl-print-tests-1 () | ||
| 29 | "Test cl-print code." | ||
| 30 | (let ((x (make-cl-print--test :a 1 :b 2))) | ||
| 31 | (let ((print-circle nil)) | ||
| 32 | (should (equal (cl-prin1-to-string `((x . ,x) (y . ,x))) | ||
| 33 | "((x . #s(cl-print--test :a 1 :b 2)) (y . #s(cl-print--test :a 1 :b 2)))"))) | ||
| 34 | (let ((print-circle t)) | ||
| 35 | (should (equal (cl-prin1-to-string `((x . ,x) (y . ,x))) | ||
| 36 | "((x . #1=#s(cl-print--test :a 1 :b 2)) (y . #1#))"))) | ||
| 37 | (should (string-match "\\`#<compiled-function (x) .*>\\'" | ||
| 38 | (cl-prin1-to-string (symbol-function #'caar)))))) | ||
| 39 | |||
| 40 | ;;; cl-print-tests.el ends here. | ||