aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Monnier2024-10-25 22:26:06 -0400
committerStefan Monnier2024-10-25 22:26:06 -0400
commitd44b94a63d2d407fca5d5ec41fcb92d7b765972e (patch)
treef6e07c6c35ed7627e7d9880779c6e57ef3a04b6b
parent574e97575f4331f43fc079b3bfa6d74213bc2559 (diff)
downloademacs-d44b94a63d2d407fca5d5ec41fcb92d7b765972e.tar.gz
emacs-d44b94a63d2d407fca5d5ec41fcb92d7b765972e.zip
cond*: Add support for Pcase patterns
* lisp/emacs-lisp/cond-star.el (cond*): Adjust docstring. (match*): Prefer `_VAR` syntax. (cond*-convert-condition): Add support for `pcase*`. * doc/lispref/control.texi (cond* Macro): Document `pcase*`. * test/lisp/emacs-lisp/cond-star-tests.el: New file.
-rw-r--r--doc/lispref/control.texi11
-rw-r--r--etc/NEWS14
-rw-r--r--lisp/emacs-lisp/cond-star.el55
-rw-r--r--test/lisp/emacs-lisp/cond-star-tests.el53
4 files changed, 109 insertions, 24 deletions
diff --git a/doc/lispref/control.texi b/doc/lispref/control.texi
index b996a372e28..6ad8a779d17 100644
--- a/doc/lispref/control.texi
+++ b/doc/lispref/control.texi
@@ -1452,12 +1452,15 @@ of the clause. As a condition, it counts as true if the first binding's
1452value is non-@code{nil}. 1452value is non-@code{nil}.
1453 1453
1454@findex match* 1454@findex match*
1455@findex pcase*
1455@code{(match* @var{pattern} @var{datum})} means to match @var{datum} 1456@code{(match* @var{pattern} @var{datum})} means to match @var{datum}
1456against the specified @var{pattern}. The condition counts as true if 1457against the specified @var{pattern}. The condition counts as true if
1457@var{pattern} matches @var{datum}. The pattern can specify variables to 1458@var{pattern} matches @var{datum}. The pattern can specify variables to
1458bind to the parts of @var{datum} that they match. 1459bind to the parts of @var{datum} that they match.
1460@code{(pcase* @var{pattern} @var{datum})} works in the same way except it
1461uses the Pcase syntax for @var{pattern}.
1459 1462
1460Both @code{bind*} and @code{match*} normally bind their bindings over 1463@code{bind*}, @code{match*}, and @code{pcase*} normally bind their bindings over
1461the execution of the whole containing clause. However, if the clause is 1464the execution of the whole containing clause. However, if the clause is
1462written to specify ``non-exit'', the clause's bindings cover the whole 1465written to specify ``non-exit'', the clause's bindings cover the whole
1463rest of the @code{cond*}. 1466rest of the @code{cond*}.
@@ -1475,6 +1478,10 @@ next clause (if any). The bindings made in @var{condition} for the
1475@var{body} of the non-exit clause are passed along to the rest of the 1478@var{body} of the non-exit clause are passed along to the rest of the
1476clauses in this @code{cond*} construct. 1479clauses in this @code{cond*} construct.
1477 1480
1481Note: @code{pcase*} does not support @code{:non-exit}, and when used in
1482a non-exit clause, it follows the semantics of @code{pcase-let}, see
1483@ref{Destructuring with pcase Patterns}.
1484
1478@subheading Matching clauses 1485@subheading Matching clauses
1479 1486
1480A matching clause looks like @code{(match* @var{pattern} @var{datum})}. 1487A matching clause looks like @code{(match* @var{pattern} @var{datum})}.
@@ -1482,7 +1489,7 @@ It evaluates the expression @var{datum} and matches the pattern
1482@var{pattern} (which is not evaluated) against it. 1489@var{pattern} (which is not evaluated) against it.
1483 1490
1484@var{pattern} allows these kinds of patterns, and those that are lists 1491@var{pattern} allows these kinds of patterns, and those that are lists
1485often include other patters within them: 1492often include other patterns within them:
1486 1493
1487@table @code 1494@table @code
1488@item _ 1495@item _
diff --git a/etc/NEWS b/etc/NEWS
index 18b6678dce9..a00536607da 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -741,12 +741,14 @@ text "covered" by the overlay.
741 741
742+++ 742+++
743** New macro 'cond*'. 743** New macro 'cond*'.
744The new macro 'cond*' is an alternative to 'pcase'. Like 'pcase', it 744The new macro 'cond*' is an alternative to 'cond' and 'pcase'.
745can be used to define several clauses, each one with its own condition; 745Like them, it can be used to define several clauses, each one with its
746the first clause that matches will cause its body to be evaluated. 746own condition; the first clause that matches will cause its body to be
747'cond*' uses syntax that is different from that of 'pcase', which some 747evaluated.
748users might find less cryptic. See the Info node "(elisp) cond* Macro" 748'cond*' can use Pcase's pattern matching syntax and also provides
749for details. 749another pattern matching syntax that is different from that of 'pcase',
750which some users might find less cryptic.
751See the Info node "(elisp) cond* Macro" for details.
750 752
751--- 753---
752** New function 'shell-command-do-open'. 754** New function 'shell-command-do-open'.
diff --git a/lisp/emacs-lisp/cond-star.el b/lisp/emacs-lisp/cond-star.el
index 9495ad96a6c..4cd8b9fd0fc 100644
--- a/lisp/emacs-lisp/cond-star.el
+++ b/lisp/emacs-lisp/cond-star.el
@@ -31,10 +31,10 @@
31;; and, or, if, progn, let, let*, setq. 31;; and, or, if, progn, let, let*, setq.
32;; For regexp matching only, it can call string-match and match-string. 32;; For regexp matching only, it can call string-match and match-string.
33 33
34;;; ??? If a clause starts with a keyword, 34;; ??? If a clause starts with a keyword,
35;;; should the element after the keyword be treated in the usual way 35;; should the element after the keyword be treated in the usual way
36;;; as a pattern? Currently `cond*-non-exit-clause-substance' explicitly 36;; as a pattern? Currently `cond*-non-exit-clause-substance' explicitly
37;;; prevents that by adding t at the front of its value. 37;; prevents that by adding t at the front of its value.
38 38
39;;; Code: 39;;; Code:
40 40
@@ -44,15 +44,20 @@ A `cond*' construct is a series of clauses, and a clause
44normally has the form (CONDITION BODY...). 44normally has the form (CONDITION BODY...).
45 45
46CONDITION can be a Lisp expression, as in `cond'. 46CONDITION can be a Lisp expression, as in `cond'.
47Or it can be `(bind* BINDINGS...)' or `(match* PATTERN DATUM)'. 47Or it can be one of `(pcase* PATTERN DATUM)',
48`(bind* BINDINGS...)', or `(match* PATTERN DATUM)',
49
50`(pcase* PATTERN DATUM)' means to match DATUM against the
51pattern PATTERN, using the same pattern syntax as `pcase'.
52The condition counts as true if PATTERN matches DATUM.
48 53
49`(bind* BINDINGS...)' means to bind BINDINGS (as if they were in `let*') 54`(bind* BINDINGS...)' means to bind BINDINGS (as if they were in `let*')
50for the body of the clause. As a condition, it counts as true 55for the body of the clause. As a condition, it counts as true
51if the first binding's value is non-nil. All the bindings are made 56if the first binding's value is non-nil. All the bindings are made
52unconditionally for whatever scope they cover. 57unconditionally for whatever scope they cover.
53 58
54`(match* PATTERN DATUM)' means to match DATUM against the pattern PATTERN 59`(match* PATTERN DATUM)' is an alternative to `pcase*' that uses another
55The condition counts as true if PATTERN matches DATUM. 60syntax for its patterns, see `match*'.
56 61
57When a clause's condition is true, and it exits the `cond*' 62When a clause's condition is true, and it exits the `cond*'
58or is the last clause, the value of the last expression 63or is the last clause, the value of the last expression
@@ -70,7 +75,7 @@ are passed along to the rest of the clauses in this `cond*' construct.
70\\[match*\\] for documentation of the patterns for use in `match*'." 75\\[match*\\] for documentation of the patterns for use in `match*'."
71 (cond*-convert clauses)) 76 (cond*-convert clauses))
72 77
73(defmacro match* (pattern datum) 78(defmacro match* (pattern _datum)
74 "This specifies matching DATUM against PATTERN. 79 "This specifies matching DATUM against PATTERN.
75It is not really a Lisp function, and it is meaningful 80It is not really a Lisp function, and it is meaningful
76only in the CONDITION of a `cond*' clause. 81only in the CONDITION of a `cond*' clause.
@@ -133,7 +138,7 @@ ATOM (meaning any other kind of non-list not described above)
133\(constrain SYMBOL EXP) 138\(constrain SYMBOL EXP)
134 matches datum if the form EXP is true. 139 matches datum if the form EXP is true.
135 EXP can refer to symbols bound earlier in the pattern." 140 EXP can refer to symbols bound earlier in the pattern."
136 (ignore datum) 141 ;; FIXME: `byte-compile-warn-x' is not necessarily defined here.
137 (byte-compile-warn-x pattern "`match*' used other than as a `cond*' condition")) 142 (byte-compile-warn-x pattern "`match*' used other than as a `cond*' condition"))
138 143
139(defun cond*-non-exit-clause-p (clause) 144(defun cond*-non-exit-clause-p (clause)
@@ -245,8 +250,8 @@ This is used for conditional exit clauses."
245 ;; Then always go on to run the UNCONDIT-CLAUSES. 250 ;; Then always go on to run the UNCONDIT-CLAUSES.
246 (if true-exps 251 (if true-exps
247 `(let ((,init-gensym ,first-value)) 252 `(let ((,init-gensym ,first-value))
248;;; ??? Should we make the bindings a second time for the UNCONDIT-CLAUSES. 253;;; ??? Should we make the bindings a second time for the UNCONDIT-CLAUSES.
249;;; as the doc string says, for uniformity with match*? 254;;; as the doc string says, for uniformity with match*?
250 (let* ,mod-bindings 255 (let* ,mod-bindings
251 (when ,init-gensym 256 (when ,init-gensym
252 . ,true-exps) 257 . ,true-exps)
@@ -262,6 +267,24 @@ This is used for conditional exit clauses."
262 (let* ,mod-bindings 267 (let* ,mod-bindings
263 (when ,init-gensym 268 (when ,init-gensym
264 . ,true-exps))))))) 269 . ,true-exps)))))))
270 ((eq pat-type 'pcase*)
271 (if true-exps
272 (progn
273 (when uncondit-clauses
274 ;; FIXME: This happens in cases like
275 ;; (cond* ((match* `(,x . ,y) EXP) THEN :non-exit)
276 ;; (t ELSE))
277 ;; where ELSE is supposed to run after THEN also (and
278 ;; with access to `x' and `y').
279 (error ":non-exit not supported with `pcase*'"))
280 (cl-assert (or (null iffalse) rest))
281 `(pcase ,(nth 2 condition)
282 (,(nth 1 condition) ,@true-exps)
283 (_ ,iffalse)))
284 (cl-assert (null iffalse))
285 (cl-assert (null rest))
286 `(pcase-let ((,(nth 1 condition) ,(nth 2 condition)))
287 (cond* . ,uncondit-clauses))))
265 ((eq pat-type 'match*) 288 ((eq pat-type 'match*)
266 (cond*-match condition true-exps uncondit-clauses iffalse)) 289 (cond*-match condition true-exps uncondit-clauses iffalse))
267 (t 290 (t
@@ -369,11 +392,11 @@ as in `cond*-condition'."
369 ;; because they are all gensyms anyway. 392 ;; because they are all gensyms anyway.
370 (if (cdr backtrack-aliases) 393 (if (cdr backtrack-aliases)
371 (setq expression 394 (setq expression
372 `(let ,(mapcar 'cdr (cdr backtrack-aliases)) 395 `(let ,(mapcar #'cdr (cdr backtrack-aliases))
373 ,expression))) 396 ,expression)))
374 (if retrieve-value-swap-outs 397 (if retrieve-value-swap-outs
375 (setq expression 398 (setq expression
376 `(let ,(mapcar 'cadr retrieve-value-swap-outs) 399 `(let ,(mapcar #'cadr retrieve-value-swap-outs)
377 ,expression))) 400 ,expression)))
378 ;; If we used a gensym, wrap on code to bind it. 401 ;; If we used a gensym, wrap on code to bind it.
379 (if gensym 402 (if gensym
@@ -397,8 +420,8 @@ This is used for the bindings specified explicitly in match* patterns."
397 420
398(defvar cond*-debug-pattern nil) 421(defvar cond*-debug-pattern nil)
399 422
400;;; ??? Structure type patterns not implemented yet. 423;; ??? Structure type patterns not implemented yet.
401;;; ??? Probably should optimize the `nth' calls in handling `list'. 424;; ??? Probably should optimize the `nth' calls in handling `list'.
402 425
403(defun cond*-subpat (subpat cdr-ignore bindings inside-or backtrack-aliases data) 426(defun cond*-subpat (subpat cdr-ignore bindings inside-or backtrack-aliases data)
404 "Generate code to match the subpattern within `match*'. 427 "Generate code to match the subpattern within `match*'.
@@ -486,7 +509,7 @@ whether SUBPAT (as well as the subpatterns that contain/precede it) matches,"
486 (unless (symbolp elt) 509 (unless (symbolp elt)
487 (byte-compile-warn-x vars "Non-symbol %s given as name for matched substring" elt))) 510 (byte-compile-warn-x vars "Non-symbol %s given as name for matched substring" elt)))
488 ;; Bind these variables to nil, before the pattern. 511 ;; Bind these variables to nil, before the pattern.
489 (setq bindings (nconc (mapcar 'list vars) bindings)) 512 (setq bindings (nconc (mapcar #'list vars) bindings))
490 ;; Make the expressions to set the variables. 513 ;; Make the expressions to set the variables.
491 (setq setqs (mapcar 514 (setq setqs (mapcar
492 (lambda (var) 515 (lambda (var)
diff --git a/test/lisp/emacs-lisp/cond-star-tests.el b/test/lisp/emacs-lisp/cond-star-tests.el
new file mode 100644
index 00000000000..7cf0a99f8db
--- /dev/null
+++ b/test/lisp/emacs-lisp/cond-star-tests.el
@@ -0,0 +1,53 @@
1;;; cond-star-tests.el --- tests for emacs-lisp/cond-star.el -*- lexical-binding:t -*-
2
3;; Copyright (C) 2024 Free Software Foundation, Inc.
4
5;; This file is part of GNU Emacs.
6
7;; GNU Emacs is free software: you can redistribute it and/or modify
8;; it under the terms of the GNU General Public License as published by
9;; the Free Software Foundation, either version 3 of the License, or
10;; (at your option) any later version.
11
12;; GNU Emacs is distributed in the hope that it will be useful,
13;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15;; GNU General Public License for more details.
16
17;; You should have received a copy of the GNU General Public License
18;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
19
20;;; Commentary:
21
22;;; Code:
23
24(require 'cond-star)
25(require 'ert)
26
27(ert-deftest cond-star-test-1 ()
28 (should (equal (cond*
29 ((pcase* `(,x . ,y) (cons 5 4)) (list x y))
30 (t 6))
31 '(5 4)))
32 (should (equal (cond*
33 ((pcase* `(,x . ,y) nil) (list x y))
34 (t 6))
35 6))
36 ;; FIXME: Not supported.
37 ;; (let* ((z nil)
38 ;; (res (cond*
39 ;; ((pcase* `(,x . ,y) (cons 5 4)) (setq z 6) :non-exit)
40 ;; (t `(,x ,y ,z)))))
41 ;; (should (equal res '(5 4 6))))
42 (should (equal (cond*
43 ((pcase* `(,x . ,y) (cons 5 4)))
44 (t (list x y)))
45 '(5 4)))
46 (should (equal (cond*
47 ((pcase* `(,x . ,y) nil))
48 (t (list x y)))
49 '(nil nil)))
50 )
51
52
53;;; cond-star-tests.el ends here