1f30f7c42829dff0829f4759d63953543be49e3d
[sbcl.git] / src / code / parse-defmacro.lisp
1 ;;;; the PARSE-DEFMACRO function and related code
2
3 ;;;; This software is part of the SBCL system. See the README file for
4 ;;;; more information.
5 ;;;;
6 ;;;; This software is derived from the CMU CL system, which was
7 ;;;; written at Carnegie Mellon University and released into the
8 ;;;; public domain. The software is in the public domain and is
9 ;;;; provided with absolutely no warranty. See the COPYING and CREDITS
10 ;;;; files for more information.
11
12 (in-package "SB!KERNEL")
13
14 ;;; variables for accumulating the results of parsing a DEFMACRO. (Declarations
15 ;;; in DEFMACRO are the reason this isn't as easy as it sounds.)
16 (defvar *arg-tests* nil) ; tests that do argument counting at expansion time
17 (declaim (type list *arg-tests*))
18 (defvar *system-lets* nil) ; LET bindings done to allow lambda-list parsing
19 (declaim (type list *system-lets*))
20 (defvar *user-lets* nil) ; LET bindings that the user has explicitly supplied
21 (declaim (type list *user-lets*))
22
23 ;; the default default for unsupplied optional and keyword args
24 (defvar *default-default* nil)
25
26 ;;; temps that we introduce and might not reference
27 (defvar *ignorable-vars*)
28 (declaim (type list *ignorable-vars*))
29
30 ;;; Return, as multiple-values, a body, possibly a declare form to put where
31 ;;; this code is inserted, the documentation for the parsed body, and bounds
32 ;;; on the number of arguments.
33 (defun parse-defmacro (lambda-list arg-list-name body name error-kind
34                                    &key
35                                    (anonymousp nil)
36                                    (doc-string-allowed t)
37                                    ((:environment env-arg-name))
38                                    ((:default-default *default-default*))
39                                    (error-fun 'error))
40   (multiple-value-bind (forms declarations documentation)
41       (parse-body body doc-string-allowed)
42     (let ((*arg-tests* ())
43           (*user-lets* ())
44           (*system-lets* ())
45           (*ignorable-vars* ()))
46       (multiple-value-bind (env-arg-used minimum maximum)
47           (parse-defmacro-lambda-list lambda-list arg-list-name name
48                                       error-kind error-fun (not anonymousp)
49                                       nil env-arg-name)
50         (values `(let* ,(nreverse *system-lets*)
51                    ,@(when *ignorable-vars*
52                        `((declare (ignorable ,@*ignorable-vars*))))
53                    ,@*arg-tests*
54                    (let* ,(nreverse *user-lets*)
55                      ,@declarations
56                      ,@forms))
57                 `(,@(when (and env-arg-name (not env-arg-used))
58                       `((declare (ignore ,env-arg-name)))))
59                 documentation
60                 minimum
61                 maximum)))))
62
63 ;;; partial reverse-engineered documentation:
64 ;;;   TOP-LEVEL is true for calls through PARSE-DEFMACRO from DEFSETF and
65 ;;;     DESTRUCTURING-BIND, false otherwise.
66 ;;; -- WHN 19990620
67 (defun parse-defmacro-lambda-list (possibly-dotted-lambda-list
68                                    arg-list-name
69                                    name
70                                    error-kind
71                                    error-fun
72                                    &optional
73                                    top-level
74                                    env-illegal
75                                    env-arg-name)
76   (let* (;; PATH is a sort of pointer into the part of the lambda list we're
77          ;; considering at this point in the code. PATH-0 is the root of the
78          ;; lambda list, which is the initial value of PATH.
79          (path-0 (if top-level
80                    `(cdr ,arg-list-name)
81                    arg-list-name))
82          (path path-0) ; (will change below)
83          (now-processing :required)
84          (maximum 0)
85          (minimum 0)
86          (keys ())
87          ;; ANSI specifies that dotted lists are "treated exactly as if the
88          ;; parameter name that ends the list had appeared preceded by &rest."
89          ;; We force this behavior by transforming dotted lists into ordinary
90          ;; lists with explicit &REST elements.
91          (lambda-list (do ((in-pdll possibly-dotted-lambda-list (cdr in-pdll))
92                            (reversed-result nil))
93                           ((atom in-pdll)
94                            (nreverse (if in-pdll
95                                        (list* in-pdll '&rest reversed-result)
96                                        reversed-result)))
97                         (push (car in-pdll) reversed-result)))
98          rest-name restp allow-other-keys-p env-arg-used)
99     (when (member '&whole (rest lambda-list))
100       (error "&WHOLE may only appear first in ~S lambda-list." error-kind))
101     (do ((rest-of-args lambda-list (cdr rest-of-args)))
102         ((null rest-of-args))
103       (let ((var (car rest-of-args)))
104         (cond ((eq var '&whole)
105                (cond ((and (cdr rest-of-args) (symbolp (cadr rest-of-args)))
106                       (setq rest-of-args (cdr rest-of-args))
107                       (push-let-binding (car rest-of-args) arg-list-name nil))
108                      (t
109                       (defmacro-error "&WHOLE" error-kind name))))
110               ((eq var '&environment)
111                (cond (env-illegal
112                       (error "&ENVIRONMENT is not valid with ~S." error-kind))
113                      ((not top-level)
114                       (error "&ENVIRONMENT is only valid at top level of ~
115                               lambda-list.")))
116                (cond ((and (cdr rest-of-args) (symbolp (cadr rest-of-args)))
117                       (setq rest-of-args (cdr rest-of-args))
118                       (push-let-binding (car rest-of-args) env-arg-name nil)
119                       (setq env-arg-used t))
120                      (t
121                       (defmacro-error "&ENVIRONMENT" error-kind name))))
122               ((or (eq var '&rest)
123                    (eq var '&body))
124                (cond ((and (cdr rest-of-args) (symbolp (cadr rest-of-args)))
125                       (setq rest-of-args (cdr rest-of-args))
126                       (setq restp t)
127                       (push-let-binding (car rest-of-args) path nil))
128                      (t
129                       (defmacro-error (symbol-name var) error-kind name))))
130               ((eq var '&optional)
131                (setq now-processing :optionals))
132               ((eq var '&key)
133                (setq now-processing :keywords)
134                (setq rest-name (gensym "KEYWORDS-"))
135                (push rest-name *ignorable-vars*)
136                (setq restp t)
137                (push-let-binding rest-name path t))
138               ((eq var '&allow-other-keys)
139                (setq allow-other-keys-p t))
140               ((eq var '&aux)
141                (setq now-processing :auxs))
142               ((listp var)
143                (cond ; (since it's too early to use CASE)
144                  ((eq now-processing :required)
145                   (let ((sub-list-name (gensym "SUBLIST-")))
146                     (push-sub-list-binding sub-list-name `(car ,path) var
147                                            name error-kind error-fun)
148                     (parse-defmacro-lambda-list var sub-list-name name
149                                                 error-kind error-fun))
150                   (setq path `(cdr ,path)
151                         minimum (1+ minimum)
152                         maximum (1+ maximum)))
153                  ((eq now-processing :optionals)
154                   (when (> (length var) 3)
155                     (cerror "Ignore extra noise."
156                             "more than variable, initform, and suppliedp ~
157                             in &optional binding: ~S"
158                             var))
159                   (push-optional-binding (car var) (cadr var) (caddr var)
160                                          `(not (null ,path)) `(car ,path)
161                                          name error-kind error-fun)
162                   (setq path `(cdr ,path)
163                         maximum (1+ maximum)))
164                  ((eq now-processing :keywords)
165                   (let* ((keyword-given (consp (car var)))
166                          (variable (if keyword-given
167                                        (cadar var)
168                                        (car var)))
169                          (keyword (if keyword-given
170                                       (caar var)
171                                       (keywordicate variable)))
172                          (supplied-p (caddr var)))
173                     (push-optional-binding variable (cadr var) supplied-p
174                                            `(keyword-supplied-p ',keyword
175                                                                 ,rest-name)
176                                            `(lookup-keyword ',keyword
177                                                             ,rest-name)
178                                            name error-kind error-fun)
179                     (push keyword keys)))
180                  ((eq now-processing :auxs)
181                   (push-let-binding (car var) (cadr var) nil))))
182               ((symbolp var)
183                (cond ; (too early in bootstrapping to use CASE)
184                 ;; FIXME: ^ This "too early in bootstrapping" is no
185                 ;; longer an issue in current SBCL bootstrapping.
186                  ((eq now-processing :required)
187                   (push-let-binding var `(car ,path) nil)
188                   (setq minimum (1+ minimum)
189                         maximum (1+ maximum)
190                         path `(cdr ,path)))
191                  ((eq now-processing :optionals)
192                   (push-let-binding var `(car ,path) nil `(not (null ,path)))
193                   (setq path `(cdr ,path)
194                         maximum (1+ maximum)))
195                  ((eq now-processing :keywords)
196                   (let ((key (keywordicate var)))
197                     (push-let-binding var
198                                       `(lookup-keyword ,key ,rest-name)
199                                       nil)
200                     (push key keys)))
201                  ((eq now-processing :auxs)
202                   (push-let-binding var nil nil))))
203               (t
204                (error "non-symbol in lambda-list: ~S" var)))))
205     (push `(unless ,(if restp
206                         ;; (If RESTP, then the argument list might be
207                         ;; dotted, in which case ordinary LENGTH won't
208                         ;; work.)
209                         `(list-of-length-at-least-p ,path-0 ,minimum)
210                         `(proper-list-of-length-p ,path-0 ,minimum ,maximum))
211              ,(if (eq error-fun 'error)
212                   `(do-arg-count-error ',error-kind ',name ,path-0
213                                        ',lambda-list ,minimum
214                                        ,(unless restp maximum))
215                   `(,error-fun 'defmacro-ll-arg-count-error
216                                :kind ',error-kind
217                                ,@(when name `(:name ',name))
218                                :argument ,path-0
219                                :lambda-list ',lambda-list
220                                :minimum ,minimum
221                                ,@(unless restp
222                                    `(:maximum ,maximum)))))
223           *arg-tests*)
224     (when keys
225       (let ((problem (gensym "KEY-PROBLEM-"))
226             (info (gensym "INFO-")))
227         (push `(multiple-value-bind (,problem ,info)
228                    (verify-keywords ,rest-name
229                                     ',keys
230                                     ',allow-other-keys-p)
231                  (when ,problem
232                    (,error-fun
233                     'defmacro-ll-broken-key-list-error
234                     :kind ',error-kind
235                     ,@(when name `(:name ',name))
236                     :problem ,problem
237                     :info ,info)))
238               *arg-tests*)))
239     (values env-arg-used minimum (if (null restp) maximum nil))))
240
241 (defun push-sub-list-binding (variable path object name error-kind error-fun)
242   (let ((var (gensym "TEMP-")))
243     (push `(,variable
244             (let ((,var ,path))
245               (if (listp ,var)
246                 ,var
247                 (,error-fun 'defmacro-bogus-sublist-error
248                             :kind ',error-kind
249                             ,@(when name `(:name ',name))
250                             :object ,var
251                             :lambda-list ',object))))
252           *system-lets*)))
253
254 (defun push-let-binding (variable path systemp &optional condition
255                                   (init-form *default-default*))
256   (let ((let-form (if condition
257                       `(,variable (if ,condition ,path ,init-form))
258                       `(,variable ,path))))
259     (if systemp
260       (push let-form *system-lets*)
261       (push let-form *user-lets*))))
262
263 (defun push-optional-binding (value-var init-form supplied-var condition path
264                                         name error-kind error-fun)
265   (unless supplied-var
266     (setq supplied-var (gensym "SUPPLIEDP-")))
267   (push-let-binding supplied-var condition t)
268   (cond ((consp value-var)
269          (let ((whole-thing (gensym "OPTIONAL-SUBLIST-")))
270            (push-sub-list-binding whole-thing
271                                   `(if ,supplied-var ,path ,init-form)
272                                   value-var name error-kind error-fun)
273            (parse-defmacro-lambda-list value-var whole-thing name
274                                        error-kind error-fun)))
275         ((symbolp value-var)
276          (push-let-binding value-var path nil supplied-var init-form))
277         (t
278          (error "Illegal optional variable name: ~S" value-var))))
279
280 (defun defmacro-error (problem kind name)
281   (error "Illegal or ill-formed ~A argument in ~A~@[ ~S~]."
282          problem kind name))
283
284 ;;; Determine whether KEY-LIST is a valid list of keyword/value pairs. Do not
285 ;;; signal the error directly, 'cause we don't know how it should be signaled.
286 (defun verify-keywords (key-list valid-keys allow-other-keys)
287   (do ((already-processed nil)
288        (unknown-keyword nil)
289        (remaining key-list (cddr remaining)))
290       ((null remaining)
291        (if (and unknown-keyword
292                 (not allow-other-keys)
293                 (not (lookup-keyword :allow-other-keys key-list)))
294            (values :unknown-keyword (list unknown-keyword valid-keys))
295            (values nil nil)))
296     (cond ((not (and (consp remaining) (listp (cdr remaining))))
297            (return (values :dotted-list key-list)))
298           ((null (cdr remaining))
299            (return (values :odd-length key-list)))
300           ((member (car remaining) already-processed)
301            (return (values :duplicate (car remaining))))
302           ((or (eq (car remaining) :allow-other-keys)
303                (member (car remaining) valid-keys))
304            (push (car remaining) already-processed))
305           (t
306            (setq unknown-keyword (car remaining))))))
307
308 (defun lookup-keyword (keyword key-list)
309   (do ((remaining key-list (cddr remaining)))
310       ((endp remaining))
311     (when (eq keyword (car remaining))
312       (return (cadr remaining)))))
313
314 (defun keyword-supplied-p (keyword key-list)
315   (do ((remaining key-list (cddr remaining)))
316       ((endp remaining))
317     (when (eq keyword (car remaining))
318       (return t))))