aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Monnier2017-02-23 21:06:54 -0500
committerStefan Monnier2017-02-23 21:06:54 -0500
commit407e650413c0296f5873a1399c2306b25f81f310 (patch)
tree7ef40c77b1a38cf127c07cf4662497b8170a658b
parentf6d2ba74f80b9a055a3d8072d49475aec45c2dbe (diff)
downloademacs-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.el196
-rw-r--r--lisp/emacs-lisp/nadvice.el18
-rw-r--r--src/print.c32
-rw-r--r--test/lisp/emacs-lisp/cl-print-tests.el40
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.
44You can add methods to it to customize the output.
45But if you just want to print something, don't call this directly:
46call 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. */
644Lisp_Object Vprin1_to_string_buffer; 644Lisp_Object Vprin1_to_string_buffer;
645 645
646DEFUN ("prin1-to-string", Fprin1_to_string, Sprin1_to_string, 1, 2, 0, 646DEFUN ("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
1263DEFUN ("print--preprocess", Fprint_preprocess, Sprint_preprocess, 1, 1, 0,
1264 doc: /* Extract sharing info from OBJECT needed to print it.
1265Fills `print-number-table'. */)
1266 (Lisp_Object object)
1267{
1268 print_number_index = 0;
1269 print_preprocess (object);
1270 return Qnil;
1271}
1272
1263static void 1273static void
1264print_preprocess_string (INTERVAL interval, Lisp_Object arg) 1274print_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.