From: Michael Parker
Subject: [Announce] WITH - B*stard son of LOOP
Date: 
Message-ID: <64F6F0055D14A31B.CF395EEA02EFC171.A852310C6E970B58@lp.airnews.net>
I decided to release this after reading Paul Graham's thoughts on Arc
and the ensuing discussion here re the usefulness of syntax.  I've
been using a version of this for some time now, although it has been
heavily revised recently to make it easier to extend.

Hopefully I won't catch too much flak from the LOOP-haters on the list
:-)

WITH is a macro that provides a general, conversational binding
construct analogous to what 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 etc 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 provide
convenient hooks for extending the syntax.

As an example, the function below is part of the regular expression
parser in the clawk system (it's the toplevel regex parsing routine):

(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 scanner = (new-re-scanner str)
    and vars (token value) = (next scanner)
    and seq = (parse-seq token value scanner)
    and 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))))))


The WITH macro accepts the following grammar:

    with               <- WITH bindings DO . body
    body               <- form*
    bindings           <- binding [ conjunction binding ]*
    conjunction        <- AND | ALSO
    binding            <- undeclared-binding [ declaration ]
    declaration        <- AS type |
                          DECLARE declare-clause*
    undeclared-binding <- modal-var-form assgn form
    modal-var-form     <- var |
                          mode var assgn form |
                          mode ( var* ) assgn form
    mode               <- SPECIAL |
                          VARS |
                          DESTR |
                          FN |
                          REC |
                          OPEN-FILE |
                          OUTPUT-TO-STRING |
                          INPUT-FROM-STRING |
                          other-defined-mode
   assgn               <- '=' | ':=' | '<-'

The interpretation of the conjunctions is that AND causes sequential
binding, while ALSO causes parallel binding.  At present, this is
limited to compatible modes, for example variables and specials
can be bound in parallel, but you can't bind a variable and a
function in parallel (there's just no such structure in CL).
Also, some modes (such as VARS and DESTR) aren't capable of
parallel binding at all, at least not outside their limited domain.
I hope to relax 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 in the underlying CL.

The mode is a keyword (ala LOOP, not a symbol in the keyword package)
that selects the binding mode.

At the moment, I've got the following modes defined:

special - Declares the variable to be special. 
vars - Expands to multiple-value-bind. 
destr - Expands to destructuring-bind. 
fn - Expands to flet. 
rec - Expands to labels. 
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. 

Several of these also have aliases, e.g. DESTR is also called BIND.

The binding modes are definable via the 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.
From: Michael Parker
Subject: Re: [Announce] WITH - B*stard son of LOOP
Date: 
Message-ID: <18E2D048A744D434.F18170A96704BDD1.9831F1AD42341431@lp.airnews.net>
Whoops.  Forgot to give the URL.

http://www.geocities.com/mparker762


Michael Parker wrote:
> 
> I decided to release this after reading Paul Graham's thoughts on Arc
> and the ensuing discussion here re the usefulness of syntax.  I've
> been using a version of this for some time now, although it has been
> heavily revised recently to make it easier to extend.
> 
> Hopefully I won't catch too much flak from the LOOP-haters on the list
> :-)
> 
> WITH is a macro that provides a general, conversational binding
> construct analogous to what 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 etc 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 provide
> convenient hooks for extending the syntax.
> 
> As an example, the function below is part of the regular expression
> parser in the clawk system (it's the toplevel regex parsing routine):
> 
> (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 scanner = (new-re-scanner str)
>     and vars (token value) = (next scanner)
>     and seq = (parse-seq token value scanner)
>     and 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))))))
> 
> The WITH macro accepts the following grammar:
> 
>     with               <- WITH bindings DO . body
>     body               <- form*
>     bindings           <- binding [ conjunction binding ]*
>     conjunction        <- AND | ALSO
>     binding            <- undeclared-binding [ declaration ]
>     declaration        <- AS type |
>                           DECLARE declare-clause*
>     undeclared-binding <- modal-var-form assgn form
>     modal-var-form     <- var |
>                           mode var assgn form |
>                           mode ( var* ) assgn form
>     mode               <- SPECIAL |
>                           VARS |
>                           DESTR |
>                           FN |
>                           REC |
>                           OPEN-FILE |
>                           OUTPUT-TO-STRING |
>                           INPUT-FROM-STRING |
>                           other-defined-mode
>    assgn               <- '=' | ':=' | '<-'
> 
> The interpretation of the conjunctions is that AND causes sequential
> binding, while ALSO causes parallel binding.  At present, this is
> limited to compatible modes, for example variables and specials
> can be bound in parallel, but you can't bind a variable and a
> function in parallel (there's just no such structure in CL).
> Also, some modes (such as VARS and DESTR) aren't capable of
> parallel binding at all, at least not outside their limited domain.
> I hope to relax 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 in the underlying CL.
> 
> The mode is a keyword (ala LOOP, not a symbol in the keyword package)
> that selects the binding mode.
> 
> At the moment, I've got the following modes defined:
> 
> special - Declares the variable to be special.
> vars - Expands to multiple-value-bind.
> destr - Expands to destructuring-bind.
> fn - Expands to flet.
> rec - Expands to labels.
> 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.
> 
> Several of these also have aliases, e.g. DESTR is also called BIND.
> 
> The binding modes are definable via the 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.