Next: , Up: Rolling Your Own

7.2 Writing Drivers

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)))
         (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.

— Macro: defmacro-driver arglist &body body

Defines a driver clause in both the for and generate forms, and provides a parameter generate which body can examine to determine how it was invoked. arglist is as in defmacro-clause, and should begin with the symbol for.

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)))
          (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.