With - Bastard Son Of Loop
WITH is a macro that provides a general,
conversational binding construct much like loop
does for iterative constructs. It allows the programmer to
mix and match simple variable bindings, special variable
bindings, destructuring-bind, multiple-value-bind, flet,
labels, with-open-file without constantly opening up a new
indendation level for each new type of binding. It also
allows a more convenient syntax for type declarations that
places the declaration nearer to the variable being bound.
Unlike loop it doesn't try to be exhaustive, but it
does try to cover the basics, and it does allow a convenient
hook for extending the syntax.
Note: WITH has recently been revised, and the new syntax
is incompatible with the old syntax (albeit only slightly).
The new syntax uses AND as the parallel-binding conjunction,
and simple concatenation as the sequential-binding conjunction.
A side-affect of this change is that the VAR keyword for
introducing a new variable is now required. This change
has the effect of making WITH look suspiciously like ML's
LET (it probably didn't help matters that with now
also allows :: as an alternative to AS, and IN as an
alternative to DO).
If you just came here for the code, you can find it here.
As an example of with, the function below is
part of the regular expression parser in the clawk
system.
(defun parse-str (str)
(let ((scanner (new-re-scanner str)))
(multiple-value-bind (token value)
(next scanner)
(let ((seq (parse-seq token value scanner)))
(multiple-value-bind (token value)
(next scanner)
(cond ((null token) (list 'reg 0 seq))
(t (throw 'regex-parse-error
(list "Regex parse error at ~A ~A" token value)))))))))
Written using with, this becomes much more
readable:
(defun parse-str (str)
(with
var scanner = (new-re-scanner str)
vars (token value) = (next scanner)
var seq = (parse-seq token value scanner)
vars (token value) = (next scanner)
do
(cond ((null token) (list 'reg 0 seq))
(t (throw 'regex-parse-error
(list "Regex parse error at ~A ~A" token value))))))
With accepts the grammar:
with <- WITH bindings [ DO | IN ] . body
body <- form*
bindings <- binding [ conjunction binding ]*
conjunction <- AND |
binding <- undeclared-binding [ declaration ]
declaration <- [ AS | :: ] type |
DECLARE declare-clause*
undeclared-binding <- modal-var-bind-form assgn form
modal-var-bind-form<- mode var |
mode ( var+ )
mode <- VAR |
SPECIAL |
VARS |
DESTR |
FN |
REC |
OPEN-FILE |
OPEN-STREAM |
OUTPUT-TO-STRING |
INPUT-FROM-STRING |
SLOTS |
ACCESSORS |
other-defined-mode
assgn usually [ = | <- | := ], but can be defined
for each binding-mode.
(unless it doesn't, in which case you'll need to read the
source).
The interpretation of the conjunctions is that binding
clauses joined by and occur in parallel, while
simply listing binding clauses one after another causes
sequential binding. At present, parallel binding is limited
due to the underlying language, for example variables and
specials can be bound in parallel, but variables and
functions can't. Also, some modes (such as vars
and destr) aren't capable of parallel binding at
all, at least not outside their limited domain within a
single binding clause. I hope to remove this limitation in
the future, but it doesn't seem to be a problem in practice,
at least no more of a problem that it is without
with.
The mode is a keyword (with-style keyword, not
keyword-package keyword or lambda-list keyword) that selects
the binding mode.
At the moment, I've got modes defined for:
var - Declares a lexical variable.
special - Declares a special variable.
vars - Expands to multiple-value-bind.
destr - Expands to destructuring-bind.
assgn term be one of (=, <- := from in)
fn - Expands to flet.
rec - Expands to labels.
open-stream - Expands to with-open-stream.
open-file - Expands to with-open-file.
output-to-string - Expands to with-output-to-string.
input-from-string - Expands to with-input-from-string.
slots - Expands to with-slots.
assgn term is one of (in of).
accessors - Expands to with-accessors.
assgn term is one of (in of).
The binding modes are definable via
def-with-expander and def-with-alias
macros. To assist in the definition of common cases, there
are two auxiliary functions canonical-bind-expander
and canonical-with-expander. See the bottom of
with.lisp for plenty of examples.
Here's the code, in gzipped tar format.