aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Monnier2012-06-10 20:33:33 -0400
committerStefan Monnier2012-06-10 20:33:33 -0400
commit82ad98e37d6b8ee164446b5229583a3064d58fa7 (patch)
tree27ae1d553577a0eea96fccf23e3ce33aece91f84
parentcef5bb19dce668ccd99c9ce74b17c717e2c986b9 (diff)
downloademacs-82ad98e37d6b8ee164446b5229583a3064d58fa7.tar.gz
emacs-82ad98e37d6b8ee164446b5229583a3064d58fa7.zip
* lisp/emacs-lisp/pcase.el (pcase--let*): New function.
(pcase-let*): Use it. Use pcase--memoize to avoid repeated expansions. (pcase--expand): Use macroexp-let².
-rw-r--r--lisp/ChangeLog6
-rw-r--r--lisp/emacs-lisp/pcase.el162
2 files changed, 96 insertions, 72 deletions
diff --git a/lisp/ChangeLog b/lisp/ChangeLog
index f914fcf6f6c..606e2430a81 100644
--- a/lisp/ChangeLog
+++ b/lisp/ChangeLog
@@ -1,3 +1,9 @@
12012-06-11 Stefan Monnier <monnier@iro.umontreal.ca>
2
3 * emacs-lisp/pcase.el (pcase--let*): New function.
4 (pcase-let*): Use it. Use pcase--memoize to avoid repeated expansions.
5 (pcase--expand): Use macroexp-let².
6
12012-06-10 Stefan Monnier <monnier@iro.umontreal.ca> 72012-06-10 Stefan Monnier <monnier@iro.umontreal.ca>
2 8
3 * emacs-lisp/timer.el, emacs-lisp/syntax.el, emacs-lisp/smie.el: 9 * emacs-lisp/timer.el, emacs-lisp/syntax.el, emacs-lisp/smie.el:
diff --git a/lisp/emacs-lisp/pcase.el b/lisp/emacs-lisp/pcase.el
index 3c9e82a823e..61c3aef5b21 100644
--- a/lisp/emacs-lisp/pcase.el
+++ b/lisp/emacs-lisp/pcase.el
@@ -61,6 +61,8 @@
61;; memoize previous macro expansions to try and avoid recomputing them 61;; memoize previous macro expansions to try and avoid recomputing them
62;; over and over again. 62;; over and over again.
63(defconst pcase--memoize (make-hash-table :weakness 'key :test 'eq)) 63(defconst pcase--memoize (make-hash-table :weakness 'key :test 'eq))
64;; (defconst pcase--memoize-1 (make-hash-table :test 'eq))
65;; (defconst pcase--memoize-2 (make-hash-table :weakness 'key :test 'equal))
64 66
65(defconst pcase--dontcare-upats '(t _ dontcare)) 67(defconst pcase--dontcare-upats '(t _ dontcare))
66 68
@@ -107,31 +109,49 @@ like `(,a . ,(pred (< a))) or, with more checks:
107 (if (and (equal exp (car data)) (equal cases (cadr data))) 109 (if (and (equal exp (car data)) (equal cases (cadr data)))
108 ;; We have the right expansion. 110 ;; We have the right expansion.
109 (cddr data) 111 (cddr data)
112 ;; (when (gethash (car cases) pcase--memoize-1)
113 ;; (message "pcase-memoize failed because of weak key!!"))
114 ;; (when (gethash (car cases) pcase--memoize-2)
115 ;; (message "pcase-memoize failed because of eq test on %S"
116 ;; (car cases)))
110 (when data 117 (when data
111 (message "pcase-memoize: equal first branch, yet different")) 118 (message "pcase-memoize: equal first branch, yet different"))
112 (let ((expansion (pcase--expand exp cases))) 119 (let ((expansion (pcase--expand exp cases)))
113 (puthash (car cases) (cons exp (cons cases expansion)) pcase--memoize) 120 (puthash (car cases) `(,exp ,cases ,@expansion) pcase--memoize)
121 ;; (puthash (car cases) `(,exp ,cases ,@expansion) pcase--memoize-1)
122 ;; (puthash (car cases) `(,exp ,cases ,@expansion) pcase--memoize-2)
114 expansion)))) 123 expansion))))
115 124
125(defun pcase--let* (bindings body)
126 (cond
127 ((null bindings) (macroexp-progn body))
128 ((pcase--trivial-upat-p (caar bindings))
129 (macroexp-let* `(,(car bindings)) (pcase--let* (cdr bindings) body)))
130 (t
131 (let ((binding (pop bindings)))
132 (pcase--expand
133 (cadr binding)
134 `((,(car binding) ,(pcase--let* bindings body))
135 ;; We can either signal an error here, or just use `dontcare' which
136 ;; generates more efficient code. In practice, if we use `dontcare'
137 ;; we will still often get an error and the few cases where we don't
138 ;; do not matter that much, so it's a better choice.
139 (dontcare nil)))))))
140
116;;;###autoload 141;;;###autoload
117(defmacro pcase-let* (bindings &rest body) 142(defmacro pcase-let* (bindings &rest body)
118 "Like `let*' but where you can use `pcase' patterns for bindings. 143 "Like `let*' but where you can use `pcase' patterns for bindings.
119BODY should be an expression, and BINDINGS should be a list of bindings 144BODY should be an expression, and BINDINGS should be a list of bindings
120of the form (UPAT EXP)." 145of the form (UPAT EXP)."
121 (declare (indent 1) 146 (declare (indent 1)
122 (debug ((&rest &or (sexp &optional form) symbolp) body))) 147 (debug ((&rest (sexp &optional form)) body)))
123 (cond 148 (let ((cached (gethash bindings pcase--memoize)))
124 ((null bindings) (if (> (length body) 1) `(progn ,@body) (car body))) 149 ;; cached = (BODY . EXPANSION)
125 ((pcase--trivial-upat-p (caar bindings)) 150 (if (equal (car cached) body)
126 `(let (,(car bindings)) (pcase-let* ,(cdr bindings) ,@body))) 151 (cdr cached)
127 (t 152 (let ((expansion (pcase--let* bindings body)))
128 `(pcase ,(cadr (car bindings)) 153 (puthash bindings (cons body expansion) pcase--memoize)
129 (,(caar bindings) (pcase-let* ,(cdr bindings) ,@body)) 154 expansion))))
130 ;; We can either signal an error here, or just use `dontcare' which
131 ;; generates more efficient code. In practice, if we use `dontcare' we
132 ;; will still often get an error and the few cases where we don't do not
133 ;; matter that much, so it's a better choice.
134 (dontcare nil)))))
135 155
136;;;###autoload 156;;;###autoload
137(defmacro pcase-let (bindings &rest body) 157(defmacro pcase-let (bindings &rest body)
@@ -169,64 +189,62 @@ of the form (UPAT EXP)."
169(defun pcase--expand (exp cases) 189(defun pcase--expand (exp cases)
170 ;; (message "pid=%S (pcase--expand %S ...hash=%S)" 190 ;; (message "pid=%S (pcase--expand %S ...hash=%S)"
171 ;; (emacs-pid) exp (sxhash cases)) 191 ;; (emacs-pid) exp (sxhash cases))
172 (let* ((defs (if (symbolp exp) '() 192 (macroexp-let² macroexp-copyable-p val exp
173 (let ((sym (make-symbol "x"))) 193 (let* ((defs ())
174 (prog1 `((,sym ,exp)) (setq exp sym))))) 194 (seen '())
175 (seen '()) 195 (codegen
176 (codegen 196 (lambda (code vars)
177 (lambda (code vars) 197 (let ((prev (assq code seen)))
178 (let ((prev (assq code seen))) 198 (if (not prev)
179 (if (not prev) 199 (let ((res (pcase-codegen code vars)))
180 (let ((res (pcase-codegen code vars))) 200 (push (list code vars res) seen)
181 (push (list code vars res) seen) 201 res)
182 res) 202 ;; Since we use a tree-based pattern matching
183 ;; Since we use a tree-based pattern matching 203 ;; technique, the leaves (the places that contain the
184 ;; technique, the leaves (the places that contain the 204 ;; code to run once a pattern is matched) can get
185 ;; code to run once a pattern is matched) can get 205 ;; copied a very large number of times, so to avoid
186 ;; copied a very large number of times, so to avoid 206 ;; code explosion, we need to keep track of how many
187 ;; code explosion, we need to keep track of how many 207 ;; times we've used each leaf and move it
188 ;; times we've used each leaf and move it 208 ;; to a separate function if that number is too high.
189 ;; to a separate function if that number is too high. 209 ;;
190 ;; 210 ;; We've already used this branch. So it is shared.
191 ;; We've already used this branch. So it is shared. 211 (let* ((code (car prev)) (cdrprev (cdr prev))
192 (let* ((code (car prev)) (cdrprev (cdr prev)) 212 (prevvars (car cdrprev)) (cddrprev (cdr cdrprev))
193 (prevvars (car cdrprev)) (cddrprev (cdr cdrprev)) 213 (res (car cddrprev)))
194 (res (car cddrprev))) 214 (unless (symbolp res)
195 (unless (symbolp res) 215 ;; This is the first repeat, so we have to move
196 ;; This is the first repeat, so we have to move 216 ;; the branch to a separate function.
197 ;; the branch to a separate function. 217 (let ((bsym
198 (let ((bsym 218 (make-symbol (format "pcase-%d" (length defs)))))
199 (make-symbol (format "pcase-%d" (length defs))))) 219 (push `(,bsym (lambda ,(mapcar #'car prevvars) ,@code)) defs)
200 (push `(,bsym (lambda ,(mapcar #'car prevvars) ,@code)) defs) 220 (setcar res 'funcall)
201 (setcar res 'funcall) 221 (setcdr res (cons bsym (mapcar #'cdr prevvars)))
202 (setcdr res (cons bsym (mapcar #'cdr prevvars))) 222 (setcar (cddr prev) bsym)
203 (setcar (cddr prev) bsym) 223 (setq res bsym)))
204 (setq res bsym))) 224 (setq vars (copy-sequence vars))
205 (setq vars (copy-sequence vars)) 225 (let ((args (mapcar (lambda (pa)
206 (let ((args (mapcar (lambda (pa) 226 (let ((v (assq (car pa) vars)))
207 (let ((v (assq (car pa) vars))) 227 (setq vars (delq v vars))
208 (setq vars (delq v vars)) 228 (cdr v)))
209 (cdr v))) 229 prevvars)))
210 prevvars))) 230 ;; If some of `vars' were not found in `prevvars', that's
211 ;; If some of `vars' were not found in `prevvars', that's 231 ;; OK it just means those vars aren't present in all
212 ;; OK it just means those vars aren't present in all 232 ;; branches, so they can be used within the pattern
213 ;; branches, so they can be used within the pattern 233 ;; (e.g. by a `guard/let/pred') but not in the branch.
214 ;; (e.g. by a `guard/let/pred') but not in the branch. 234 ;; FIXME: But if some of `prevvars' are not in `vars' we
215 ;; FIXME: But if some of `prevvars' are not in `vars' we 235 ;; should remove them from `prevvars'!
216 ;; should remove them from `prevvars'! 236 `(funcall ,res ,@args)))))))
217 `(funcall ,res ,@args))))))) 237 (main
218 (main 238 (pcase--u
219 (pcase--u 239 (mapcar (lambda (case)
220 (mapcar (lambda (case) 240 `((match ,val . ,(car case))
221 `((match ,exp . ,(car case)) 241 ,(apply-partially
222 ,(apply-partially 242 (if (pcase--small-branch-p (cdr case))
223 (if (pcase--small-branch-p (cdr case)) 243 ;; Don't bother sharing multiple
224 ;; Don't bother sharing multiple 244 ;; occurrences of this leaf since it's small.
225 ;; occurrences of this leaf since it's small. 245 #'pcase-codegen codegen)
226 #'pcase-codegen codegen) 246 (cdr case))))
227 (cdr case)))) 247 cases))))
228 cases))))
229 (if (null defs) main
230 (macroexp-let* defs main)))) 248 (macroexp-let* defs main))))
231 249
232(defun pcase-codegen (code vars) 250(defun pcase-codegen (code vars)