;;;; the PARSE-DEFMACRO function and related code ;;;; This software is part of the SBCL system. See the README file for ;;;; more information. ;;;; ;;;; This software is derived from the CMU CL system, which was ;;;; written at Carnegie Mellon University and released into the ;;;; public domain. The software is in the public domain and is ;;;; provided with absolutely no warranty. See the COPYING and CREDITS ;;;; files for more information. (in-package "SB!KERNEL") ;;; variables for accumulating the results of parsing a DEFMACRO. (Declarations ;;; in DEFMACRO are the reason this isn't as easy as it sounds.) (defvar *arg-tests*) ; tests that do argument counting at expansion time (declaim (type list *arg-tests*)) (defvar *system-lets*) ; LET bindings done to allow lambda-list parsing (declaim (type list *system-lets*)) (defvar *user-lets*) ; LET bindings that the user has explicitly supplied (declaim (type list *user-lets*)) (defvar *env-var*) ; &ENVIRONMENT variable name ;; the default default for unsupplied &OPTIONAL and &KEY args (defvar *default-default*) ;;; temps that we introduce and might not reference (defvar *ignorable-vars*) (declaim (type list *ignorable-vars*)) ;;; Return, as multiple values, a body, possibly a DECLARE form to put ;;; where this code is inserted, the documentation for the parsed ;;; body, and bounds on the number of arguments. (defun parse-defmacro (lambda-list whole-var body name context &key (anonymousp nil) (doc-string-allowed t) ((:environment env-arg-name)) ((:default-default *default-default*)) (error-fun 'error) (wrap-block t)) (unless (listp lambda-list) (bad-type lambda-list 'list "~S lambda-list is not a list: ~S" context lambda-list)) (multiple-value-bind (forms declarations documentation) (parse-body body :doc-string-allowed doc-string-allowed) (let ((*arg-tests* ()) (*user-lets* ()) (*system-lets* ()) (*ignorable-vars* ()) (*env-var* nil)) (multiple-value-bind (env-arg-used minimum maximum) (parse-defmacro-lambda-list lambda-list whole-var name context :error-fun error-fun :anonymousp anonymousp) (values `(let* (,@(nreverse *system-lets*)) #-sb-xc-host (declare (muffle-conditions sb!ext:code-deletion-note)) ,@(when *ignorable-vars* `((declare (ignorable ,@*ignorable-vars*)))) ,@*arg-tests* (let* (,@(when env-arg-used `((,*env-var* ,env-arg-name))) ,@(nreverse *user-lets*)) ,@declarations ,@(if wrap-block `((block ,(fun-name-block-name name) ,@forms)) forms))) `(,@(when (and env-arg-name (not env-arg-used)) `((declare (ignore ,env-arg-name))))) documentation minimum maximum))))) (defun parse-defmacro-lambda-list (possibly-dotted-lambda-list whole-var name context &key error-fun anonymousp env-illegal sublist) (let* (;; PATH is a sort of pointer into the part of the lambda list we're ;; considering at this point in the code. PATH-0 is the root of the ;; lambda list, which is the initial value of PATH. (path-0 (if (or anonymousp sublist) whole-var `(cdr ,whole-var))) (path path-0) ; will change below (compiler-macro-whole (gensym "CMACRO-&WHOLE")) (now-processing :required) (maximum 0) (minimum 0) (keys ()) (key-seen nil) (aux-seen nil) (optional-seen nil) ;; ANSI specifies that dotted lists are "treated exactly as if the ;; parameter name that ends the list had appeared preceded by &REST." ;; We force this behavior by transforming dotted lists into ordinary ;; lists with explicit &REST elements. (lambda-list (do ((in-pdll possibly-dotted-lambda-list (cdr in-pdll)) (reversed-result nil)) ((atom in-pdll) (nreverse (if in-pdll (list* in-pdll '&rest reversed-result) reversed-result))) (push (car in-pdll) reversed-result))) rest-name restp allow-other-keys-p env-arg-used) (when (member '&whole (rest lambda-list)) (error "&WHOLE may only appear first in ~S lambda-list." context)) ;; Special case compiler-macros: if car of the form is FUNCALL, ;; skip over it for destructuring, pretending cdr of the form is ;; the actual form. Save original for &WHOLE. (when (and (not sublist) (eq context 'define-compiler-macro)) (push-let-binding compiler-macro-whole whole-var :system t) (push compiler-macro-whole *ignorable-vars*) (push-let-binding whole-var whole-var :system t :when `(not (eq 'funcall (car ,whole-var))) ;; Do we need to SETF too? :else `(setf ,whole-var (cdr ,whole-var)))) (do ((rest-of-lambda-list lambda-list (cdr rest-of-lambda-list))) ((null rest-of-lambda-list)) (macrolet ((process-sublist (var kind path) (once-only ((var var)) `(if (listp ,var) (let ((sublist-name (gensym ,kind))) (push-sublist-binding sublist-name ,path ,var name context error-fun) (parse-defmacro-lambda-list ,var sublist-name name context :error-fun error-fun :sublist t)) (push-let-binding ,var ,path)))) (normalize-singleton (var) `(when (null (cdr ,var)) (setf (cdr ,var) (list *default-default*))))) (let ((var (car rest-of-lambda-list))) (typecase var (list (case now-processing ((:required) (when restp (defmacro-error (format nil "required argument after ~A" restp) context name)) (when (process-sublist var "REQUIRED-" `(car ,path)) ;; Note &ENVIRONMENT from DEFSETF sublist (aver (eq context 'defsetf)) (setf env-arg-used t)) (setq path `(cdr ,path) minimum (1+ minimum) maximum (1+ maximum))) ((:optionals) (normalize-singleton var) (destructuring-bind (varname &optional default-form suppliedp-name) var (push-optional-binding varname default-form suppliedp-name :is-supplied-p `(not (null ,path)) :path `(car ,path) :name name :context context :error-fun error-fun)) (setq path `(cdr ,path) maximum (1+ maximum))) ((:keywords) (normalize-singleton var) (let* ((keyword-given (consp (car var))) (variable (if keyword-given (cadar var) (car var))) (keyword (if keyword-given (caar var) (keywordicate variable))) (default-form (cadr var)) (suppliedp-name (caddr var))) (push-optional-binding variable default-form suppliedp-name :is-supplied-p `(keyword-supplied-p ',keyword ,rest-name) :path `(lookup-keyword ',keyword ,rest-name) :name name :context context :error-fun error-fun) (push keyword keys))) ((:auxs) (push-let-binding (car var) (cadr var))))) ((and symbol (not (eql nil))) (case var (&whole (cond ((cdr rest-of-lambda-list) (pop rest-of-lambda-list) (process-sublist (car rest-of-lambda-list) "WHOLE-LIST-" (if (eq 'define-compiler-macro context) compiler-macro-whole whole-var))) (t (defmacro-error "&WHOLE" context name)))) (&environment (cond (env-illegal (error "&ENVIRONMENT is not valid with ~S." context)) ;; DEFSETF explicitly allows &ENVIRONMENT, and we get ;; it here in a sublist. ((and sublist (neq context 'defsetf)) (error "&ENVIRONMENT is only valid at top level of ~ lambda-list.")) (env-arg-used (error "Repeated &ENVIRONMENT."))) (cond ((and (cdr rest-of-lambda-list) (symbolp (cadr rest-of-lambda-list))) (setq rest-of-lambda-list (cdr rest-of-lambda-list)) (check-defmacro-arg (car rest-of-lambda-list)) (setq *env-var* (car rest-of-lambda-list) env-arg-used t)) (t (defmacro-error "&ENVIRONMENT" context name)))) ((&rest &body) (cond ((or key-seen aux-seen) (error "~A after ~A in ~A" var (or key-seen aux-seen) context)) ((and (not restp) (cdr rest-of-lambda-list)) (setq rest-of-lambda-list (cdr rest-of-lambda-list) restp var) (process-sublist (car rest-of-lambda-list) "REST-LIST-" path)) (t (defmacro-error (symbol-name var) context name)))) (&optional (when (or key-seen aux-seen restp) (error "~A after ~A in ~A lambda-list." var (or key-seen aux-seen restp) context)) (when optional-seen (error "Multiple ~A in ~A lambda list." var context)) (setq now-processing :optionals optional-seen var)) (&key (when aux-seen (error "~A after ~A in ~A lambda-list." '&key '&aux context)) (when key-seen (error "Multiple ~A in ~A lambda-list." '&key context)) (setf now-processing :keywords rest-name (gensym "KEYWORDS-") restp var key-seen var) (push rest-name *ignorable-vars*) (push-let-binding rest-name path :system t)) (&allow-other-keys (unless (eq now-processing :keywords) (error "~A outside ~A section of lambda-list in ~A." var '&key context)) (when allow-other-keys-p (error "Multiple ~A in ~A lambda-list." var context)) (setq allow-other-keys-p t)) (&aux (when (eq context 'defsetf) (error "~A not allowed in a ~A lambda-list." var context)) (when aux-seen (error "Multiple ~A in ~A lambda-list." '&aux context)) (setq now-processing :auxs aux-seen var)) ;; FIXME: Other lambda list keywords. (t (case now-processing ((:required) (when restp (defmacro-error (format nil "required argument after ~A" restp) context name)) (push-let-binding var `(car ,path)) (setq minimum (1+ minimum) maximum (1+ maximum) path `(cdr ,path))) ((:optionals) (push-let-binding var `(car ,path) :when `(not (null ,path))) (setq path `(cdr ,path) maximum (1+ maximum))) ((:keywords) (let ((key (keywordicate var))) (push-let-binding var `(lookup-keyword ,key ,rest-name) :when `(keyword-supplied-p ,key ,rest-name)) (push key keys))) ((:auxs) (push-let-binding var nil)))))) (t (error "non-symbol in lambda-list: ~S" var)))))) (let (;; common subexpression, suitable for passing to functions ;; which expect a MAXIMUM argument regardless of whether ;; there actually is a maximum number of arguments ;; (expecting MAXIMUM=NIL when there is no maximum) (explicit-maximum (and (not restp) maximum))) (unless (and restp (zerop minimum)) (push (let ((args-form (if (eq 'define-compiler-macro context) `(if (eq 'funcall (car ,whole-var)) (cdr ,path-0) ,path-0) path-0))) (with-unique-names (args) `(let ((,args ,args-form)) (unless ,(if restp ;; (If RESTP, then the argument list ;; might be dotted, in which case ;; ordinary LENGTH won't work.) `(list-of-length-at-least-p ,args ,minimum) `(proper-list-of-length-p ,args ,minimum ,maximum)) ,(if (eq error-fun 'error) `(arg-count-error ',context ',name ,args ',lambda-list ,minimum ,explicit-maximum) `(,error-fun 'arg-count-error :kind ',context ,@(when name `(:name ',name)) :args ,args :lambda-list ',lambda-list :minimum ,minimum :maximum ,explicit-maximum)))))) *arg-tests*)) (when key-seen (with-unique-names (problem info) (push `(multiple-value-bind (,problem ,info) (verify-keywords ,rest-name ',keys ',allow-other-keys-p ,(eq 'define-compiler-macro context)) (when ,problem (,error-fun 'defmacro-lambda-list-broken-key-list-error :kind ',context ,@(when name `(:name ',name)) :problem ,problem :info ,info))) *arg-tests*))) (values env-arg-used minimum explicit-maximum)))) ;;; We save space in macro definitions by calling this function. (defun arg-count-error (context name args lambda-list minimum maximum) (let (#-sb-xc-host (sb!debug:*stack-top-hint* (or sb!debug:*stack-top-hint* 'arg-count-error))) (error 'arg-count-error :kind context :name name :args args :lambda-list lambda-list :minimum minimum :maximum maximum))) (defun push-sublist-binding (variable path object name context error-fun) (check-defmacro-arg variable) (let ((var (gensym "TEMP-"))) (push `(,variable (let ((,var ,path)) (if (listp ,var) ,var (,error-fun 'defmacro-bogus-sublist-error :kind ',context ,@(when name `(:name ',name)) :object ,var :lambda-list ',object)))) *system-lets*))) (defun push-let-binding (variable form &key system when (else *default-default*)) (check-defmacro-arg variable) (let ((let-form (if when `(,variable (if ,when ,form ,else)) `(,variable ,form)))) (if system (push let-form *system-lets*) (push let-form *user-lets*)))) (defun push-optional-binding (value-var init-form suppliedp-name &key is-supplied-p path name context error-fun) (unless suppliedp-name (setq suppliedp-name (gensym "SUPPLIEDP-"))) (push-let-binding suppliedp-name is-supplied-p :system t) (cond ((consp value-var) (let ((whole-thing (gensym "OPTIONAL-SUBLIST-"))) (push-sublist-binding whole-thing `(if ,suppliedp-name ,path ,init-form) value-var name context error-fun) (parse-defmacro-lambda-list value-var whole-thing name context :error-fun error-fun :sublist t))) ((symbolp value-var) (push-let-binding value-var path :when suppliedp-name :else init-form)) (t (error "illegal optional variable name: ~S" value-var)))) (defun defmacro-error (problem context name) (error "illegal or ill-formed ~A argument in ~A~@[ ~S~]" problem context name)) (defun check-defmacro-arg (arg) (when (or (and *env-var* (eq arg *env-var*)) (member arg *system-lets* :key #'car) (member arg *user-lets* :key #'car)) (error "variable ~S occurs more than once" arg))) ;;; Determine whether KEY-LIST is a valid list of keyword/value pairs. ;;; Do not signal the error directly, 'cause we don't know how it ;;; should be signaled. (defun verify-keywords (key-list valid-keys allow-other-keys &optional compiler-macro) (do ((already-processed nil) (unknown-keyword nil) (remaining key-list (cddr remaining))) ((null remaining) (if (and unknown-keyword (not allow-other-keys) (not (lookup-keyword :allow-other-keys key-list))) (values :unknown-keyword (list unknown-keyword valid-keys)) (values nil nil))) (let ((key (when (consp remaining) (car remaining)))) (cond ((not (and (consp remaining) (listp (cdr remaining)))) (return (values :dotted-list key-list))) ((null (cdr remaining)) (return (values :odd-length key-list)))) ;; Compiler-macro lambda lists are macro lambda lists -- meaning that ;; &key ((a a) t) should match a literal A, not a form evaluating to A ;; as in an ordinary lambda list. ;; ;; That, however, breaks the evaluation model unless A is also a ;; constant evaluating to itself. So, signal a condition telling the ;; compiler to punt on the expansion. (when (and compiler-macro (not (or (keywordp key) (and (symbolp key) (constantp key) (eq key (symbol-value key)))))) (signal 'compiler-macro-keyword-problem :argument key)) (cond ((or (eq key :allow-other-keys) (member key valid-keys)) (push key already-processed)) (t (setq unknown-keyword key)))))) (defun lookup-keyword (keyword key-list) (do ((remaining key-list (cddr remaining))) ((endp remaining)) (when (eq keyword (car remaining)) (return (cadr remaining))))) (defun keyword-supplied-p (keyword key-list) (do ((remaining key-list (cddr remaining))) ((endp remaining)) (when (eq keyword (car remaining)) (return t))))