Re: PARSE-BODY

Date: 28 Jul 86 13:43 PDT
From: Masinter.pa@Xerox.COM
I am (now) opposed to adding PARSE-BODY to the Common Lisp standard (as
long as the parse-body option is added to the macro expansion argument
list) for the following reasons:
a) it is of little general utility
Given the addition of &body variables for macros, I can think of no
examples where I would want to directly call parse-body instead of just
using the macro-expansion option. (I currently have lots of examples
that call parse-body, but they're all in macros that could use the new
&body options instead.)
I disagree; here's an example, of which I could provide many more:
[I named it extract-declarations in some code I wrote to avoid a possible
conflict with anybody who had already implemented a function named PARSE-BODY]
(defmacro condition-case (form &rest clauses &environment environment)
"Execute FORM with conditions handled according to CLAUSES.
Each element of CLAUSES specifies one or more condition names,
and what to do if they are signalled.
More specifically, each clause is a list:
(<conditions> <arglist> . <body-forms...>)
<conditions> is not evaluated, and should be either a condition name
or a list of condition names to be handled.
<arglist> is either a list of a single symbol specifying a variable
to be bound to the condition being handled,
or () meaning that the clause is not interested in the particular
condition object.
<body-forms> are evaluated if a condition specified by <conditions>
is signalled. The value of the last of the forms is returned from
CONDITION-CASE in this case.
The clauses are considered in order, as in TYPECASE.
If no clause is executed to handle a condition, the values of FORM are returned."
#+lispm (declare (zwei:indentation 0 3 1 1))
(flet ((lose (&rest format)
(apply #'lisp:error "Error macro-expanding ~S: ~?"
'condition-case format)))
(dolist (clause clauses)
(cond ((not (consp clause))
(lose "clauses must be lists: ~S" clause))
((not (or (null (cadr clause))
(and (consp (cadr clause))
(null (cdr (cadr clause)))
(symbolp (car (cadr clause))))))
(lose "second element of a clause must be () or a list of a variable to bind: ~S"
clause))
;... other error checks...
))
(let ((gensym (gensym)))
`(block ,gensym
;; this depends on the order in which condition-bind and condition-case
;; clauses are defined to be searched
(condition-bind
;; I get a Gold Star for not using LOOP
(,@(mapcar #'(lambda (clause)
(multiple-value-bind (body decls)
(extract-declarations (cddr clause) environment)
`(,(car clause)
#'(lambda ,(or (cadr clause) (list gensym))
(declare
,@(if (cadr clause) () `((ignore ,gensym)))
,@decls)
#+symbolics
,@(if (cadr clause) () (list gensym))
(return-from ,gensym
(progn ,@body))))))
clauses))
,form)))))
If anything, I think that PARSE-BODY is more essential than the proposed
extensions to &BODY. (I am not overly fond of the &body-extensions
because of their dissimilarity to other defmacro destructuring, but
acknowledge their usefulness)
----------------------------------------------------------------------
b) it is controversial
While every implementation will have SOMETHING like parse-body (if only
to implement the handing of macro arguments) there seems to be little
agreement on what its arguments might be or what values it might return.
It would seem that every implementation wants something slightly
different (e.g., it depends on whether you cache macro translations as
for whether you want to save the macro translation & the work of
obtaining it or recompute it.)
The `controversy' I think you will find is over rather minor (even stupid)
details. (Like order arguments and returned values, and the stuff you
cite relating to Fahlman's 4th and 5th values)
I find your last argument (about macroexpansion efficiency) rather spurious.
----------------------------------------------------------------------
c) it isn't very simple
(This is isn't a simple argument to make, unfortunately.) The value of
features is inversly proportional to the complexity of their interfaces.
Functions that have a "process-it-this-way-p" arguments and "returns 3
values, but maybe just the first" should be viewed with suspicion, that
they don't represent the "true" interface to what is going on in the
system. That is, "parse-body" is just a piece of some more complex
processing of macros, arguments & bodies that is part of the language
writers toolkit, that isn't very isolated. Motivating it (what's this
for?) would be difficult without a lot more context.
Here's what I think is a pretty-much-adequate implementation (so much
for complexity!)
(defun extract-declarations (body &optional environment doc-string-valid-p)
"Extract declarations and documentation string from BODY and return them.
The first value is what is left of BODY after any doc string and decls are
removed. It is BODY missing some number of its initial elements.
The second value is the list of declarations found.
Each element of a DECLARE found in body is a declaration
and goes on this list.
The third value is the doc string found in BODY, if there was one.
However, doc strings are only processed if DOC-STRING-VALID-P is non-NIL."
#+lmnil (declare (values body declarations doc-string))
(let (form (doc nil) (decls ()))
(loop
;; Macro-expand the form, but don't worry if we get an error.
;; In that case, we will not see it as a declaration,
;; it will get macroexpanded again, and generate a warning then.
;;>> We can't use condition:condition-case since in this toy
;;>> implementation MACROEXPAND probably doesn't signal `our' errors
(setq form #+lmnil (si:condition-case ()
(macroexpand (car body) environment)
(si:error (return)))
#+lucid ;is buggy for (macroexpand '(declare)) [bugous?]
; Of course, this means we lose if DECLARE is
; lexically defined in ENVIRONMENT.
(if (and (consp (car body))
(eq (caar body) 'declare))
(car body)
;; this is about as good as I could figure
;; out without sources or a disassembler
(let ((tem (lucid::with-error-trapping
(macroexpand (car body)
environment))))
(if (and (consp tem)
(member (car tem)
'(lisp:error lisp:cerror))
(consp (cdr tem))
(string= (cadr tem) "Error")
(consp (cddr tem))
(stringp (caddr tem))
(consp (cdddr tem))
(listp (cadddr tem)))
form
tem)))
;; other implementations probably have some way to hack
;; ignore-errors
#-(or lmnil lucid)
(macroexpand (car body) environment)
)
(cond ((and doc-string-valid-p
(stringp form))
;; If the string is the last thing in the body,
;; don't inhale it, since it needs to be the return value.
(or (cdr body) (return))
;; We skip any number of strings, but use only the first.
(or doc (setq doc form)))
((and (consp form) (eq (car form) 'declare))
;; silently ignore badly-formed declare forms. Probably should warn.
(let ((decl (remove-if-not #'consp (the list (cdr form)))))
;; hack the DOCUMENTATION declaration specially
(let ((tem (assoc 'documentation decl)))
(when (and tem doc-string-valid-p)
(when (and (null doc)
(stringp (cadr tem)))
(setq doc (cadr tem)))
(setq decl (delete 'documentation decl :key #'car))))
(if decl
;; We allow any number of DECLAREs, and process them all.
(setq decls (append decl decls)))))
(t (return)))
(pop body))
(values body decls doc)))