mirror of
https://git.savannah.gnu.org/git/emacs.git
synced 2024-12-12 09:28:24 +00:00
* lisp/emacs-lisp/pcase.el (pcase-compile-patterns): New function (bug#47261)
Extracted from `pcase--expand`. (pcase--expand): Use it.
This commit is contained in:
parent
3af2cee64b
commit
937b6c18bd
3
etc/NEWS
3
etc/NEWS
@ -433,6 +433,9 @@ to nil. This was already sometimes the case, but it is now guaranteed.
|
|||||||
This is like '(pred (lambda (x) (not (FUN x))))' but results
|
This is like '(pred (lambda (x) (not (FUN x))))' but results
|
||||||
in better code.
|
in better code.
|
||||||
|
|
||||||
|
---
|
||||||
|
*** New function 'pcase-compile-patterns' to write other macros.
|
||||||
|
|
||||||
+++
|
+++
|
||||||
** profiler.el
|
** profiler.el
|
||||||
The results displayed by 'profiler-report' now have the usage figures
|
The results displayed by 'profiler-report' now have the usage figures
|
||||||
|
@ -207,6 +207,7 @@ If EXP fails to match any of the patterns in CASES, an error is signaled."
|
|||||||
(pcase--dontwarn-upats (cons x pcase--dontwarn-upats)))
|
(pcase--dontwarn-upats (cons x pcase--dontwarn-upats)))
|
||||||
(pcase--expand
|
(pcase--expand
|
||||||
;; FIXME: Could we add the FILE:LINE data in the error message?
|
;; FIXME: Could we add the FILE:LINE data in the error message?
|
||||||
|
;; FILE is available from `macroexp-file-name'.
|
||||||
exp (append cases `((,x (error "No clause matching `%S'" ,x)))))))
|
exp (append cases `((,x (error "No clause matching `%S'" ,x)))))))
|
||||||
|
|
||||||
;;;###autoload
|
;;;###autoload
|
||||||
@ -320,34 +321,46 @@ of the elements of LIST is performed as if by `pcase-let'.
|
|||||||
(defun pcase--trivial-upat-p (upat)
|
(defun pcase--trivial-upat-p (upat)
|
||||||
(and (symbolp upat) (not (memq upat pcase--dontcare-upats))))
|
(and (symbolp upat) (not (memq upat pcase--dontcare-upats))))
|
||||||
|
|
||||||
(defun pcase--expand (exp cases)
|
(defun pcase-compile-patterns (exp cases)
|
||||||
;; (message "pid=%S (pcase--expand %S ...hash=%S)"
|
"Compile the set of patterns in CASES.
|
||||||
;; (emacs-pid) exp (sxhash cases))
|
EXP is the expression that will be matched against the patterns.
|
||||||
|
CASES is a list of elements (PAT . CODEGEN)
|
||||||
|
where CODEGEN is a function that returns the code to use when
|
||||||
|
PAT matches. That code has to be in the form of a cons cell.
|
||||||
|
|
||||||
|
CODEGEN will be called with at least 2 arguments, VARVALS and COUNT.
|
||||||
|
VARVALS is a list of elements of the form (VAR VAL . RESERVED) where VAR
|
||||||
|
is a variable bound by the pattern and VAL is a duplicable expression
|
||||||
|
that returns the value this variable should be bound to.
|
||||||
|
If the pattern PAT uses `or', CODEGEN may be called multiple times,
|
||||||
|
in which case it may want to generate the code differently to avoid
|
||||||
|
a potential code explosion. For this reason the COUNT argument indicates
|
||||||
|
how many time this CODEGEN is called."
|
||||||
(macroexp-let2 macroexp-copyable-p val exp
|
(macroexp-let2 macroexp-copyable-p val exp
|
||||||
(let* ((defs ())
|
(let* ((seen '())
|
||||||
(seen '())
|
(phcounter 0)
|
||||||
(main
|
(main
|
||||||
(pcase--u
|
(pcase--u
|
||||||
(mapcar
|
(mapcar
|
||||||
(lambda (case)
|
(lambda (case)
|
||||||
`(,(pcase--match val (pcase--macroexpand (car case)))
|
`(,(pcase--match val (pcase--macroexpand (car case)))
|
||||||
,(lambda (vars)
|
,(lambda (vars)
|
||||||
(let ((prev (assq case seen))
|
(let ((prev (assq case seen)))
|
||||||
(code (cdr case)))
|
|
||||||
(unless prev
|
(unless prev
|
||||||
;; Keep track of the cases that are used.
|
;; Keep track of the cases that are used.
|
||||||
(push (setq prev (list case)) seen))
|
(push (setq prev (list case)) seen))
|
||||||
(if (member code '(nil (nil))) nil
|
;; Put a counter in the cdr just so that not
|
||||||
;; Put `code' in the cdr just so that not all
|
;; all branches look identical (to avoid things
|
||||||
;; branches look identical (to avoid things like
|
;; like `macroexp--if' optimizing them too
|
||||||
;; `macroexp--if' optimizing them too optimistically).
|
;; optimistically).
|
||||||
(let ((ph (list 'pcase--placeholder code)))
|
(let ((ph (cons 'pcase--placeholder
|
||||||
(setcdr prev (cons (cons vars ph) (cdr prev)))
|
(setq phcounter (1+ phcounter)))))
|
||||||
ph))))))
|
(setcdr prev (cons (cons vars ph) (cdr prev)))
|
||||||
|
ph)))))
|
||||||
cases))))
|
cases))))
|
||||||
;; Take care of the place holders now.
|
;; Take care of the place holders now.
|
||||||
(dolist (branch seen)
|
(dolist (branch seen)
|
||||||
(let ((code (cdar branch))
|
(let ((codegen (cdar branch))
|
||||||
(uses (cdr branch)))
|
(uses (cdr branch)))
|
||||||
;; Find all the vars that are in scope (the union of the
|
;; Find all the vars that are in scope (the union of the
|
||||||
;; vars provided in each use case).
|
;; vars provided in each use case).
|
||||||
@ -358,48 +371,74 @@ of the elements of LIST is performed as if by `pcase-let'.
|
|||||||
(if vi
|
(if vi
|
||||||
(if (cddr v) (setcdr vi 'used))
|
(if (cddr v) (setcdr vi 'used))
|
||||||
(push (cons (car v) (cddr v)) allvarinfo))))))
|
(push (cons (car v) (cddr v)) allvarinfo))))))
|
||||||
(allvars (mapcar #'car allvarinfo))
|
(allvars (mapcar #'car allvarinfo)))
|
||||||
(ignores (mapcar (lambda (vi) (when (cdr vi) `(ignore ,(car vi))))
|
(dolist (use uses)
|
||||||
allvarinfo)))
|
(let* ((vars (car use))
|
||||||
;; Since we use a tree-based pattern matching
|
(varvals
|
||||||
;; technique, the leaves (the places that contain the
|
(mapcar (lambda (v)
|
||||||
;; code to run once a pattern is matched) can get
|
`(,v ,(cadr (assq v vars))
|
||||||
;; copied a very large number of times, so to avoid
|
,(cdr (assq v allvarinfo))))
|
||||||
;; code explosion, we need to keep track of how many
|
allvars))
|
||||||
;; times we've used each leaf and move it
|
(placeholder (cdr use))
|
||||||
;; to a separate function if that number is too high.
|
(code (funcall codegen varvals (length uses))))
|
||||||
(if (or (null (cdr uses)) (pcase--small-branch-p code))
|
;; (cl-assert (eq (car placeholder) 'pcase--placeholder))
|
||||||
(dolist (use uses)
|
(setcar placeholder (car code))
|
||||||
(let ((vars (car use))
|
(setcdr placeholder (cdr code)))))))
|
||||||
(placeholder (cdr use)))
|
|
||||||
;; (cl-assert (eq (car placeholder) 'pcase--placeholder))
|
|
||||||
(setcar placeholder 'let)
|
|
||||||
(setcdr placeholder
|
|
||||||
`(,(mapcar (lambda (v) (list v (cadr (assq v vars))))
|
|
||||||
allvars)
|
|
||||||
;; Try and silence some of the most common
|
|
||||||
;; spurious "unused var" warnings.
|
|
||||||
,@ignores
|
|
||||||
,@code))))
|
|
||||||
;; Several occurrence of this non-small branch in the output.
|
|
||||||
(let ((bsym
|
|
||||||
(make-symbol (format "pcase-%d" (length defs)))))
|
|
||||||
(push `(,bsym (lambda ,allvars ,@ignores ,@code)) defs)
|
|
||||||
(dolist (use uses)
|
|
||||||
(let ((vars (car use))
|
|
||||||
(placeholder (cdr use)))
|
|
||||||
;; (cl-assert (eq (car placeholder) 'pcase--placeholder))
|
|
||||||
(setcar placeholder 'funcall)
|
|
||||||
(setcdr placeholder
|
|
||||||
`(,bsym
|
|
||||||
,@(mapcar (lambda (v) (cadr (assq v vars)))
|
|
||||||
allvars))))))))))
|
|
||||||
(dolist (case cases)
|
(dolist (case cases)
|
||||||
(unless (or (assq case seen)
|
(unless (or (assq case seen)
|
||||||
(memq (car case) pcase--dontwarn-upats))
|
(memq (car case) pcase--dontwarn-upats))
|
||||||
(message "pcase pattern %S shadowed by previous pcase pattern"
|
(setq main
|
||||||
(car case))))
|
(macroexp-warn-and-return
|
||||||
(macroexp-let* defs main))))
|
(format "pcase pattern %S shadowed by previous pcase pattern"
|
||||||
|
(car case))
|
||||||
|
main))))
|
||||||
|
main)))
|
||||||
|
|
||||||
|
(defun pcase--expand (exp cases)
|
||||||
|
;; (message "pid=%S (pcase--expand %S ...hash=%S)"
|
||||||
|
;; (emacs-pid) exp (sxhash cases))
|
||||||
|
(let* ((defs ())
|
||||||
|
(codegen
|
||||||
|
(lambda (code)
|
||||||
|
(if (member code '(nil (nil) ('nil)))
|
||||||
|
(lambda (&rest _) ''nil)
|
||||||
|
(let ((bsym ()))
|
||||||
|
(lambda (varvals count &rest _)
|
||||||
|
(let* ((ignored-vars
|
||||||
|
(delq nil (mapcar (lambda (vv) (if (nth 2 vv) (car vv)))
|
||||||
|
varvals)))
|
||||||
|
(ignores (if ignored-vars
|
||||||
|
`((ignore . ,ignored-vars)))))
|
||||||
|
;; Since we use a tree-based pattern matching
|
||||||
|
;; technique, the leaves (the places that contain the
|
||||||
|
;; code to run once a pattern is matched) can get
|
||||||
|
;; copied a very large number of times, so to avoid
|
||||||
|
;; code explosion, we need to keep track of how many
|
||||||
|
;; times we've used each leaf and move it
|
||||||
|
;; to a separate function if that number is too high.
|
||||||
|
(if (or (< count 2) (pcase--small-branch-p code))
|
||||||
|
`(let ,(mapcar (lambda (vv) (list (car vv) (cadr vv)))
|
||||||
|
varvals)
|
||||||
|
;; Try and silence some of the most common
|
||||||
|
;; spurious "unused var" warnings.
|
||||||
|
,@ignores
|
||||||
|
,@code)
|
||||||
|
;; Several occurrence of this non-small branch in
|
||||||
|
;; the output.
|
||||||
|
(unless bsym
|
||||||
|
(setq bsym (make-symbol
|
||||||
|
(format "pcase-%d" (length defs))))
|
||||||
|
(push `(,bsym (lambda ,(mapcar #'car varvals)
|
||||||
|
,@ignores ,@code))
|
||||||
|
defs))
|
||||||
|
`(funcall ,bsym ,@(mapcar #'cadr varvals)))))))))
|
||||||
|
(main
|
||||||
|
(pcase-compile-patterns
|
||||||
|
exp
|
||||||
|
(mapcar (lambda (case)
|
||||||
|
(cons (car case) (funcall codegen (cdr case))))
|
||||||
|
cases))))
|
||||||
|
(macroexp-let* defs main)))
|
||||||
|
|
||||||
(defun pcase--macroexpand (pat)
|
(defun pcase--macroexpand (pat)
|
||||||
"Expands all macro-patterns in PAT."
|
"Expands all macro-patterns in PAT."
|
||||||
|
Loading…
Reference in New Issue
Block a user