The backquote pcase
pattern `--pcase-macroexpander
is implemented using pcase-defmacro
in the library pcase.el
.
There is no pattern included there that does what you discussed.
But, it may be added as shown in the following Elisp code that defines an alternative pcase
pattern bq-opt
.
The added lines are cleanly added as one block. This block is framed by two lines only consisting of semi-colons.
(pcase-defmacro bq-opt (qpat)
"Pattern (bq-opt QPAT) dealing with ?\\, like the backquote pattern.
QPAT can take the following forms:
((optional QPAT1) . QPAT2) matches if QPAT1 matches the car
and QPAT2 matches the cdr or if QPAT2 matches
(QPAT1 . QPAT2) matches if QPAT1 matches the car and QPAT2 the cdr.
,PAT matches if the `pcase' pattern PAT matches.
SYMBOL matches if EXPVAL is `equal' to SYMBOL.
KEYWORD likewise for KEYWORD.
NUMBER likewise for NUMBER.
STRING likewise for STRING.
The list or vector QPAT is a template. The predicate formed
by a backquote-style pattern is a combination of those
formed by any sub-patterns, wrapped in a top-level condition:
EXPVAL must be \"congruent\" with the template. For example:
(bq-opt technical ,forum)
The predicate is the logical-AND of:
- Is EXPVAL a list of two elements?
- Is the first element the symbol `technical'?
- True! (The second element can be anything, and for the sake
of the body forms, its value is bound to the symbol `forum'.)"
(declare (debug (pcase-QPAT)))
(cond
((eq (car-safe qpat) '\,) (cadr qpat))
((vectorp qpat)
`(and (pred vectorp)
(app length ,(length qpat))
,@(let ((upats nil))
(dotimes (i (length qpat))
(push `(app (pcase--flip aref ,i) ,(list '\` (aref qpat i)))
upats))
(nreverse upats))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Added case (optional QPAT)
((pcase qpat
(`((optional ,qpat1) . ,qpat2)
`(or (and (pred consp)
(app car ,qpat1)
(app cdr ,(list 'bq-opt qpat2)))
,(list 'bq-opt qpat2)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
((consp qpat)
`(and (pred consp)
(app car ,(list 'bq-opt (car qpat)))
(app cdr ,(list 'bq-opt (cdr qpat)))))
((or (stringp qpat) (numberp qpat) (symbolp qpat)) `',qpat)
;; In all other cases just raise an error so we can't break
;; backward compatibility when adding \` support for other
;; compounded values that are not `consp'
(t (error "Unknown QPAT: %S" qpat))))
Usage example with optional keywords in EXPVAL
:
(pcase '(:def "k" foo :wk "ho")
((bq-opt ((optional :def)
,(and (or (pred stringp) (pred vectorp)) key)
,(and (pred symbolp) def)
(optional :wk)
,(and (pred stringp) desc)
))
(format "%s | %s | %s" key def desc)))
Result:
"k | foo | ho"
Usage example without optional keywords in EXPVAL
:
(pcase '("k" foo "ho")
((bq-opt ((optional :def)
,(and (or (pred stringp) (pred vectorp)) key)
,(and (pred symbolp) def)
(optional :wk)
,(and (pred stringp) desc)
))
(format "%s | %s | %s" key def desc)))
Result:
"k | foo | ho"