aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Monnier2014-09-22 14:05:22 -0400
committerStefan Monnier2014-09-22 14:05:22 -0400
commit2b968ea662e3dfdf3cd125a8d236220b938cb6ab (patch)
treecacd9bb823b66bd8d8fe2877237b6033e11e6e30
parent7fbd780a0013b09c294625e4985f7000af55a5c6 (diff)
downloademacs-2b968ea662e3dfdf3cd125a8d236220b938cb6ab.tar.gz
emacs-2b968ea662e3dfdf3cd125a8d236220b938cb6ab.zip
* lisp/emacs-lisp/pcase.el: Allow (F . ARGS) in `app' patterns.
(pcase--funcall, pcase--eval): New functions. (pcase--u1): Use them for guard, pred, let, and app. (\`): Use the new feature to generate better code for vector patterns.
-rw-r--r--lisp/ChangeLog5
-rw-r--r--lisp/emacs-lisp/pcase.el96
-rw-r--r--test/automated/pcase-tests.el2
3 files changed, 54 insertions, 49 deletions
diff --git a/lisp/ChangeLog b/lisp/ChangeLog
index 6f8178a9a4c..f1401b1b38a 100644
--- a/lisp/ChangeLog
+++ b/lisp/ChangeLog
@@ -1,5 +1,10 @@
12014-09-22 Stefan Monnier <monnier@iro.umontreal.ca> 12014-09-22 Stefan Monnier <monnier@iro.umontreal.ca>
2 2
3 * emacs-lisp/pcase.el: Allow (F . ARGS) in `app' patterns.
4 (pcase--funcall, pcase--eval): New functions.
5 (pcase--u1): Use them for guard, pred, let, and app.
6 (\`): Use the new feature to generate better code for vector patterns.
7
3 * emacs-lisp/pcase.el: Use pcase-defmacro to handle backquote. 8 * emacs-lisp/pcase.el: Use pcase-defmacro to handle backquote.
4 (pcase--upat): Remove. 9 (pcase--upat): Remove.
5 (pcase--macroexpand): Don't hardcode handling of `. 10 (pcase--macroexpand): Don't hardcode handling of `.
diff --git a/lisp/emacs-lisp/pcase.el b/lisp/emacs-lisp/pcase.el
index e17088ac9f2..ddcd4040f2b 100644
--- a/lisp/emacs-lisp/pcase.el
+++ b/lisp/emacs-lisp/pcase.el
@@ -104,17 +104,13 @@ UPatterns can take the following forms:
104 (and UPAT...) matches if all the patterns match. 104 (and UPAT...) matches if all the patterns match.
105 'VAL matches if the object is `equal' to VAL 105 'VAL matches if the object is `equal' to VAL
106 `QPAT matches if the QPattern QPAT matches. 106 `QPAT matches if the QPattern QPAT matches.
107 (pred PRED) matches if PRED applied to the object returns non-nil. 107 (pred FUN) matches if FUN applied to the object returns non-nil.
108 (guard BOOLEXP) matches if BOOLEXP evaluates to non-nil. 108 (guard BOOLEXP) matches if BOOLEXP evaluates to non-nil.
109 (let UPAT EXP) matches if EXP matches UPAT. 109 (let UPAT EXP) matches if EXP matches UPAT.
110 (app FUN UPAT) matches if FUN applied to the object matches UPAT. 110 (app FUN UPAT) matches if FUN applied to the object matches UPAT.
111If a SYMBOL is used twice in the same pattern (i.e. the pattern is 111If a SYMBOL is used twice in the same pattern (i.e. the pattern is
112\"non-linear\"), then the second occurrence is turned into an `eq'uality test. 112\"non-linear\"), then the second occurrence is turned into an `eq'uality test.
113 113
114FUN can be either of the form (lambda ARGS BODY) or a symbol.
115It has to obey the rule that if (FUN X) returns V then calling it again will
116return the same V again (so that multiple (FUN X) can be consolidated).
117
118QPatterns can take the following forms: 114QPatterns can take the following forms:
119 (QPAT1 . QPAT2) matches if QPAT1 matches the car and QPAT2 the cdr. 115 (QPAT1 . QPAT2) matches if QPAT1 matches the car and QPAT2 the cdr.
120 [QPAT1 QPAT2..QPATn] matches a vector of length n and QPAT1..QPATn match 116 [QPAT1 QPAT2..QPATn] matches a vector of length n and QPAT1..QPATn match
@@ -123,12 +119,14 @@ QPatterns can take the following forms:
123 STRING matches if the object is `equal' to STRING. 119 STRING matches if the object is `equal' to STRING.
124 ATOM matches if the object is `eq' to ATOM. 120 ATOM matches if the object is `eq' to ATOM.
125 121
126PRED can take the form 122FUN can take the form
127 FUNCTION in which case it gets called with one argument. 123 SYMBOL or (lambda ARGS BODY) in which case it's called with one argument.
128 (F ARG1 .. ARGn) in which case F gets called with an n+1'th argument 124 (F ARG1 .. ARGn) in which case F gets called with an n+1'th argument
129 which is the value being matched. 125 which is the value being matched.
130A PRED of the form FUNCTION is equivalent to one of the form (FUNCTION). 126So a FUN of the form SYMBOL is equivalent to one of the form (FUN).
131PRED patterns can refer to variables bound earlier in the pattern. 127FUN can refer to variables bound earlier in the pattern.
128FUN is assumed to be pure, i.e. it can be dropped if its result is not used,
129and two identical calls can be merged into one.
132E.g. you can match pairs where the cdr is larger than the car with a pattern 130E.g. you can match pairs where the cdr is larger than the car with a pattern
133like `(,a . ,(pred (< a))) or, with more checks: 131like `(,a . ,(pred (< a))) or, with more checks:
134`(,(and a (pred numberp)) . ,(and (pred numberp) (pred (< a))))" 132`(,(and a (pred numberp)) . ,(and (pred numberp) (pred (< a))))"
@@ -600,6 +598,40 @@ MATCH is the pattern that needs to be matched, of the form:
600 (declare (debug (sexp body))) 598 (declare (debug (sexp body)))
601 `(,fun ,arg2 ,arg1)) 599 `(,fun ,arg2 ,arg1))
602 600
601(defun pcase--funcall (fun arg vars)
602 "Build a function call to FUN with arg ARG."
603 (if (symbolp fun)
604 `(,fun ,arg)
605 (let* (;; `vs' is an upper bound on the vars we need.
606 (vs (pcase--fgrep (mapcar #'car vars) fun))
607 (env (mapcar (lambda (var)
608 (list var (cdr (assq var vars))))
609 vs))
610 (call (progn
611 (when (memq arg vs)
612 ;; `arg' is shadowed by `env'.
613 (let ((newsym (make-symbol "x")))
614 (push (list newsym arg) env)
615 (setq arg newsym)))
616 (if (functionp fun)
617 `(funcall #',fun ,arg)
618 `(,@fun ,arg)))))
619 (if (null vs)
620 call
621 ;; Let's not replace `vars' in `fun' since it's
622 ;; too difficult to do it right, instead just
623 ;; let-bind `vars' around `fun'.
624 `(let* ,env ,call)))))
625
626(defun pcase--eval (exp vars)
627 "Build an expression that will evaluate EXP."
628 (let* ((found (assq exp vars)))
629 (if found (cdr found)
630 (let* ((vs (pcase--fgrep (mapcar #'car vars) exp))
631 (env (mapcar (lambda (v) (list v (cdr (assq v vars))))
632 vs)))
633 (if env (macroexp-let* env exp) exp)))))
634
603;; It's very tempting to use `pcase' below, tho obviously, it'd create 635;; It's very tempting to use `pcase' below, tho obviously, it'd create
604;; bootstrapping problems. 636;; bootstrapping problems.
605(defun pcase--u1 (matches code vars rest) 637(defun pcase--u1 (matches code vars rest)
@@ -674,30 +706,9 @@ Otherwise, it defers to REST which is a list of branches of the form
674 sym (lambda (pat) (pcase--split-pred vars upat pat)) rest)) 706 sym (lambda (pat) (pcase--split-pred vars upat pat)) rest))
675 (then-rest (car splitrest)) 707 (then-rest (car splitrest))
676 (else-rest (cdr splitrest))) 708 (else-rest (cdr splitrest)))
677 (pcase--if (if (and (eq (car upat) 'pred) (symbolp (cadr upat))) 709 (pcase--if (if (eq (car upat) 'pred)
678 `(,(cadr upat) ,sym) 710 (pcase--funcall (cadr upat) sym vars)
679 (let* ((exp (cadr upat)) 711 (pcase--eval (cadr upat) vars))
680 ;; `vs' is an upper bound on the vars we need.
681 (vs (pcase--fgrep (mapcar #'car vars) exp))
682 (env (mapcar (lambda (var)
683 (list var (cdr (assq var vars))))
684 vs))
685 (call (if (eq 'guard (car upat))
686 exp
687 (when (memq sym vs)
688 ;; `sym' is shadowed by `env'.
689 (let ((newsym (make-symbol "x")))
690 (push (list newsym sym) env)
691 (setq sym newsym)))
692 (if (functionp exp)
693 `(funcall #',exp ,sym)
694 `(,@exp ,sym)))))
695 (if (null vs)
696 call
697 ;; Let's not replace `vars' in `exp' since it's
698 ;; too difficult to do it right, instead just
699 ;; let-bind `vars' around `exp'.
700 `(let* ,env ,call))))
701 (pcase--u1 matches code vars then-rest) 712 (pcase--u1 matches code vars then-rest)
702 (pcase--u else-rest)))) 713 (pcase--u else-rest))))
703 ((symbolp upat) 714 ((symbolp upat)
@@ -714,13 +725,7 @@ Otherwise, it defers to REST which is a list of branches of the form
714 ;; (cons (cons (nth 1 upat) (nth 2 upat)) vars) rest) 725 ;; (cons (cons (nth 1 upat) (nth 2 upat)) vars) rest)
715 (macroexp-let2 726 (macroexp-let2
716 macroexp-copyable-p sym 727 macroexp-copyable-p sym
717 (let* ((exp (nth 2 upat)) 728 (pcase--eval (nth 2 upat) vars)
718 (found (assq exp vars)))
719 (if found (cdr found)
720 (let* ((vs (pcase--fgrep (mapcar #'car vars) exp))
721 (env (mapcar (lambda (v) (list v (cdr (assq v vars))))
722 vs)))
723 (if env (macroexp-let* env exp) exp))))
724 (pcase--u1 (cons (pcase--match sym (nth 1 upat)) matches) 729 (pcase--u1 (cons (pcase--match sym (nth 1 upat)) matches)
725 code vars rest))) 730 code vars rest)))
726 ((eq (car-safe upat) 'app) 731 ((eq (car-safe upat) 'app)
@@ -737,14 +742,7 @@ Otherwise, it defers to REST which is a list of branches of the form
737 (if (not (get nsym 'pcase-used)) 742 (if (not (get nsym 'pcase-used))
738 body 743 body
739 (macroexp-let* 744 (macroexp-let*
740 `((,nsym 745 `((,nsym ,(pcase--funcall fun sym vars)))
741 ,(if (symbolp fun)
742 `(,fun ,sym)
743 (let* ((vs (pcase--fgrep (mapcar #'car vars) fun))
744 (env (mapcar (lambda (v) (list v (cdr (assq v vars))))
745 vs))
746 (call `(funcall #',fun ,sym)))
747 (if env (macroexp-let* env call) call)))))
748 body)))) 746 body))))
749 ((eq (car-safe upat) 'quote) 747 ((eq (car-safe upat) 'quote)
750 (pcase--mark-used sym) 748 (pcase--mark-used sym)
@@ -794,7 +792,7 @@ Otherwise, it defers to REST which is a list of branches of the form
794 (app length ,(length qpat)) 792 (app length ,(length qpat))
795 ,@(let ((upats nil)) 793 ,@(let ((upats nil))
796 (dotimes (i (length qpat)) 794 (dotimes (i (length qpat))
797 (push `(app (lambda (v) (aref v ,i)) ,(list '\` (aref qpat i))) 795 (push `(app (pcase--flip aref ,i) ,(list '\` (aref qpat i)))
798 upats)) 796 upats))
799 (nreverse upats)))) 797 (nreverse upats))))
800 ((consp qpat) 798 ((consp qpat)
diff --git a/test/automated/pcase-tests.el b/test/automated/pcase-tests.el
index 7e3c40235e6..ec0c3bc7fd5 100644
--- a/test/automated/pcase-tests.el
+++ b/test/automated/pcase-tests.el
@@ -58,6 +58,8 @@
58 (should-not (pcase-tests-grep 'memq exp)) 58 (should-not (pcase-tests-grep 'memq exp))
59 (should-not (pcase-tests-grep 'member exp)))) 59 (should-not (pcase-tests-grep 'member exp))))
60 60
61(ert-deftest pcase-tests-vectors ()
62 (should (equal (pcase [1 2] (`[,x] 1) (`[,x ,y] (+ x y))) 3)))
61 63
62;; Local Variables: 64;; Local Variables:
63;; no-byte-compile: t 65;; no-byte-compile: t