source: trunk/abcl/src/org/armedbear/lisp/destructuring-bind.lisp

Last change on this file was 15569, checked in by Mark Evenson, 2 years ago

Untabify en masse

Results of running style.org source blocks on tree

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 22.6 KB
Line 
1;;; destructuring-bind.lisp
2;;;
3;;; Copyright (C) 2003-2005 Peter Graves
4;;; $Id: destructuring-bind.lisp 15569 2022-03-19 12:50:18Z mevenson $
5;;;
6;;; This program is free software; you can redistribute it and/or
7;;; modify it under the terms of the GNU General Public License
8;;; as published by the Free Software Foundation; either version 2
9;;; of the License, or (at your option) any later version.
10;;;
11;;; This program is distributed in the hope that it will be useful,
12;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
13;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14;;; GNU General Public License for more details.
15;;;
16;;; You should have received a copy of the GNU General Public License
17;;; along with this program; if not, write to the Free Software
18;;; Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19;;;
20;;; As a special exception, the copyright holders of this library give you
21;;; permission to link this library with independent modules to produce an
22;;; executable, regardless of the license terms of these independent
23;;; modules, and to copy and distribute the resulting executable under
24;;; terms of your choice, provided that you also meet, for each linked
25;;; independent module, the terms and conditions of the license of that
26;;; module.  An independent module is a module which is not derived from
27;;; or based on this library.  If you modify this library, you may extend
28;;; this exception to your version of the library, but you are not
29;;; obligated to do so.  If you do not wish to do so, delete this
30;;; exception statement from your version.
31
32;;;; Adapted from CMUCL/SBCL.
33
34(in-package #:system)
35
36(export '(parse-body))
37
38(defun parse-body (body &optional (doc-string-allowed t))
39  (let ((decls ())
40        (doc nil))
41    (do ((tail body (cdr tail)))
42        ((endp tail)
43         (values tail (nreverse decls) doc))
44      (let ((form (car tail)))
45        (cond ((and (stringp form) (cdr tail))
46               (if doc-string-allowed
47                   (setq doc form
48                         ;; Only one doc string is allowed.
49                         doc-string-allowed nil)
50                   (return (values tail (nreverse decls) doc))))
51              ((not (and (consp form) (symbolp (car form))))
52               (return (values tail (nreverse decls) doc)))
53              ((eq (car form) 'declare)
54               (push form decls))
55              (t
56               (return (values tail (nreverse decls) doc))))))))
57
58;; We don't have DEFVAR yet...
59(eval-when (:compile-toplevel :load-toplevel :execute)
60  (%defvar '*arg-tests* ())
61  (%defvar '*system-lets* ())
62  (%defvar '*user-lets* ())
63  (%defvar '*ignorable-vars* ())
64  (%defvar '*env-var* nil))
65
66(defun arg-count-error (error-kind name arg lambda-list minimum maximum)
67  (declare (ignore error-kind arg lambda-list minimum maximum))
68  (error 'program-error
69         :format-control "Wrong number of arguments for ~S."
70         :format-arguments (list name)))
71
72(defun bogus-sublist-error  (&key kind name object lambda-list)
73  (error 'program-error
74         :format-control "Error while parsing arguments to ~A ~S:~%Bogus sublist:~%  ~S~%to satisfy lambda-list:~%  ~:S~%"
75         :format-arguments (list kind name object lambda-list)))
76 
77(defun lambda-list-broken-key-list-error (&key kind name problem info)
78  (error 'program-error
79         :format-control (concatenate 'string "Error while parsing arguments to ~A ~S:~%"
80                                      (ecase problem
81                                        (:dotted-list
82                                         "Keyword/value list is dotted: ~S")
83                                        (:odd-length
84                                         "Odd number of elements in keyword/value list: ~S")
85                                        (:duplicate
86                                         "Duplicate keyword: ~S")
87                                        (:unknown-keyword
88                                         "~{Unknown keyword: ~S ; expected one of ~{~S~^, ~}~}")))
89         :format-arguments (list kind name info)))
90
91;;; Return, as multiple values, a body, possibly a DECLARE form to put
92;;; where this code is inserted, the documentation for the parsed
93;;; body, and bounds on the number of arguments.
94(defun parse-defmacro (lambda-list arg-list-name body name context
95                                   &key
96                                   (anonymousp nil)
97                                   (doc-string-allowed t)
98                                   ((:environment env-arg-name))
99                                   (error-fun 'error)
100                                   (wrap-block t))
101  (multiple-value-bind (forms declarations documentation)
102      (parse-body body doc-string-allowed)
103    (let ((*arg-tests* ())
104          (*user-lets* ())
105          (*system-lets* ())
106          (*ignorable-vars* ())
107          (*env-var* nil))
108      (multiple-value-bind (env-arg-used minimum maximum)
109          (parse-defmacro-lambda-list lambda-list arg-list-name name
110                                      context error-fun (not anonymousp)
111                                      nil)
112        (values `(let* (,@(when env-arg-used
113                            `((,*env-var* ,env-arg-name)))
114                        ,@(nreverse *system-lets*))
115                   ,@(when *ignorable-vars*
116                       `((declare (ignorable ,@*ignorable-vars*))))
117                   ,@*arg-tests*
118                   (let* ,(nreverse *user-lets*)
119                     ,@declarations
120                     ,@(if wrap-block
121                           `((block ,(fdefinition-block-name name) ,@forms))
122                           forms)))
123                `(,@(when (and env-arg-name (not env-arg-used))
124                      `((declare (ignore ,env-arg-name)))))
125                documentation
126                minimum
127                maximum)))))
128
129(defun defmacro-error (problem name)
130  (error 'type-error "~S is not of type ~S~%" problem name))
131
132(defun verify-keywords (key-list valid-keys allow-other-keys)
133  (do ((already-processed nil)
134       (unknown-keyword nil)
135       (remaining key-list (cddr remaining)))
136      ((null remaining)
137       (if (and unknown-keyword
138                (not allow-other-keys)
139                (not (lookup-keyword :allow-other-keys key-list)))
140           (values :unknown-keyword (list unknown-keyword valid-keys))
141           (values nil nil)))
142    (cond ((not (and (consp remaining) (listp (cdr remaining))))
143           (return (values :dotted-list key-list)))
144          ((null (cdr remaining))
145           (return (values :odd-length key-list)))
146          ((or (eq (car remaining) :allow-other-keys)
147               (memql (car remaining) valid-keys))
148           (push (car remaining) already-processed))
149          (t
150           (setq unknown-keyword (car remaining))))))
151
152(defun lookup-keyword (keyword key-list)
153  (do ((remaining key-list (cddr remaining)))
154      ((endp remaining))
155    (when (eq keyword (car remaining))
156      (return (cadr remaining)))))
157
158(defun keyword-supplied-p (keyword key-list)
159  (do ((remaining key-list (cddr remaining)))
160      ((endp remaining))
161    (when (eq keyword (car remaining))
162      (return t))))
163
164(defun dot-length (cons)
165  (do ((rest cons (cdr rest))
166       (length 0 (1+ length)))
167      ((or (null rest) (atom rest)) length)))
168
169(defun parse-defmacro-lambda-list
170       (lambda-list arg-list-name name error-kind error-fun
171                    &optional top-level env-illegal ;;env-arg-name
172                    )
173  (let* ((path-0 (if top-level `(cdr ,arg-list-name) arg-list-name))
174         (path path-0)
175         (now-processing :required)
176         (maximum 0)
177         (minimum 0)
178         (keys ())
179         rest-name restp allow-other-keys-p env-arg-used)
180    ;; This really strange way to test for &WHOLE is necessary because MEMBER
181    ;; does not have to work on dotted lists, and dotted lists are legal
182    ;; in lambda lists.
183    (when (and (do ((list lambda-list (cdr list)))
184                   ((atom list) nil)
185                 (when (eq (car list) '&WHOLE) (return t)))
186               (not (eq (car lambda-list) '&WHOLE)))
187      (error "&Whole must appear first in ~S lambda-list." error-kind))
188    (do ((rest-of-args lambda-list (cdr rest-of-args)))
189        ((atom rest-of-args)
190         (cond ((null rest-of-args) nil)
191               ;; Varlist is dotted, treat as &rest arg and exit.
192               (t (push-let-binding rest-of-args path nil)
193                  (setq restp t))))
194      (let ((var (car rest-of-args)))
195        (cond ((eq var '&whole)
196               (cond ((and (cdr rest-of-args) (symbolp (cadr rest-of-args)))
197                      (setq rest-of-args (cdr rest-of-args))
198                      (push-let-binding (car rest-of-args) arg-list-name nil))
199                     ((and (cdr rest-of-args) (consp (cadr rest-of-args)))
200                      (pop rest-of-args)
201                      (let* ((destructuring-lambda-list (car rest-of-args))
202                             (sub (gensym "WHOLE-SUBLIST")))
203                        (push-sub-list-binding
204                         sub arg-list-name destructuring-lambda-list
205                         name error-kind error-fun)
206                        (parse-defmacro-lambda-list
207                         destructuring-lambda-list sub name error-kind error-fun)))
208                     (t
209                      (defmacro-error "&WHOLE" name))))
210              ((eq var '&environment)
211               (cond (env-illegal
212                      (error "&ENVIRONMENT is not valid with ~S." error-kind))
213                     ((not top-level)
214                      (error "&ENVIRONMENT is only valid at top level of lambda list.")))
215               (cond ((and (cdr rest-of-args) (symbolp (cadr rest-of-args)))
216                      (setq rest-of-args (cdr rest-of-args))
217                      (setq *env-var* (car rest-of-args)
218                            env-arg-used t))
219                     (t
220                      (defmacro-error "&ENVIRONMENT" name))))
221              ((or (eq var '&rest) (eq var '&body))
222               (cond ((and (cdr rest-of-args) (symbolp (cadr rest-of-args)))
223                      (setq rest-of-args (cdr rest-of-args))
224                      (setq restp t)
225                      (push-let-binding (car rest-of-args) path nil))
226                     ((and (cdr rest-of-args) (consp (cadr rest-of-args)))
227                      (pop rest-of-args)
228                      (setq restp t)
229                      (let* ((destructuring-lambda-list (car rest-of-args))
230                             (sub (gensym "REST-SUBLIST")))
231                        (push-sub-list-binding sub path destructuring-lambda-list
232                                               name error-kind error-fun)
233                        (parse-defmacro-lambda-list
234                         destructuring-lambda-list sub name error-kind error-fun)))
235                     (t
236                      (defmacro-error (symbol-name var) name))))
237              ((eq var '&optional)
238               (setq now-processing :optionals))
239              ((eq var '&key)
240               (setq now-processing :keywords)
241               (setq rest-name (gensym "KEYWORDS-"))
242               (push rest-name *ignorable-vars*)
243               (setq restp t)
244               (push-let-binding rest-name path t))
245              ((eq var '&allow-other-keys)
246               (setq allow-other-keys-p t))
247              ((eq var '&aux)
248               (setq now-processing :auxs))
249              ((listp var)
250               (case now-processing
251                 (:required
252                  (let ((sub-list-name (gensym "SUBLIST-")))
253                    (push-sub-list-binding sub-list-name `(car ,path) var
254                                           name error-kind error-fun)
255                    (parse-defmacro-lambda-list var sub-list-name name
256                                                error-kind error-fun))
257                  (setq path `(cdr ,path))
258                  (incf minimum)
259                  (incf maximum))
260                 (:optionals
261                  (when (> (length var) 3)
262                    (error "more than variable, initform, and suppliedp in &optional binding ~S"
263                           var))
264                  (push-optional-binding (car var) (cadr var) (caddr var)
265                                         `(not (null ,path)) `(car ,path)
266                                         name error-kind error-fun)
267                  (setq path `(cdr ,path))
268                  (incf maximum))
269                 (:keywords
270                  (let* ((keyword-given (consp (car var)))
271                         (variable (if keyword-given
272                                       (cadar var)
273                                       (car var)))
274                         (keyword (if keyword-given
275                                      (caar var)
276                                      (make-keyword variable)))
277                         (supplied-p (caddr var)))
278                    (push-optional-binding variable (cadr var) supplied-p
279                                           `(keyword-supplied-p ',keyword
280                                                                ,rest-name)
281                                           `(lookup-keyword ',keyword
282                                                            ,rest-name)
283                                           name error-kind error-fun)
284                    (push keyword keys)))
285                 (:auxs (push-let-binding (car var) (cadr var) nil))))
286              ((symbolp var)
287               (case now-processing
288                 (:required
289                  (incf minimum)
290                  (incf maximum)
291                  (push-let-binding var `(car ,path) nil)
292                  (setq path `(cdr ,path)))
293                 (:optionals
294                  (incf maximum)
295                  (push-let-binding var `(car ,path) nil `(not (null ,path)))
296                  (setq path `(cdr ,path)))
297                 (:keywords
298                  (let ((key (make-keyword var)))
299                    (push-let-binding var `(lookup-keyword ,key ,rest-name)
300                                      nil)
301                    (push key keys)))
302                 (:auxs
303                  (push-let-binding var nil nil))))
304              (t
305               (error "non-symbol in lambda-list: ~S" var)))))
306    ;; Generate code to check the number of arguments.
307    (push `(unless (<= ,minimum
308                       (dot-length ,path-0)
309                       ,@(unless restp
310                           (list maximum)))
311             ,(if (eq error-fun 'error)
312                  `(arg-count-error ',error-kind ',name ,path-0
313                                    ',lambda-list ,minimum
314                                    ,(unless restp maximum))
315                  `(,error-fun 'arg-count-error
316                    :kind ',error-kind
317                    ,@(when name `(:name ',name))
318                    :argument ,path-0
319                    :lambda-list ',lambda-list
320                    :minimum ,minimum
321                    ,@(unless restp `(:maximum ,maximum)))))
322          *arg-tests*)
323    (if keys
324        (let ((problem (gensym "KEY-PROBLEM-"))
325              (info (gensym "INFO-")))
326          (push `(multiple-value-bind (,problem ,info)
327                     (verify-keywords ,rest-name ',keys ',allow-other-keys-p)
328                   (when ,problem
329                     ,(if (eq error-fun 'error)
330                          `(lambda-list-broken-key-list-error 
331                           :kind ',error-kind
332                           ,@(when name `(:name ',name))
333                           :problem ,problem
334                           :info ,info)
335                          `(,error-fun
336                           'defmacro-lambda-list-broken-key-list-error
337                           :kind ',error-kind
338                           ,@(when name `(:name ',name))
339                           :problem ,problem
340                           :info ,info))))
341                *arg-tests*)))
342    (values env-arg-used minimum (if (null restp) maximum nil))))
343
344
345(defun push-sub-list-binding (variable path object name error-kind error-fun)
346  (let ((var (gensym "TEMP-")))
347    (push `(,variable
348            (let ((,var ,path))
349              (if (listp ,var)
350                  ,var
351                  ,(if (eq error-fun 'error)
352                       `(bogus-sublist-error
353                                    :kind ',error-kind
354                                    ,@(when name `(:name ',name))
355                                    :object ,var
356                                    :lambda-list ',object)
357                       `(,error-fun 'defmacro-bogus-sublist-error
358                                    :kind ',error-kind
359                                    ,@(when name `(:name ',name))
360                                    :object ,var
361                                    :lambda-list ',object)))))
362          *system-lets*)))
363
364(defun push-let-binding (variable path systemp &optional condition
365                                  (init-form nil))
366  (let ((let-form (if condition
367                      `(,variable (if ,condition ,path ,init-form))
368                      `(,variable ,path))))
369    (if systemp
370        (push let-form *system-lets*)
371        (push let-form *user-lets*))))
372
373(defun push-optional-binding (value-var init-form supplied-var condition path
374                                        name error-kind error-fun)
375  (unless supplied-var
376    (setq supplied-var (gensym "SUPPLIEDP-")))
377  (push-let-binding supplied-var condition t)
378  (cond ((consp value-var)
379         (let ((whole-thing (gensym "OPTIONAL-SUBLIST-")))
380           (push-sub-list-binding whole-thing
381                                  `(if ,supplied-var ,path ,init-form)
382                                  value-var name error-kind error-fun)
383           (parse-defmacro-lambda-list value-var whole-thing name
384                                       error-kind error-fun)))
385        ((symbolp value-var)
386         (push-let-binding value-var path nil supplied-var init-form))
387        (t
388         (error "Illegal optional variable name: ~S" value-var))))
389
390(defmacro destructuring-bind (lambda-list arg-list &rest body)
391  (let* ((arg-list-name (gensym "ARG-LIST-")))
392    (multiple-value-bind (body local-decls)
393        (parse-defmacro lambda-list arg-list-name body nil 'destructuring-bind
394                        :anonymousp t
395                        :doc-string-allowed nil
396                        :wrap-block nil)
397      `(let ((,arg-list-name ,arg-list))
398         ,@local-decls
399         ,body))))
400
401;; Redefine SYS:MAKE-MACRO-EXPANDER to use PARSE-DEFMACRO.
402(defun make-macro-expander (definition)
403  (let* ((name (car definition))
404         (lambda-list (cadr definition))
405         (form (gensym "WHOLE-"))
406         (env (gensym "ENVIRONMENT-"))
407         (body (parse-defmacro lambda-list form (cddr definition) name 'defmacro
408                               :environment env)))
409    `(lambda (,form ,env) (block ,name ,body))))
410
411#|
412These conditions might be signaled but are not defined. Probably can't define them here as clos might not be active.
413Taken from cmucl.
414
415(define-condition defmacro-lambda-list-bind-error (program-error)
416  ((kind :reader defmacro-lambda-list-bind-error-kind
417         :initarg :kind)
418   (name :reader defmacro-lambda-list-bind-error-name
419         :initarg :name
420         :initform nil)))
421
422(defun print-defmacro-ll-bind-error-intro (condition stream)
423  (if (null (defmacro-lambda-list-bind-error-name condition))
424      (format stream
425              "Error while parsing arguments to ~A in ~S:~%"
426              (defmacro-lambda-list-bind-error-kind condition)
427              (condition-function-name condition))
428      (format stream
429              "Error while parsing arguments to ~A ~S:~%"
430              (defmacro-lambda-list-bind-error-kind condition)
431              (defmacro-lambda-list-bind-error-name condition))))
432
433(define-condition defmacro-bogus-sublist-error
434                  (defmacro-lambda-list-bind-error)
435  ((object :reader defmacro-bogus-sublist-error-object :initarg :object)
436   (lambda-list :reader defmacro-bogus-sublist-error-lambda-list
437                :initarg :lambda-list))
438  (:report
439   (lambda (condition stream)
440     (print-defmacro-ll-bind-error-intro condition stream)
441     (format stream
442             "Bogus sublist:~%  ~S~%to satisfy lambda-list:~%  ~:S~%"
443             (defmacro-bogus-sublist-error-object condition)
444             (defmacro-bogus-sublist-error-lambda-list condition)))))
445
446
447
448(define-condition arg-count-error (defmacro-lambda-list-bind-error)
449  ((argument :reader defmacro-ll-arg-count-error-argument :initarg :argument)
450   (lambda-list :reader defmacro-ll-arg-count-error-lambda-list
451                :initarg :lambda-list)
452   (minimum :reader defmacro-ll-arg-count-error-minimum :initarg :minimum)
453   (maximum :reader defmacro-ll-arg-count-error-maximum :initarg :maximum))
454  (:report
455   (lambda (condition stream)
456     (print-defmacro-ll-bind-error-intro condition stream)
457     (format stream
458             "Invalid number of elements in:~%  ~:S~%~
459             to satisfy lambda-list:~%  ~:S~%"
460             (defmacro-ll-arg-count-error-argument condition)
461             (defmacro-ll-arg-count-error-lambda-list condition))
462     (cond ((null (defmacro-ll-arg-count-error-maximum condition))
463            (format stream "Expected at least ~D"
464                    (defmacro-ll-arg-count-error-minimum condition)))
465           ((= (defmacro-ll-arg-count-error-minimum condition)
466               (defmacro-ll-arg-count-error-maximum condition))
467            (format stream "Expected exactly ~D"
468                    (defmacro-ll-arg-count-error-minimum condition)))
469           (t
470            (format stream "Expected between ~D and ~D"
471                    (defmacro-ll-arg-count-error-minimum condition)
472                    (defmacro-ll-arg-count-error-maximum condition))))
473     (format stream ", but got ~D."
474             (length (defmacro-ll-arg-count-error-argument condition))))))
475
476(define-condition defmacro-lambda-list-broken-key-list-error
477                  (defmacro-lambda-list-bind-error)
478  ((problem :reader defmacro-ll-broken-key-list-error-problem
479            :initarg :problem)
480   (info :reader defmacro-ll-broken-key-list-error-info :initarg :info))
481  (:report (lambda (condition stream)
482             (print-defmacro-ll-bind-error-intro condition stream)
483             (format stream
484                     (ecase
485                         (defmacro-ll-broken-key-list-error-problem condition)
486                       (:dotted-list
487                        "Keyword/value list is dotted: ~S")
488                       (:odd-length
489                        "Odd number of elements in keyword/value list: ~S")
490                       (:duplicate
491                        "Duplicate keyword: ~S")
492                       (:unknown-keyword
493                        "~{Unknown keyword: ~S; expected one of ~{~S~^, ~}~}"))
494                     (defmacro-ll-broken-key-list-error-info condition)))))
495|#
Note: See TracBrowser for help on using the repository browser.