I was pleased to discover that DESTRUCTURING-CASE made it into
Alexandria meanwhile. Very nice! This allows me to share the
little gem that I'm attaching with this posting. It's a handy
macro to make writing macros like DEFPACKAGE, DEFGENERIC of
DEFREADTABLE easy like a breeze.
Happy to be giving back again! :-)
T
diff --git a/macros.lisp b/macros.lisp
index 4450435..0d4b9e9 100644
--- a/macros.lisp
+++ b/macros.lisp
@@ -303,4 +303,76 @@ Example:
(setf (documentation name 'function) (documentation 'destructuring-case 'function)))
+;;; DESTRUCTURE-CLAUSES
+(defmacro destructure-clauses (clauses patterns &body body)
+ "Utility macro to conveniently destructure DEFPACKAGE like clauses.
+
+ clauses ::= clause*
+ clause ::= (keyword . list) ; e.g. (:FOO 1 :K 2)
+
+ patterns ::= pattern*
+ pattern ::= (keyword . ordinary-lambda-list) ; e.g. (:FOO N &KEY K)
+
+The CAR of each clause in CLAUSES will be tried to be matched against
+the CAR of each pattern in PATTERNS. If a match is found, the CDR of
+the matched pattern is interpreted as an ordinary lambda list and its
+parameters are bound to the values provided by the CDR of the clause.
+
+When all CLAUSES are processed, BODY will be executed with all the
+parameters of the matched patterns bound appropriately. Notice that
+parameters must hence be named distinctively in each pattern.
+
+Example:
+
+ (defmacro defpackage (name &body clauses)
+ (destructure-clauses clauses
+ ((:use &rest uses)
+ (:nicknames &rest nicknames)
+ (:export &rest exports)
+ ...)
+ (expand-defpackage name uses nicknames exports ...))
+
+Implementation note:
+
+ That a pattern consists of ordinary lambda list and not of
+ destructuring lambda lists is a /shortcoming/ of the current
+ implementation. Patches are welcome.
+"
+ (multiple-value-bind (pattern-table vars n-vars)
+ (loop for pattern in patterns
+ for vars = (ordinary-lambda-list-parameters (cdr pattern))
+ for n-vars = (mapcar #'make-gensym vars)
+ collect (cons pattern (list vars n-vars)) into pattern-mappings
+ append vars into all-vars
+ append n-vars into all-n-vars
+ finally
+ (return (values (alist-hash-table pattern-mappings)
+ all-vars
+ all-n-vars)))
+ (with-unique-names (clause)
+ `(let ,n-vars
+ (dolist (,clause ,clauses)
+ (destructuring-ecase ,clause
+ ,@(loop for p in patterns
+ for (pattern-vars pattern-n-vars) = (gethash p pattern-table)
+ collect `(,p ,@(loop for var in pattern-vars
+ for n-var in pattern-n-vars
+ collect `(setq ,n-var ,var))))))
+ (let ,(mapcar #'list vars n-vars)
+ ,@body)))))
+
+(defun ordinary-lambda-list-parameters (ordinary-lambda-list)
+ "Return a list of all parameter names in ORDINARY-LAMBDA-LIST."
+ (flet ((optional-parameter-name (spec)
+ (car spec))
+ (key-parameter-name (spec)
+ (second (first spec))))
+ (multiple-value-bind (reqs opts rest keys aok auxs)
+ (parse-ordinary-lambda-list ordinary-lambda-list)
+ (assert (null aok) () "&ALLOW-OTHER-KEYS not supported.")
+ (assert (null auxs) () "&AUX not supported.")
+ (append reqs
+ (mapcar #'optional-parameter-name opts)
+ (ensure-list rest)
+ (mapcar #'key-parameter-name keys)))))
\ No newline at end of file
diff --git a/package.lisp b/package.lisp
index babeb95..a025cc7 100644
--- a/package.lisp
+++ b/package.lisp
@@ -240,4 +240,5 @@
#:destructuring-case
#:destructuring-ccase
#:destructuring-ecase
+ #:destructure-clauses
))
diff --git a/tests.lisp b/tests.lisp
index e218113..51ea8ca 100644
--- a/tests.lisp
+++ b/tests.lisp
@@ -1856,3 +1856,47 @@
(when err
(incf n))))))
13)
+
+(deftest destructure-clauses.1
+ (let ((+clauses+ '((:required-1 :R1)
+ (:required-n :R2 :R3 :R4)
+ (:optional-0)
+ (:optional-1 :O2)
+ (:key-0)
+ (:key-1 :KEY2 :K2)
+ (:key-n :KEY3 :K3 :KEY4 :K4)
+ (:required+optional-0 :R5)
+ (:required+optional-1 :R6 :O4)
+ (:required+key :R7 :R8 :KEY5 :K5))))
+ (destructure-clauses +clauses+
+ ((:required-1 req1)
+ (:required-n req2 req3 req4)
+ (:optional-0 &optional (opt1 :O1))
+ (:optional-1 &optional opt2)
+ (:key-0 &key (key1 :K1))
+ (:key-1 &key key2)
+ (:key-n &key key3 key4)
+ (:required+optional-0 req5 &optional (opt3 :O3))
+ (:required+optional-1 req6 &optional opt4)
+ (:required+key req7 req8 &key key5))
+ (values
+ (list req1)
+ (list req2 req3 req4)
+ (list opt1)
+ (list opt2)
+ (list key1)
+ (list key2)
+ (list key3 key4)
+ (list req5 opt3)
+ (list req6 opt4)
+ (list req7 req8 key5))))
+ (:R1)
+ (:R2 :R3 :R4)
+ (:O1)
+ (:O2)
+ (:K1)
+ (:K2)
+ (:K3 :K4)
+ (:R5 :O3)
+ (:R6 :O4)
+ (:R7 :R8 :K5))
\ No newline at end of file