Next: Extensibility Aids, Up: Rolling Your Own
In principle, drivers can be implemented just as easily as other
iterate
clauses. In practice, they are a little harder to get right.
As an example, consider writing a driver that iterates over all the
elements of a vector, ignoring its fill-pointer. for...
in-vector
won't work for this, because it observes the fill-pointer.
It's necessary to use array-dimension
instead of length
to obtain the size of the vector. Here is one approach:
(defmacro-clause (FOR var IN-WHOLE-VECTOR v) "All the elements of a vector (disregards fill-pointer)" (let ((vect (gensym)) (index (gensym))) `(progn (with ,vect = ,v) (for ,index from 0 below (array-dimension ,vect 0)) (for ,var = (aref ,vect ,index)))))
Note that we immediately put v
in a variable, in case it is an
expression. Again, this is just good Lisp macrology. It also has a
subtle effect on the semantics of the driver: v
is evaluated
only once, at the beginning of the loop, so changes to v
in the
loop have no effect on the driver. Similarly, the bounds for
numerical iteration e.g. the above array-dimension
are also
evaluated once only. This is how all of iterate
's drivers work.
There is an important point concerning the progn
in this code.
We need the progn
, of course, because we are returning several
forms, one of which is a driver. But iterate
drivers must occur at
top-level. Is this code in error? No, because top-level is
defined in iterate
to include forms inside a progn
. This is
just the definition of top-level that Common Lisp uses, and for the
same reason: to allow macros to return multiple forms at top-level.
While our for... in-whole-vector
clause will work, it is
not ideal. In particular, it does not support generating. Do do so,
we need to use for... next
or for... do-next
.
The job is simplified by the defmacro-driver
macro.
&body
bodyDefines a driver clause in both the
for
andgenerate
forms, and provides a parametergenerate
which body can examine to determine how it was invoked. arglist is as indefmacro-clause
, and should begin with the symbolfor
.
With defmacro-driver
, our driver looks like this:
(defmacro-driver (FOR var IN-WHOLE-VECTOR v) "All the elements of a vector (disregards fill-pointer)" (let ((vect (gensym)) (end (gensym)) (index (gensym)) (kwd (if generate 'generate 'for))) `(progn (with ,vect = ,v) (with ,end = (array-dimension ,vect 0)) (with ,index = -1) (,kwd ,var next (progn (incf ,index) (if (>= ,index ,end) (terminate)) (aref ,vect ,index))))))
We are still missing one thing: the &sequence
keywords.
We can get them easily enough, by writing
(defmacro-driver (FOR var IN-WHOLE-VECTOR v &sequence) ...)
We can now refer to parameters from
, to
, by
,
etc. which contain either the values for the corresponding keyword, or
nil
if the keyword was not supplied. Implementing the right
code for these keywords is cumbersome but not difficult; it is left as
an exercise. But before you begin, see defclause-sequence
below for an easier way.