#lang scribble/lp
@CHUNK[<*>
(require (for-syntax racket))
]
This is an implementation of Common Lisp's LOOP macro for Racket. The LOOP macro is similar to Racket's for/* macros, except all rolled into
one, and far more powerful. An example:
@racketblock[(loop for x in '(a b c d e f g)
for y from 0
when (even? y)
collect x)]
It's output: @scheme[(b d f)]
There is one very important extension to the Common Lisp standard. This version of LOOP supports a Pythonesque @scheme[yield] clause:
@racketblock[(let-values ((next-value continue) (loop for x in '(a b c d e f g) yield x))
(displayln next-value)
(continue))
]
@scheme[continue] is, perhaps not entirely obviously, a continuation that re-enters the loop.
@section{The Main Body}
The main body of the macro iterates over all the clauses and builds the following variables:
@itemlist[ @item{@scheme[finally] is the @scheme[finally] clause, which gets executed after everything else, including the @scheme[return] clause.
It does not affect the return value.}
@item{@scheme[iterations] is a syntax-list of all the variables that must be changed with each iteration of the loop, along with
a snippet of code that does the change. An example of what might be in this variable is
@scheme[#'((variable-name (add1 variable-name))
(var2 (cdr var2)))] The @scheme[iterations] get executed just as the loop is recursing into the next iteration}
@item{@scheme[current-condition] is a list of boolean clauses that is built while processing @scheme[if] clauses.}
@item{@scheme[loop-conditions] A syntax-list of conditions that will be combined with the @scheme[and]
operator to determine if the loop should continue. These are checked just before the @scheme[iterations] are executed.}
@item{@scheme[action-clauses] This is a list of action forms that will be combined with the @scheme[current-conditions] if they are
defined, otherwise they go into the @scheme[body] naked.}
@item{@scheme[current-cond-body] This is a list of clauses for a @scheme[cond] form. This
implements the @scheme[if] and @scheme[else], and @scheme[do] clauses of the loop. The @scheme[current-condition] gets added
to this list along with code from one or more action clauses.}
@item{@scheme[body] is a collection of all action clauses and @scheme[current-cond-body]s to be executed. }
@item{@scheme[list-defs] are let-bindings for any lists that are being iterated over using a @scheme[for] clause. They are
named with the @scheme[(gensym)] function.}
@item{@scheme[let-defs] are let-bindings for any variables bound with a @scheme[for] clause.}]
These variables can then be combined to form the loop itself:
@CHUNK[<loop-body>
#`(call/cc (λ (#,return-continuation)
(let local-loop ((#,collection #,initial-collection)
(#,count* #,initial-count)
(#,sum* #,initial-sum)
(#,string-accumulator #,initial-string)
#,@let-defs
#,@list-defs)
(begin . #,(syntax-reverse body))
(begin .
#,<increment-lists>)
(cond ((and . #,loop-conditions)
(local-loop #,collection #,count* #,sum* #,string-accumulator #,@(add-iterations let-vars iterations) #,@(get-let-vars lists)))
(else
(#,return-continuation (or collection count sum (void))))))))
]
The @scheme[return-continuation] is used for all exits from the loop.
All the lists being iterated over are handled separately from the recursion process. Each list has a corresponding variable that
is bound to the next element of the list via the @scheme[car] function. This binding is all that takes place during the
@scheme[iterations], and must happen after the lists themselves have been @scheme[cdr]'d off.
@CHUNK[<increment-lists>
(let unroll-lists ((list-names (get-let-vars lists))
(result #'()))
(syntax-case list-names ()
(() result)
((var . rest)
(unroll-lists rest #`((set! var (cdr var)) . #,result)))))
]
@section{Handling Conditional Statements}
Common Lisp's LOOP facility allows the use of @scheme[if] and @scheme[else] clauses that alter the behavior of the loop. Expansion of these
clauses proceeds as follows:
@itemlist[@item{Rewrite all @scheme[when] clauses as @scheme[if] clauses}
@item { Rewrite all @scheme[if foo and bar] as @scheme[if foo if bar] }
@item { Collect all consecutive @scheme[if foo] clauses into @scheme[current-condition] }
@item { If an action clause (such as @scheme[do], @scheme[collect], @scheme[count], etc) is encountered while a
@scheme[current-condition] exists, combine the action clause and the @scheme[current-condition] into
a clause that can be added to a @scheme[cond] form (@scheme[current-cond-body])@italic{.} The @scheme[and] operator
is added to the front of the @scheme[current-condition] list, unless @scheme[current-condition] is the word @scheme[else]@italic{.} }
@item { After an action clause, an @scheme[else] clause can be encountered, which goes into the @scheme[current-condition],
ultimately adding another clause to the @scheme[current-cond-body] when an action clause is encountered. }
@item { If an @scheme[end] clause is encountered, a @scheme[cond] statement is created with the @scheme[current-cond-body]
and added to the @scheme[body]@italic{.} }
@item { If an @scheme[if] clause is encountered after @scheme[if condition action-clause ...], rewrite it as if it was preceded
by @scheme[end]@italic{.} }
#:style 'ordered ]