aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Monnier2014-12-01 09:45:15 -0500
committerStefan Monnier2014-12-01 09:45:15 -0500
commitf0e8c1eac226641ea8acab9e0f47ce3541803f0d (patch)
tree88af1f8b3ecf2c3e576cb5db3ff8957fb234d77c
parent578d91ac509a9856cf854bea75b6328cf40d1d03 (diff)
downloademacs-f0e8c1eac226641ea8acab9e0f47ce3541803f0d.tar.gz
emacs-f0e8c1eac226641ea8acab9e0f47ce3541803f0d.zip
New macro `define-inline'.
* lisp/emacs-lisp/inline.el: New file.
-rw-r--r--etc/NEWS2
-rw-r--r--lisp/ChangeLog16
-rw-r--r--lisp/emacs-lisp/autoload.el11
-rw-r--r--lisp/emacs-lisp/inline.el251
4 files changed, 268 insertions, 12 deletions
diff --git a/etc/NEWS b/etc/NEWS
index 9c34bb29903..6c636cf3095 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -373,6 +373,8 @@ Emacs-21.
373 373
374* Lisp Changes in Emacs 25.1 374* Lisp Changes in Emacs 25.1
375 375
376** define-inline provides a new way to define inlinable functions.
377
376** New function macroexpand-1 to perform a single step of macroexpansion. 378** New function macroexpand-1 to perform a single step of macroexpansion.
377 379
378** Some "x-*" were obsoleted: 380** Some "x-*" were obsoleted:
diff --git a/lisp/ChangeLog b/lisp/ChangeLog
index d6691f51d17..41b3ddbc3aa 100644
--- a/lisp/ChangeLog
+++ b/lisp/ChangeLog
@@ -1,9 +1,13 @@
12014-12-01 Stefan Monnier <monnier@iro.umontreal.ca>
2
3 * emacs-lisp/inline.el: New file.
4
12014-12-01 Eric S. Raymond <esr@snark.thyrsus.com> 52014-12-01 Eric S. Raymond <esr@snark.thyrsus.com>
2 6
3 * vc/vc.el, vc-hooks.el, and all backends: API simplification; 7 * vc/vc.el, vc-hooks.el, and all backends: API simplification;
4 vc-state-heuristic is no longer a public method, having been 8 vc-state-heuristic is no longer a public method, having been
5 removed where it is redundant, unnecessary, or known buggy. This 9 removed where it is redundant, unnecessary, or known buggy.
6 eliminated all backends except CVS. Eliminates bug#7850. 10 This eliminated all backends except CVS. Eliminates bug#7850.
7 11
8 * vc/vc-cvs.el, vc/vc-hooks.el, vc/vc-rcs.el, vc/vc-sccs.el: 12 * vc/vc-cvs.el, vc/vc-hooks.el, vc/vc-rcs.el, vc/vc-sccs.el:
9 Eliminate vc-mistrust-permissions. It was only relevant to the 13 Eliminate vc-mistrust-permissions. It was only relevant to the
@@ -41,8 +45,8 @@
41 45
422014-11-29 Fabián Ezequiel Gallina <fgallina@gnu.org> 462014-11-29 Fabián Ezequiel Gallina <fgallina@gnu.org>
43 47
44 * progmodes/python.el (python-shell-completion-setup-code): Use 48 * progmodes/python.el (python-shell-completion-setup-code):
45 __builtin__ module (or builtins in Python 3) and catch all errors 49 Use __builtin__ module (or builtins in Python 3) and catch all errors
46 when importing readline and rlcompleter. 50 when importing readline and rlcompleter.
47 51
482014-11-29 Stephen Berman <stephen.berman@gmx.net> 522014-11-29 Stephen Berman <stephen.berman@gmx.net>
@@ -94,8 +98,8 @@
94 98
952014-11-29 Eli Zaretskii <eliz@gnu.org> 992014-11-29 Eli Zaretskii <eliz@gnu.org>
96 100
97 * vc/vc-git.el (vc-git-command, vc-git--call): Bind 101 * vc/vc-git.el (vc-git-command, vc-git--call):
98 coding-system-for-read and coding-system-for-write to 102 Bind coding-system-for-read and coding-system-for-write to
99 vc-git-commits-coding-system. 103 vc-git-commits-coding-system.
100 (vc-git-previous-revision): Use "~1" instead of "^", since the 104 (vc-git-previous-revision): Use "~1" instead of "^", since the
101 latter is a special character for MS-Windows system shells. 105 latter is a special character for MS-Windows system shells.
diff --git a/lisp/emacs-lisp/autoload.el b/lisp/emacs-lisp/autoload.el
index 38956df66de..01f59704a39 100644
--- a/lisp/emacs-lisp/autoload.el
+++ b/lisp/emacs-lisp/autoload.el
@@ -120,7 +120,8 @@ expression, in which case we want to handle forms differently."
120 ;; Look for an interactive spec. 120 ;; Look for an interactive spec.
121 (interactive (pcase body 121 (interactive (pcase body
122 ((or `((interactive . ,_) . ,_) 122 ((or `((interactive . ,_) . ,_)
123 `(,_ (interactive . ,_) . ,_)) t)))) 123 `(,_ (interactive . ,_) . ,_))
124 t))))
124 ;; Add the usage form at the end where describe-function-1 125 ;; Add the usage form at the end where describe-function-1
125 ;; can recover it. 126 ;; can recover it.
126 (when (listp args) (setq doc (help-add-fundoc-usage doc args))) 127 (when (listp args) (setq doc (help-add-fundoc-usage doc args)))
@@ -140,11 +141,9 @@ expression, in which case we want to handle forms differently."
140 ;; For complex cases, try again on the macro-expansion. 141 ;; For complex cases, try again on the macro-expansion.
141 ((and (memq car '(easy-mmode-define-global-mode define-global-minor-mode 142 ((and (memq car '(easy-mmode-define-global-mode define-global-minor-mode
142 define-globalized-minor-mode defun defmacro 143 define-globalized-minor-mode defun defmacro
143 ;; FIXME: we'd want `defmacro*' here as well, so as 144 easy-mmode-define-minor-mode define-minor-mode
144 ;; to handle its `declare', but when autoload is run 145 define-inline cl-defun cl-defmacro))
145 ;; CL is not loaded so macroexpand doesn't know how 146 (macrop car)
146 ;; to expand it!
147 easy-mmode-define-minor-mode define-minor-mode))
148 (setq expand (let ((load-file-name file)) (macroexpand form))) 147 (setq expand (let ((load-file-name file)) (macroexpand form)))
149 (memq (car expand) '(progn prog1 defalias))) 148 (memq (car expand) '(progn prog1 defalias)))
150 (make-autoload expand file 'expansion)) ;Recurse on the expansion. 149 (make-autoload expand file 'expansion)) ;Recurse on the expansion.
diff --git a/lisp/emacs-lisp/inline.el b/lisp/emacs-lisp/inline.el
new file mode 100644
index 00000000000..3f11781aec0
--- /dev/null
+++ b/lisp/emacs-lisp/inline.el
@@ -0,0 +1,251 @@
1;;; inline.el --- Define functions by their inliner -*- lexical-binding:t; -*-
2
3;; Copyright (C) 2014 Stefan Monnier
4
5;; Author: Stefan Monnier <monnier@iro.umontreal.ca>
6
7;; This program 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;; This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
19
20;;; Commentary:
21
22;; This package provides the macro `define-inline' which lets you define
23;; functions by defining their (exhaustive) compiler macro.
24;;
25;; The idea is that instead of doing like defsubst and cl-defsubst (i.e. from
26;; the function's definition, guess the best way to inline the function),
27;; we go the other way around: the programmer provides the code that does the
28;; inlining (as a compiler-macro) and from that we derive the definition of the
29;; function itself. The idea originated in an attempt to clean up `cl-typep',
30;; whose function definition amounted to (eval (cl--make-type-test EXP TYPE)).
31;;
32;; The simplest use is for plain and simple inlinable functions. Rather than:
33;;
34;; (defmacro myaccessor (obj)
35;; (macroexp-let2 macroexp-copyable-p obj obj
36;; `(if (foop ,obj) (aref (cdr ,obj) 3) (aref ,obj 2))))
37;; Or
38;; (defsubst myaccessor (obj)
39;; (if (foop obj) (aref (cdr obj) 3) (aref obj 2)))
40;; Or
41;; (cl-defsubst myaccessor (obj)
42;; (if (foop obj) (aref (cdr obj) 3) (aref obj 2)))
43;;
44;; You'd do
45;;
46;; (define-inline myaccessor (obj)
47;; (inline-letevals (obj)
48;; (inline-quote (if (foop ,obj) (aref (cdr ,obj) 3) (aref ,obj 2)))))
49;;
50;; Other than verbosity, you get the best of all 3 above without their
51;; respective downsides:
52;; - defmacro: can't be passed to `mapcar' since it's not a function.
53;; - defsubst: not as efficient, and doesn't work as a `gv' place.
54;; - cl-defsubst: only works by accident, since it has latent bugs in its
55;; handling of variables and scopes which could bite you at any time.
56;; (e.g. try (cl-defsubst my-test1 (x) (let ((y 5)) (+ x y)))
57;; and then M-: (macroexpand-all '(my-test1 y)) RET)
58;; There is still one downside shared with the defmacro and cl-defsubst
59;; approach: when the function is inlined, the scoping rules (dynamic or
60;; lexical) will be inherited from the the call site.
61
62;; Of course, since define-inline defines a compiler macro, you can also do
63;; call-site optimizations, just like you can with `defmacro', but not with
64;; defsubst nor cl-defsubst.
65
66;;; Code:
67
68(require 'macroexp)
69
70(defmacro inline-quote (exp)
71 "Similar to backquote, but quotes code and only accepts , and not ,@."
72 (declare (debug t))
73 (error "inline-quote can only be used within define-inline"))
74
75(defmacro inline-const-p (exp)
76 "Return non-nil if the value of EXP is already known."
77 (declare (debug t))
78 (error "inline-const-p can only be used within define-inline"))
79
80(defmacro inline-const-val (exp)
81 "Return the value of EXP."
82 (declare (debug t))
83 (error "inline-const-val can only be used within define-inline"))
84
85(defmacro inline-error (format &rest args)
86 "Signal an error."
87 (declare (debug t))
88 (error "inline-error can only be used within define-inline"))
89
90(defmacro inline--leteval (_var-exp &rest _body)
91 (declare (indent 1) (debug (sexp &rest body)))
92 (error "inline-letevals can only be used within define-inline"))
93(defmacro inline--letlisteval (_list &rest _body)
94 (declare (indent 1) (debug (sexp &rest body)))
95 (error "inline-letevals can only be used within define-inline"))
96
97(defmacro inline-letevals (vars &rest body)
98 "Make sure the expressions in VARS are evaluated.
99VARS should be a list of elements of the form (VAR EXP) or just VAR, in case
100EXP is equal to VAR. The result is to evaluate EXP and bind the result to VAR.
101
102The tail of VARS can be either nil or a symbol VAR which should hold a list
103of arguments,in which case each argument is evaluated and the resulting
104new list is re-bound to VAR.
105
106After VARS is handled, BODY is evaluated in the new environment."
107 (declare (indent 1) (debug (sexp &rest body)))
108 (cond
109 ((consp vars)
110 `(inline--leteval ,(pop vars) (inline-letevals ,vars ,@body)))
111 (vars
112 `(inline--letlisteval ,vars ,@body))
113 (t (macroexp-progn body))))
114
115
116;;;###autoload
117(defmacro define-inline (name args &rest body)
118 ;; FIXME: How can this work with CL arglists?
119 (declare (indent defun) (debug defun) (doc-string 3))
120 (let ((doc (if (stringp (car-safe body)) (list (pop body))))
121 (declares (if (eq (car-safe (car-safe body)) 'declare) (pop body)))
122 (cm-name (intern (format "%s--inliner" name)))
123 (bodyexp (macroexp-progn body)))
124 ;; If the function is autoloaded then when we load the .el file, the
125 ;; `compiler-macro' property is already set (from loaddefs.el) and might
126 ;; hence be called during the macroexpand-all calls below (if the function
127 ;; is recursive).
128 ;; So we disable any pre-loaded compiler-macro setting to avoid this.
129 (function-put name 'compiler-macro nil)
130 `(progn
131 (defun ,name ,args
132 ,@doc
133 (declare (compiler-macro ,cm-name) ,@(cdr declares))
134 ,(macroexpand-all bodyexp
135 `((inline-quote . inline--dont-quote)
136 ;; (inline-\` . inline--dont-quote)
137 (inline--leteval . inline--dont-leteval)
138 (inline--letlisteval . inline--dont-letlisteval)
139 (inline-const-p . inline--alwaysconst-p)
140 (inline-const-val . inline--alwaysconst-val)
141 (inline-error . inline--error)
142 ,@macroexpand-all-environment)))
143 :autoload-end
144 (eval-and-compile
145 (defun ,cm-name ,(cons 'inline--form args)
146 (ignore inline--form) ;In case it's not used!
147 (catch 'inline--just-use
148 ,(macroexpand-all
149 bodyexp
150 `((inline-quote . inline--do-quote)
151 ;; (inline-\` . inline--do-quote)
152 (inline--leteval . inline--do-leteval)
153 (inline--letlisteval
154 . inline--do-letlisteval)
155 (inline-const-p . inline--testconst-p)
156 (inline-const-val . inline--getconst-val)
157 (inline-error . inline--warning)
158 ,@macroexpand-all-environment))))))))
159
160(defun inline--do-quote (exp)
161 (pcase exp
162 (`(,'\, ,e) e) ;Eval `e' now *and* later.
163 (`'(,'\, ,e) `(list 'quote ,e)) ;Only eval `e' now, not later.
164 (`#'(,'\, ,e) `(list 'function ,e)) ;Only eval `e' now, not later.
165 ((pred consp)
166 (let ((args ()))
167 (while (and (consp exp) (not (eq '\, (car exp))))
168 (push (inline--do-quote (pop exp)) args))
169 (setq args (nreverse args))
170 (if exp
171 `(backquote-list* ,@args ,(inline--do-quote exp))
172 `(list ,@args))))
173 (_ (macroexp-quote exp))))
174
175(defun inline--dont-quote (exp)
176 (pcase exp
177 (`(,'\, ,e) e)
178 (`'(,'\, ,e) e)
179 (`#'(,'\, ,e) e)
180 ((pred consp)
181 (let ((args ()))
182 (while (and (consp exp) (not (eq '\, (car exp))))
183 (push (inline--dont-quote (pop exp)) args))
184 (setq args (nreverse args))
185 (if exp
186 `(apply ,@args ,(inline--dont-quote exp))
187 args)))
188 (_ exp)))
189
190(defun inline--do-leteval (var-exp &rest body)
191 `(macroexp-let2 ,(if (symbolp var-exp) #'macroexp-copyable-p #'ignore)
192 ,(or (car-safe var-exp) var-exp)
193 ,(or (car (cdr-safe var-exp)) var-exp)
194 ,@body))
195
196(defun inline--dont-leteval (var-exp &rest body)
197 (if (symbolp var-exp)
198 (macroexp-progn body)
199 `(let (,var-exp) ,@body)))
200
201(defun inline--do-letlisteval (listvar &rest body)
202 ;; Here's a sample situation:
203 ;; (define-inline foo (arg &rest keys)
204 ;; (inline-letevals (arg . keys)
205 ;; <check-keys>))
206 ;; I.e. in <check-keys> we need `keys' to contain a list of
207 ;; macroexp-copyable-p expressions.
208 (let ((bsym (make-symbol "bindings")))
209 `(let* ((,bsym ())
210 (,listvar (mapcar (lambda (e)
211 (if (macroexp-copyable-p e) e
212 (let ((v (make-symbol "v")))
213 (push (list v e) ,bsym)
214 v)))
215 ,listvar)))
216 (macroexp-let* (nreverse ,bsym)
217 ,(macroexp-progn body)))))
218
219(defun inline--dont-letlisteval (_listvar &rest body)
220 (macroexp-progn body))
221
222(defun inline--testconst-p (exp)
223 `(macroexp-const-p ,exp))
224
225(defun inline--alwaysconst-p (_exp)
226 t)
227
228(defun inline--getconst-val (exp)
229 (macroexp-let2 macroexp-copyable-p exp exp
230 `(cond
231 ((not (macroexp-const-p ,exp))
232 (throw 'inline--just-use inline--form))
233 ((consp ,exp) (cadr ,exp))
234 (t ,exp))))
235
236(defun inline--alwaysconst-val (exp)
237 exp)
238
239(defun inline--error (&rest args)
240 `(error ,@args))
241
242(defun inline--warning (&rest _args)
243 `(throw 'inline--just-use
244 ;; FIXME: This would inf-loop by calling us right back when
245 ;; macroexpand-all recurses to expand inline--form.
246 ;; (macroexp--warn-and-return (format ,@args)
247 ;; inline--form)
248 inline--form))
249
250(provide 'inline)
251;;; inline.el ends here