1 ;;; lambda-list.lisp --- Lambda list parsing and destructuring
3 ;; JSCL is free software: you can redistribute it and/or
4 ;; modify it under the terms of the GNU General Public License as
5 ;; published by the Free Software Foundation, either version 3 of the
6 ;; License, or (at your option) any later version.
8 ;; JSCL is distributed in the hope that it will be useful, but
9 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
10 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 ;; General Public License for more details.
13 ;; You should have received a copy of the GNU General Public License
14 ;; along with JSCL. If not, see <http://www.gnu.org/licenses/>.
16 ;;; lambda-list-keywords
17 ;; '(&optional &rest &key &aux &allow-other-keys &body &optional)
19 ;;;; Lambda list parsing
22 variable initform supplied-p-parameter)
25 variable keyword-name initform supplied-p-parameter)
30 (def!struct d-lambda-list
39 (defun var-or-pattern (x)
42 (cons (parse-destructuring-lambda-list x))))
44 (defun parse-optvar (desc)
47 (make-optvar :variable desc))
49 (let ((variable (first desc))
50 (initform (second desc))
51 (supplied-p-parameter (third desc)))
52 (unless (null (cdddr desc))
53 (error "Bad optional parameter specification `~S'" desc))
54 (unless (symbolp supplied-p-parameter)
55 (error "`~S' is not a valid supplied optional parameter." supplied-p-parameter))
56 (make-optvar :variable (var-or-pattern variable)
58 :supplied-p-parameter supplied-p-parameter)))))
60 (defun parse-keyvar (desc)
63 (make-keyvar :variable desc :keyword-name (intern (string desc) "KEYWORD")))
67 (initform (second desc))
68 (supplied-p-parameter (third desc)))
69 (unless (null (cdddr desc))
70 (error "Bad keyword parameter specification `~S'" desc))
71 (unless (symbolp supplied-p-parameter)
72 (error "`~S' is not a valid supplied optional parameter." supplied-p-parameter))
73 (let ((name (first desc)))
76 (setq keyword-name (intern (string name) "KEYWORD"))
79 (unless (null (cddr name))
80 (error "Bad keyword argument name description `~S'" name))
81 (setq keyword-name (first name))
82 (setq variable (second name)))))
83 (unless (symbolp keyword-name)
84 (error "~S is not a valid keyword-name." keyword-name))
85 (make-keyvar :variable (var-or-pattern variable)
86 :keyword-name keyword-name
88 :supplied-p-parameter supplied-p-parameter)))))
90 (defun parse-auxvar (desc)
93 (make-auxvar :variable desc))
95 (let ((variable (first desc))
96 (initform (second desc)))
97 (unless (null (cdddr desc))
98 (error "Bad aux variable specification `~S'" desc))
99 (make-auxvar :variable (var-or-pattern variable)
100 :initform initform)))))
103 (defun parse-destructuring-lambda-list (lambda-list)
104 (let (;; Destructured lambda list structure where we accumulate the
105 ;; results of the parsing.
106 (d-ll (make-d-lambda-list))
107 ;; List of lambda list keywords which we have already seen.
108 (lambda-keywords nil))
109 (flet (;; Check if we are in the beginning of the section NAME in
110 ;; the lambda list. It checks also if the section is in the
111 ;; proper place and it is new.
112 (lambda-section (name)
113 (let ((section (first lambda-list)))
114 (when (find section lambda-keywords)
115 (error "Bad placed ~a in the lambda-list ~S." section lambda-list))
116 (when (eq name section)
117 (push name lambda-keywords)
120 ;; Check if we are in the middle of a lambda list section,
121 ;; looking for a lambda list keyword in the current
122 ;; position of the lambda list.
124 (and (consp lambda-list)
125 (not (find (first lambda-list) lambda-list-keywords)))))
128 (when (lambda-section '&whole)
129 (let ((wholevar (pop lambda-list)))
130 (setf (d-lambda-list-wholevar d-ll) (var-or-pattern wholevar))))
133 (loop while (in-section-p)
134 do (let ((var (pop lambda-list)))
135 (push (var-or-pattern var) (d-lambda-list-reqvars d-ll))))
136 (setf (d-lambda-list-reqvars d-ll)
137 (reverse (d-lambda-list-reqvars d-ll)))
140 (when (lambda-section '&optional)
141 (loop while (in-section-p)
142 do (push (parse-optvar (pop lambda-list))
143 (d-lambda-list-optvars d-ll)))
144 (setf (d-lambda-list-optvars d-ll)
145 (reverse (d-lambda-list-optvars d-ll))))
147 ;; Dotted lambda-list and &rest/&body vars. If the lambda-list
148 ;; is dotted. Convert it the tail to a &rest and finish.
149 (when (and lambda-list (atom lambda-list))
150 (push lambda-list (d-lambda-list-restvar d-ll))
151 (setq lambda-list nil))
152 (when (find (car lambda-list) '(&body &rest))
154 (setf (d-lambda-list-restvar d-ll)
155 (var-or-pattern (pop lambda-list))))
158 (when (lambda-section '&key)
159 (loop while (in-section-p)
160 do (push (parse-keyvar (pop lambda-list))
161 (d-lambda-list-keyvars d-ll)))
162 (setf (d-lambda-list-keyvars d-ll)
163 (reverse (d-lambda-list-keyvars d-ll))))
164 (when (lambda-section '&allow-other-keys)
165 (setf (d-lambda-list-allow-other-keys d-ll) t))
168 (when (lambda-section '&aux)
169 (loop while (in-section-p)
170 do (push (parse-auxvar (pop lambda-list))
171 (d-lambda-list-auxvars d-ll)))
172 (setf (d-lambda-list-auxvars d-ll)
173 (reverse (d-lambda-list-auxvars d-ll))))
179 ;;; Return T if KEYWORD is supplied in the list of arguments LIST.
180 (defun keyword-supplied-p (keyword list)
182 for (key value) on list by #'cddr
183 thereis (eq key keyword)))
185 ;;; Return the value of KEYWORD in the list of arguments LIST or NIL
186 ;;; if it is not supplied.
187 (defun keyword-lookup (keyword list)
189 for (key value) on list by #'cddr
190 when (eq key keyword) do (return value)))
192 ;;; Validate a list of keyword arguments.
193 (defun validate-keyvars (list keyword-list &optional allow-other-keys)
194 (let (;; If it is non-NIL, we have to check for unknown keyword
195 ;; arguments in the list to signal an error in that case.
197 (or allow-other-keys (keyword-lookup :allow-other-keys list))))
198 (unless allow-other-keys
200 for (key value) on list by #'cddr
201 unless (find key keyword-list)
202 do (error "Unknown keyword argument `~S'." key))))
204 for (key . tail) on list by #'cddr
205 unless (symbolp key) do
206 (error "Keyword argument `~S' is not a symbol." key)
207 unless (consp tail) do
208 (error "Odd number of keyword arguments."))))
210 (defmacro !destructuring-bind (lambda-list expression &body body)
211 (multiple-value-bind (d-ll)
212 (parse-destructuring-lambda-list lambda-list)
213 (let ((reqvar-count (length (d-lambda-list-reqvars d-ll)))
214 (optvar-count (length (d-lambda-list-optvars d-ll)))
216 (labels ( ;; Return a chain of the form (CAR (CDR (CDR ... (CDR X))), such that
217 ;; there are N calls to CDR.
218 (nth-chain (x n &optional tail)
220 (if (zerop n) x `(cdr ,(nth-chain x (1- n) t)))
221 `(car ,(nth-chain x n t))))
222 ;; Compute the bindings for a pattern against FORM. If
223 ;; PATTERN is a lambda-list the pattern is bound to an
224 ;; auxiliary variable, otherwise PATTERN must be a
225 ;; symbol it will be bound to the form. The variable
226 ;; where the form is bound is returned.
227 (compute-pbindings (pattern form)
230 ;; Bind the symbol to FORM.
232 (push `(,pattern ,form) bindings)
234 ;; Bind FORM to a auxiliar variable and bind
235 ;; pattern agains it recursively.
237 (let ((subpart (gensym)))
242 (compute-bindings pattern subpart)
245 ;; Compute the bindings for the full D-LAMBDA-LIST d-ll
247 (compute-bindings (d-ll form)
248 (compute-pbindings (d-lambda-list-wholevar d-ll) form)
251 (dolist (reqvar (d-lambda-list-reqvars d-ll))
252 (compute-pbindings reqvar (nth-chain form count))
255 (dolist (optvar (d-lambda-list-optvars d-ll))
256 (when (optvar-supplied-p-parameter optvar)
257 (compute-pbindings (optvar-supplied-p-parameter optvar)
258 `(not (null ,(nth-chain form count t)))))
259 (compute-pbindings (optvar-variable optvar)
260 `(if (null ,(nth-chain form count t))
261 ,(optvar-initform optvar)
262 ,(nth-chain form count)))
265 ;; Rest-variable and keywords
266 (when (or (d-lambda-list-restvar d-ll)
267 (d-lambda-list-keyvars d-ll))
268 ;; If there is a rest or keyword variable, we
269 ;; will add a binding for the rest or an
270 ;; auxiliary variable. The computations in of the
271 ;; keyword start in this variable, so we avoid
272 ;; the long tail of nested CAR/CDR operations
274 (let* ((chain (nth-chain form (+ reqvar-count optvar-count) t))
275 (pattern (or (d-lambda-list-restvar d-ll) (gensym)))
276 (rest (compute-pbindings pattern chain)))
277 (dolist (keyvar (d-lambda-list-keyvars d-ll))
278 (let ((variable (keyvar-variable keyvar))
279 (keyword (keyvar-keyword-name keyvar))
280 (supplied (or (keyvar-supplied-p-parameter keyvar)
283 (compute-pbindings supplied `(keyword-supplied-p ,keyword ,rest)))
284 (compute-pbindings variable `(if ,supplied
285 (keyword-lookup ,keyword ,rest)
286 ,(keyvar-initform keyvar)))))))
289 (dolist (auxvar (d-lambda-list-auxvars d-ll))
290 (compute-pbindings (auxvar-variable auxvar) (auxvar-initform auxvar))))))
292 ;; Macroexpansion. Compute bindings and generate code for them
293 ;; and some necessary checking.
294 (let ((whole (gensym)))
295 (compute-bindings d-ll whole)
296 `(let ((,whole ,expression))
297 (let* ,(reverse bindings)
302 (defmacro destructuring-bind (lambda-list expression &body body)
303 `(!destructuring-bind ,lambda-list ,expression ,@body))