loop2.rkt
#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)
			  ;; Is the body needed?
			  (begin . #,(syntax-reverse body))
			  (begin .
				 #,<increment-lists>)
			  ;; If all of the loop-conditions are true, do another iteration. (add-iterations ...)
			  ;; adds incrementation clauses to those variables that iterate. So if the variable
			  ;; is defined by (loop for x from 0), x is replaced with (add1 x) in the expanded code,
			  ;; or if it's from a list, it gets replaced with (car corresponding-list). The relevant
			  ;; code-blocks are stored in the "iterations" variable.
			  (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 ]