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
forandgenerateforms, and provides a parametergeneratewhich 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.