Destructuring-bind
- Subject: Destructuring-bind
- From: Camm Maguire
- Date: 15 Feb 2002 13:34:34 -0500
Greetings!
Raymond Toy <toy@rtp.ericsson.se> writes:
> >>>>> "Camm" == Camm Maguire <camm@enhanced.com> writes:
>
> FWIW, I just grabbed some of the code from CMUCL, loaded it up in gcl
> 2.4.0, and ran your example. It works. It's attached below.
>
> If you decide to use this, it should be cleaned up with the comments
> and stuff added back in, including fixing the various error cases.
>
Thanks, as always! OK, these all seem to come from two cmulisp files,
defmacro.lisp and macros.lisp. I can add the comments back in, but
what do you mean by fixing the error cases? gcl seems to understand
(error..) just fine.
Thanks again,
> Ray
>
> (defvar *arg-tests* ()
> "A list of tests that do argument counting at expansion time.")
>
> (defvar *system-lets* nil)
> (defvar *user-lets* ()
> "Let bindings that the user has explicitly supplied.")
>
> (defvar *default-default* nil
> "Unsupplied optional and keyword arguments get this value defaultly.")
>
> (defun push-optional-binding (value-var init-form supplied-var condition path
> name error-kind error-fun)
> (unless supplied-var
> (setf supplied-var (gensym "SUPLIEDP-")))
> (push-let-binding supplied-var condition t)
> (cond ((consp value-var)
> (let ((whole-thing (gensym "OPTIONAL-SUBLIST-")))
> (push-sub-list-binding whole-thing
> `(if ,supplied-var ,path ,init-form)
> value-var name error-kind error-fun)
> (parse-defmacro-lambda-list value-var whole-thing name
> error-kind error-fun)))
> ((symbolp value-var)
> (push-let-binding value-var path nil supplied-var init-form))
> (t
> (error "Illegal optional variable name: ~S" value-var))))
>
> (defun push-let-binding (variable path systemp &optional condition
> (init-form *default-default*))
> (let ((let-form (if condition
> `(,variable (if ,condition ,path ,init-form))
> `(,variable ,path))))
> (if systemp
> (push let-form *system-lets*)
> (push let-form *user-lets*))))
>
> (defun push-sub-list-binding (variable path object name error-kind error-fun)
> (let ((var (gensym "TEMP-")))
> (push `(,variable
> (let ((,var ,path))
> (if (listp ,var)
> ,var
> (,error-fun 'defmacro-bogus-sublist-error
> :kind ',error-kind
> ,@(when name `(:name ',name))
> :object ,var
> :lambda-list ',object))))
> *system-lets*)))
>
> (defun parse-body (body environment &optional (doc-string-allowed t))
> "This function is to parse the declarations and doc-string out of the body of
> a defun-like form. Body is the list of stuff which is to be parsed.
> Environment is ignored. If Doc-String-Allowed is true, then a doc string
> will be parsed out of the body and returned. If it is false then a string
> will terminate the search for declarations. Three values are returned: the
> tail of Body after the declarations and doc strings, a list of declare forms,
> and the doc-string, or NIL if none."
> (declare (ignore environment))
> (let ((decls ())
> (doc nil))
> (do ((tail body (cdr tail)))
> ((endp tail)
> (values tail (nreverse decls) doc))
> (let ((form (car tail)))
> (cond ((and (stringp form) (cdr tail))
> (if doc-string-allowed
> (setq doc form
> ;; Only one doc string is allowed.
> doc-string-allowed nil)
> (return (values tail (nreverse decls) doc))))
> ((not (and (consp form) (symbolp (car form))))
> (return (values tail (nreverse decls) doc)))
> ((eq (car form) 'declare)
> (push form decls))
> (t
> (return (values tail (nreverse decls) doc))))))))
>
> (defun parse-defmacro-lambda-list
> (lambda-list arg-list-name name error-kind error-fun
> &optional top-level env-illegal env-arg-name)
> (let ((path (if top-level `(cdr ,arg-list-name) arg-list-name))
> (now-processing :required)
> (maximum 0)
> (minimum 0)
> (keys ())
> rest-name restp allow-other-keys-p env-arg-used)
> ;; This really strange way to test for '&whole is neccessary because member
> ;; does not have to work on dotted lists, and dotted lists are legal
> ;; in lambda-lists.
> (when (and (do ((list lambda-list (cdr list)))
> ((atom list) nil)
> (when (eq (car list) '&whole) (return t)))
> (not (eq (car lambda-list) '&whole)))
> (error "&Whole must appear first in ~S lambda-list." error-kind))
> (do ((rest-of-args lambda-list (cdr rest-of-args)))
> ((atom rest-of-args)
> (cond ((null rest-of-args) nil)
> ;; Varlist is dotted, treat as &rest arg and exit.
> (t (push-let-binding rest-of-args path nil)
> (setf restp t))))
> (let ((var (car rest-of-args)))
> (cond ((eq var '&whole)
> (cond ((and (cdr rest-of-args) (symbolp (cadr rest-of-args)))
> (setf rest-of-args (cdr rest-of-args))
> (push-let-binding (car rest-of-args) arg-list-name nil))
> (t
> (defmacro-error "&WHOLE" error-kind name))))
> ((eq var '&environment)
> (cond (env-illegal
> (error "&Environment not valid with ~S." error-kind))
> ((not top-level)
> (error "&Environment only valid at top level of ~
> lambda-list.")))
> (cond ((and (cdr rest-of-args) (symbolp (cadr rest-of-args)))
> (setf rest-of-args (cdr rest-of-args))
> (push-let-binding (car rest-of-args) env-arg-name nil)
> (setf env-arg-used t))
> (t
> (defmacro-error "&ENVIRONMENT" error-kind name))))
> ((or (eq var '&rest) (eq var '&body))
> (cond ((and (cdr rest-of-args) (symbolp (cadr rest-of-args)))
> (setf rest-of-args (cdr rest-of-args))
> (setf restp t)
> (push-let-binding (car rest-of-args) path nil))
> ;;
> ;; This branch implements an incompatible extension to
> ;; Common Lisp. In place of a symbol following &body,
> ;; there may be a list of up to three elements which will
> ;; be bound to the body, declarations, and doc-string of
> ;; the body.
> ((and (cdr rest-of-args)
> (consp (cadr rest-of-args))
> (symbolp (caadr rest-of-args)))
> (setf rest-of-args (cdr rest-of-args))
> (setf restp t)
> (let ((body-name (caar rest-of-args))
> (declarations-name (cadar rest-of-args))
> (doc-string-name (caddar rest-of-args))
> (parse-body-values (gensym)))
> (push-let-binding
> parse-body-values
> `(multiple-value-list
> (parse-body ,path ,env-arg-name
> ,(not (null doc-string-name))))
> t)
> (setf env-arg-used t)
> (when body-name
> (push-let-binding body-name
> `(car ,parse-body-values) nil))
> (when declarations-name
> (push-let-binding declarations-name
> `(cadr ,parse-body-values) nil))
> (when doc-string-name
> (push-let-binding doc-string-name
> `(caddr ,parse-body-values) nil))))
> (t
> (defmacro-error (symbol-name var) error-kind name))))
> ((eq var '&optional)
> (setf now-processing :optionals))
> ((eq var '&key)
> (setf now-processing :keywords)
> (setf rest-name (gensym "KEYWORDS-"))
> (push rest-name *ignorable-vars*)
> (setf restp t)
> (push-let-binding rest-name path t))
> ((eq var '&allow-other-keys)
> (setf allow-other-keys-p t))
> ((eq var '&aux)
> (setf now-processing :auxs))
> ((listp var)
> (case now-processing
> (:required
> (let ((sub-list-name (gensym "SUBLIST-")))
> (push-sub-list-binding sub-list-name `(car ,path) var
> name error-kind error-fun)
> (parse-defmacro-lambda-list var sub-list-name name
> error-kind error-fun))
> (setf path `(cdr ,path))
> (incf minimum)
> (incf maximum))
> (:optionals
> (when (> (length var) 3)
> (cerror "Ignore extra noise."
> "More than variable, initform, and suppliedp ~
> in &optional binding - ~S"
> var))
> (push-optional-binding (car var) (cadr var) (caddr var)
> `(not (null ,path)) `(car ,path)
> name error-kind error-fun)
> (setf path `(cdr ,path))
> (incf maximum))
> (:keywords
> (let* ((keyword-given (consp (car var)))
> (variable (if keyword-given
> (cadar var)
> (car var)))
> (keyword (if keyword-given
> (caar var)
> (make-keyword variable)))
> (supplied-p (caddr var)))
> (push-optional-binding variable (cadr var) supplied-p
> `(keyword-supplied-p ',keyword
> ,rest-name)
> `(lookup-keyword ',keyword
> ,rest-name)
> name error-kind error-fun)
> (push keyword keys)))
> (:auxs (push-let-binding (car var) (cadr var) nil))))
> ((symbolp var)
> (case now-processing
> (:required
> (incf minimum)
> (incf maximum)
> (push-let-binding var `(car ,path) nil)
> (setf path `(cdr ,path)))
> (:optionals
> (incf maximum)
> (push-let-binding var `(car ,path) nil `(not (null ,path)))
> (setf path `(cdr ,path)))
> (:keywords
> (let ((key (make-keyword var)))
> (push-let-binding var `(lookup-keyword ,key ,rest-name)
> nil)
> (push key keys)))
> (:auxs
> (push-let-binding var nil nil))))
> (t
> (error "Non-symbol in lambda-list - ~S." var)))))
> ;; Generate code to check the number of arguments, unless dotted
> ;; in which case length will not work.
> (unless restp
> (push `(unless (<= ,minimum
> (length (the list ,(if top-level
> `(cdr ,arg-list-name)
> arg-list-name)))
> ,@(unless restp
> (list maximum)))
> ,(let ((arg (if top-level
> `(cdr ,arg-list-name)
> arg-list-name)))
> (if (eq error-fun 'error)
> `(do-arg-count-error ',error-kind ',name ,arg
> ',lambda-list ,minimum
> ,(unless restp maximum))
> `(,error-fun 'defmacro-ll-arg-count-error
> :kind ',error-kind
> ,@(when name `(:name ',name))
> :argument ,arg
> :lambda-list ',lambda-list
> :minimum ,minimum
> ,@(unless restp `(:maximum ,maximum))))))
> *arg-tests*))
> (if keys
> (let ((problem (gensym "KEY-PROBLEM-"))
> (info (gensym "INFO-")))
> (push `(multiple-value-bind
> (,problem ,info)
> (verify-keywords ,rest-name ',keys ',allow-other-keys-p)
> (when ,problem
> (,error-fun
> 'defmacro-ll-broken-key-list-error
> :kind ',error-kind
> ,@(when name `(:name ',name))
> :problem ,problem
> :info ,info)))
> *arg-tests*)))
> (values env-arg-used minimum (if (null restp) maximum nil))))
>
> (defun parse-defmacro (lambda-list arg-list-name code name error-kind
> &key (annonymousp nil)
> (doc-string-allowed t)
> ((:environment env-arg-name))
> ((:default-default *default-default*))
> (error-fun 'error))
> "Returns as multiple-values a parsed body, any local-declarations that
> should be made where this body is inserted, and a doc-string if there is
> one."
> (multiple-value-bind (body declarations documentation)
> (parse-body code nil doc-string-allowed)
> (let* ((*arg-tests* ())
> (*user-lets* ())
> (*system-lets* ())
> (*ignorable-vars* ()))
> (multiple-value-bind
> (env-arg-used minimum maximum)
> (parse-defmacro-lambda-list lambda-list arg-list-name name
> error-kind error-fun (not annonymousp)
> nil env-arg-name)
> (values
> `(let* ,(nreverse *system-lets*)
> ,@(when *ignorable-vars*
> `((declare (ignorable ,@*ignorable-vars*))))
> ,@*arg-tests*
> (let* ,(nreverse *user-lets*)
> ,@declarations
> ,@body))
> `(,@(when (and env-arg-name (not env-arg-used))
> `((declare (ignore ,env-arg-name)))))
> documentation
> minimum
> maximum)))))
>
> (defmacro destructuring-bind (lambda-list arg-list &rest body)
> "Bind the variables in LAMBDA-LIST to the contents of ARG-LIST."
> (let* ((arg-list-name (gensym "ARG-LIST-")))
> (multiple-value-bind
> (body local-decls)
> (parse-defmacro lambda-list arg-list-name body nil 'destructuring-bind
> :annonymousp t :doc-string-allowed nil)
> `(let ((,arg-list-name ,arg-list))
> ,@local-decls
> ,body))))
> _______________________________________________
> Maxima mailing list
> Maxima@www.math.utexas.edu
> http://www.math.utexas.edu/mailman/listinfo/maxima
>
>
--
Camm Maguire camm@enhanced.com
==========================================================================
"The earth is but one country, and mankind its citizens." -- Baha'u'llah