diff options
| author | Stefan Monnier | 2014-09-22 14:05:22 -0400 |
|---|---|---|
| committer | Stefan Monnier | 2014-09-22 14:05:22 -0400 |
| commit | 2b968ea662e3dfdf3cd125a8d236220b938cb6ab (patch) | |
| tree | cacd9bb823b66bd8d8fe2877237b6033e11e6e30 | |
| parent | 7fbd780a0013b09c294625e4985f7000af55a5c6 (diff) | |
| download | emacs-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/ChangeLog | 5 | ||||
| -rw-r--r-- | lisp/emacs-lisp/pcase.el | 96 | ||||
| -rw-r--r-- | test/automated/pcase-tests.el | 2 |
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 @@ | |||
| 1 | 2014-09-22 Stefan Monnier <monnier@iro.umontreal.ca> | 1 | 2014-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. |
| 111 | If a SYMBOL is used twice in the same pattern (i.e. the pattern is | 111 | If 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 | ||
| 114 | FUN can be either of the form (lambda ARGS BODY) or a symbol. | ||
| 115 | It has to obey the rule that if (FUN X) returns V then calling it again will | ||
| 116 | return the same V again (so that multiple (FUN X) can be consolidated). | ||
| 117 | |||
| 118 | QPatterns can take the following forms: | 114 | QPatterns 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 | ||
| 126 | PRED can take the form | 122 | FUN 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. |
| 130 | A PRED of the form FUNCTION is equivalent to one of the form (FUNCTION). | 126 | So a FUN of the form SYMBOL is equivalent to one of the form (FUN). |
| 131 | PRED patterns can refer to variables bound earlier in the pattern. | 127 | FUN can refer to variables bound earlier in the pattern. |
| 128 | FUN is assumed to be pure, i.e. it can be dropped if its result is not used, | ||
| 129 | and two identical calls can be merged into one. | ||
| 132 | E.g. you can match pairs where the cdr is larger than the car with a pattern | 130 | E.g. you can match pairs where the cdr is larger than the car with a pattern |
| 133 | like `(,a . ,(pred (< a))) or, with more checks: | 131 | like `(,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 |