diff options
| author | Stefan Monnier | 2024-10-25 22:26:06 -0400 |
|---|---|---|
| committer | Stefan Monnier | 2024-10-25 22:26:06 -0400 |
| commit | d44b94a63d2d407fca5d5ec41fcb92d7b765972e (patch) | |
| tree | f6e07c6c35ed7627e7d9880779c6e57ef3a04b6b | |
| parent | 574e97575f4331f43fc079b3bfa6d74213bc2559 (diff) | |
| download | emacs-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.texi | 11 | ||||
| -rw-r--r-- | etc/NEWS | 14 | ||||
| -rw-r--r-- | lisp/emacs-lisp/cond-star.el | 55 | ||||
| -rw-r--r-- | test/lisp/emacs-lisp/cond-star-tests.el | 53 |
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 | |||
| 1452 | value is non-@code{nil}. | 1452 | value 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} |
| 1456 | against the specified @var{pattern}. The condition counts as true if | 1457 | against 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 |
| 1458 | bind to the parts of @var{datum} that they match. | 1459 | bind to the parts of @var{datum} that they match. |
| 1460 | @code{(pcase* @var{pattern} @var{datum})} works in the same way except it | ||
| 1461 | uses the Pcase syntax for @var{pattern}. | ||
| 1459 | 1462 | ||
| 1460 | Both @code{bind*} and @code{match*} normally bind their bindings over | 1463 | @code{bind*}, @code{match*}, and @code{pcase*} normally bind their bindings over |
| 1461 | the execution of the whole containing clause. However, if the clause is | 1464 | the execution of the whole containing clause. However, if the clause is |
| 1462 | written to specify ``non-exit'', the clause's bindings cover the whole | 1465 | written to specify ``non-exit'', the clause's bindings cover the whole |
| 1463 | rest of the @code{cond*}. | 1466 | rest 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 |
| 1476 | clauses in this @code{cond*} construct. | 1479 | clauses in this @code{cond*} construct. |
| 1477 | 1480 | ||
| 1481 | Note: @code{pcase*} does not support @code{:non-exit}, and when used in | ||
| 1482 | a 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 | ||
| 1480 | A matching clause looks like @code{(match* @var{pattern} @var{datum})}. | 1487 | A 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 |
| 1485 | often include other patters within them: | 1492 | often include other patterns within them: |
| 1486 | 1493 | ||
| 1487 | @table @code | 1494 | @table @code |
| 1488 | @item _ | 1495 | @item _ |
| @@ -741,12 +741,14 @@ text "covered" by the overlay. | |||
| 741 | 741 | ||
| 742 | +++ | 742 | +++ |
| 743 | ** New macro 'cond*'. | 743 | ** New macro 'cond*'. |
| 744 | The new macro 'cond*' is an alternative to 'pcase'. Like 'pcase', it | 744 | The new macro 'cond*' is an alternative to 'cond' and 'pcase'. |
| 745 | can be used to define several clauses, each one with its own condition; | 745 | Like them, it can be used to define several clauses, each one with its |
| 746 | the first clause that matches will cause its body to be evaluated. | 746 | own 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 | 747 | evaluated. |
| 748 | users might find less cryptic. See the Info node "(elisp) cond* Macro" | 748 | 'cond*' can use Pcase's pattern matching syntax and also provides |
| 749 | for details. | 749 | another pattern matching syntax that is different from that of 'pcase', |
| 750 | which some users might find less cryptic. | ||
| 751 | See 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 | |||
| 44 | normally has the form (CONDITION BODY...). | 44 | normally has the form (CONDITION BODY...). |
| 45 | 45 | ||
| 46 | CONDITION can be a Lisp expression, as in `cond'. | 46 | CONDITION can be a Lisp expression, as in `cond'. |
| 47 | Or it can be `(bind* BINDINGS...)' or `(match* PATTERN DATUM)'. | 47 | Or 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 | ||
| 51 | pattern PATTERN, using the same pattern syntax as `pcase'. | ||
| 52 | The 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*') |
| 50 | for the body of the clause. As a condition, it counts as true | 55 | for the body of the clause. As a condition, it counts as true |
| 51 | if the first binding's value is non-nil. All the bindings are made | 56 | if the first binding's value is non-nil. All the bindings are made |
| 52 | unconditionally for whatever scope they cover. | 57 | unconditionally 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 |
| 55 | The condition counts as true if PATTERN matches DATUM. | 60 | syntax for its patterns, see `match*'. |
| 56 | 61 | ||
| 57 | When a clause's condition is true, and it exits the `cond*' | 62 | When a clause's condition is true, and it exits the `cond*' |
| 58 | or is the last clause, the value of the last expression | 63 | or 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. |
| 75 | It is not really a Lisp function, and it is meaningful | 80 | It is not really a Lisp function, and it is meaningful |
| 76 | only in the CONDITION of a `cond*' clause. | 81 | only 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 | ||