diff options
| author | Stefan Monnier | 2012-06-10 20:33:33 -0400 |
|---|---|---|
| committer | Stefan Monnier | 2012-06-10 20:33:33 -0400 |
| commit | 82ad98e37d6b8ee164446b5229583a3064d58fa7 (patch) | |
| tree | 27ae1d553577a0eea96fccf23e3ce33aece91f84 | |
| parent | cef5bb19dce668ccd99c9ce74b17c717e2c986b9 (diff) | |
| download | emacs-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/ChangeLog | 6 | ||||
| -rw-r--r-- | lisp/emacs-lisp/pcase.el | 162 |
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 @@ | |||
| 1 | 2012-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 | |||
| 1 | 2012-06-10 Stefan Monnier <monnier@iro.umontreal.ca> | 7 | 2012-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. |
| 119 | BODY should be an expression, and BINDINGS should be a list of bindings | 144 | BODY should be an expression, and BINDINGS should be a list of bindings |
| 120 | of the form (UPAT EXP)." | 145 | of 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) |