Previous page Table of Contents Next page
The for macro defines a complex iteration statement. It is extensible through the use of the define-for-clause and define-for-result macros. The for-statement class provides for communication among these macros.
The features of the for statement are perhaps best explained through its source code and comments:
;; This class holds the state passed between the for statement and its extensions
defclass for-statement(macro-context, previous-context, scope)
;; The following five stacks collect information about the clauses.
;; The first three stacks must be maintained in parallel with each other.
;; The vars stack contains typed-variables, the rest contain expressions.
;; The vars are in scope in the steps and endtests, but not in the inits
;; and setup. When the vars are advanced to the steps' values, all vars
;; are redefined in parallel.
;; The setup expressions are evaluated before anything else.
vars = stack() ; iteration variables
inits = stack() ; initial value expressions for vars
steps = stack() ; next value expressions for vars
endtests = stack() ; true => continue iteration
setup = stack() ; evaluated first
;; The following slots collect information from result statements.
result-family := false ; to detect inconsistencies
result := false ; result to return
_exit is name or false := false ; exit function name
;; The following slots contain useful context
macro-context = macro-context
previous-context = previous-context
scope = scope
;; Create exit function name on demand
defun exit(state is for-statement) is name
state._exit or
state._exit := name("for-exit", state.macro-context)
;; Dictionary of all defined for-result statements keyed by simple-name.
def *for-result-statements* = dictionary()
;; This is the skeleton of the for statement.
;; The clauses and result statements are all defined as extensions.
defmacro for =>
;; The for statement will be enclosed in a block, set up the corresponding
;; compiler scope before parsing
with-new-compiler-scope
;; Create the object to hold the state for extensions.
def state = for-statement(macro-context,
get-previous-context(),
get-local-compiler-scope())
;; Define the for clause parser, which works by calling extensions
;; defined by define-for-clause. Unusually, it returns its results
;; by pushing them on the stacks rather than as an ordinary value.
defun parse-for-clause(tokens, indentation, error?)
def token = next(tokens)
if token is name
;; Look for an endtest clause defined by this name
def parser = known-definition(name(#"parse-for-?(token)-endtest-clause", token))
if parser is function
advance(tokens)
parser(tokens, indentation, state)
else
;; Look for an iteration clause, starting with a typed-variable
def var = parse-typed-variable(tokens, indentation, error?)
if var
def token = next(tokens)
;; Look for an iteration clause defined by this name
def parser = token is name and
known-definition(name(#"parse-for-?(token)-iteration-clause", token))
if parser is function
advance(tokens)
parser(tokens, indentation, var, state)
else
wrong-token-error(tokens, "an iteration clause name")
else if error?
wrong-token-error(tokens, "a variable name or an endtest clause name")
;; Define the available result statement extensions in the local scope.
add-for-result-statements(state.scope, previous-context, state)
;; Now we are ready to parse the for statement and generate the code.
;; The iteration variables are supposed to be in scope for some portions
;; of this parsing but not for other portions. That is difficult to do,
;; so we leave them out and hope that any definitions in the enclosing
;; scope that should have been shadowed do not affect the parsing. When
;; the expansion of the for macro is constructed and compiled, everything
;; will finally be in the right scope.
defparser for-statement { ?clause is for-clause & , ^^ }+ ?:body =>
;; Get the stuff defined by any result statements that were used
def exit = state._exit
def exit-clause = exit and `catch ?exit`
def result-exp = state.result
;; Get the stuff defined by iteration and endtest clauses
if state.endtests.length = 0 state.endtests.push := `true`
def variables = state.vars.bottom-up
def initials = state.inits.bottom-up
def step-exps = state.steps.bottom-up
def end-tests = state.endtests.bottom-up
def setups = state.setup.bottom-up
;; Put it all together
`block
?exit-clause
{ ^ ?setups }*
defun loop({ ?variables &, }*)
if { ?end-tests & and }*
?body
loop({ ?step-exps &, }*)
loop({ ?initials &, }*)
?result-exp`
;; Now invoke the locally defined parser to expand the macro
;; The true argument means report an error if it fails to parse
parse-for-statement(tokens, indentation, true)
;; Subroutine to define the available result statement extensions in a local scope.
;; A result statement is available if its parser is in scope in the context of the
;; caller of the for statement.
;; This could be written using for but then it would depend on itself.
defun add-for-result-statements(scope, previous-context, for-state)
def state := start-iteration(*for-result-statements*)
until end?(*for-result-statements*, state)
def nam = next-key(*for-result-statements*, state)
def fcn = next(*for-result-statements*, state)
if known-definition(name(#"parse-for-?(nam)-result", previous-context)) eq fcn
def local-name = name(nam, previous-context)
add-definition(scope, local-name,
constant-definition(scope, local-name, macro(fcn(_, _, for-state))))
state := advance(*for-result-statements*, state)
;; Define a for-result statement which will be locally available inside a for
;; statement so long as the function defined here is in scope.
defmacro define-for-result ?:name ?:pattern \=> ?:block =>
def stmt = simple-name(name)
def parser = name-in-context(#"parse-for-?(name)-result", previous-context)
def possibilities = collect-pattern-starts(list(pattern))
def err = `wrong-token-error(?=tokens, ?possibilities)`
def expander = translate-pattern(`?=tokens`, pattern, block, err)
`do
defun ?parser(?=tokens is token-stream, ?=indentation is integer, ?=state is for-statement)
def ?=macro-context = unique-macro-context()
?expander
*for-result-statements*[#?stmt] := ?parser`
;; Define a for clause which will be available when the function
;; defined here is in scope.
;; An iteration clause starts with ? and the name for the typed variable
;; followed by the name of the clause and additional pattern.
;; An endtest clause starts with an ordinary name followed by additional pattern.
;; A clause parses itself into the stacks in the state.
defmacro define-for-clause
\? ?:variable [ is typed-variable ] ?clause is name ?:pattern \=> ?:block =>
;; This is an iteration clause
def parser = name-in-context(#"parse-for-?(clause)-iteration-clause",
previous-context)
def possibilities = collect-pattern-starts(list(pattern))
def err = `wrong-token-error(?=tokens, ?possibilities)`
def expander = translate-pattern(`?=tokens`, pattern, block, err)
`defun ?parser(?=tokens is token-stream, ?=indentation is integer,
?variable is typed-variable, ?=state is for-statement)
def ?=macro-context = unique-macro-context()
?expander`
?clause is variable ?:pattern \=> ?:block =>
;; This is an endtest clause
def parser = name-in-context(#"parse-for-?(clause)-endtest-clause",
previous-context)
def possibilities = collect-pattern-starts(list(pattern))
def err = `wrong-token-error(?=tokens, ?possibilities)`
def expander = translate-pattern(`?=tokens`, pattern, block, err)
`defun ?parser(?=tokens is token-stream, ?=indentation is integer,
?=state is for-statement)
def ?=macro-context = unique-macro-context()
?expander`
;; Define the "standard" for-clauses. The user can define more.
;; Continue iterating while a test is true
define-for-clause while ?test => state.endtests.push := test
;; Continue iterating while a test is false
define-for-clause until ?test => state.endtests.push := `not ?test`
;; Set variable to initial-value first time, next-value thereafter
define-for-clause ?var = ?initial-value then ?next-value =>
state.vars.push := var
state.inits.push := initial-value
state.steps.push := next-value
;; Iterate over elements of a sequence
define-for-clause ?var in ?sequence [ key ?key is typed-variable ] =>
;; Set up a variable to hold the sequence being iterated
state.setup.push := `def sequence = ?sequence`
;; Set up an iteration variable to hold the iteration state
state.vars.push := typed-variable(`state`, `$anything`)
state.inits.push := `start-iteration(sequence)`
state.steps.push := `advance(sequence, state)`
;; Set up an endtest to check if the iteration is exhausted
state.endtests.push := `not end?(sequence, state)`
;; Define the variable(s) of iteration in an endtest so later
;; endtests will be able to see them
def vardef = `def ?var = next(sequence, state)`
def keydef = key and `def ?key = next-key(sequence, state)`
state.endtests.push := `do
?vardef
?keydef
true`
;; Iterate over an arithmetic range
;; Either an initial-value or a limit must be specified.
;; If there is no initial-value, it defaults to 0.
;; If there is no limit, there is no endtest.
;; The increment defaults to 1. If downfrom, downto, or above
;; is used, the increment is subtracted, otherwise the increment is added.
;; The increment can be written before or after the limit.
;; The order of evaluation of initial-value, increment, and limit
;; is not guaranteed.
define-for-clause ?var [ { from | downfrom ??down } ?initial-value ]
[ { { to |
downto ??down |
below ??exclusive |
above ??down ??exclusive }
?limit } |
by ?increment ]* =>
if not initial-value and not limit
error("Either an initial-value or a limit must be specified.")
def variable = var.name
def increment1 = if increment then `by` else 1
def addsub = if down then `-` else `+`
state.vars.push := var
state.inits.push := initial-value or 0
state.steps.push := `?variable ?addsub ?increment1`
if increment ;only evaluate increment expression once
state.setup.push := `def by = ?increment`
if limit
state.setup.push := `def limit = ?limit`
def op = if down
if exclusive `>` else `>=`
else
if exclusive `<` else `<=`
state.endtests.push := `?variable ?op limit`
;; Define the "standard" for result statements. The user can define more.
;; Immediately return from the for statement
;; Any following expression is the value to return
define-for-result return [ ?value ] =>
def exit = state.exit
def result = value or `false`
`?exit( ?result )`
;; Add up numbers
define-for-result sum ?number =>
if not state.result-family
state.result-family := #sum/count
state.result := `accumulator`
state.setup.push := `def accumulator := 0`
else if state.result-family ~= #sum/count
error("Cannot mix sum/count and " + state.result-family + " result statements.")
def accumulator = state.result
`?accumulator := ?accumulator + ?number`
;; Count the number of times a test is true
define-for-result count ?test =>
if not state.result-family
state.result-family := #sum/count
state.result := `accumulator`
state.setup.push := `def accumulator := 0`
else if state.result-family ~= #sum/count
error("Cannot mix sum/count and " + state.result-family + " result statements.")
def accumulator = state.result
`if ?test ?accumulator := ?accumulator + 1`
;; Set result to minimum of occurrences of value
define-for-result minimize ?value =>
if not state.result-family
state.result-family := #minimize
state.result := `accumulator`
state.setup.push := `def accumulator := false`
else if state.result-family ~= #minimize
error("Cannot mix minimize and " + state.result-family + " result statements.")
def accumulator = state.result
`block
def temp = ?value
if not ?accumulator or temp < ?accumulator
?accumulator := temp`
;; Set result to maximum of occurrences of value
define-for-result maximize ?value =>
if not state.result-family
state.result-family := #maximize
state.result := `accumulator`
state.setup.push := `def accumulator := false`
else if state.result-family ~= #maximize
error("Cannot mix maximize and " + state.result-family + " result statements.")
def accumulator = state.result
`block
def temp = ?value
if not ?accumulator or temp > ?accumulator
?accumulator := temp`
;; Collect value into a collection that will be the result of the for statement
define-for-result collect ?value =>
if not state.result-family
state.result-family := #collect/append
state.result := `collection-stack.bottom-up`
state.setup.push := `def collection-stack = stack()`
else if state.result-family ~= #collect/append
error("Cannot mix collect/append and " + state.result-family + " result statements.")
;; The first token of state.result is the name of the collection stack.
;; This assumes the sequence constructed by `...` is actually an array.
def collection-stack = state.result[0]
`?collection-stack.push := ?value`
;; Append elements of value to a collection that will be the result of the for statement
define-for-result append ?value =>
if not state.result-family
state.result-family := #collect/append
state.result := `collection-stack.bottom-up`
state.setup.push := `def collection-stack = stack()`
else if state.result-family ~= #collect/append
error("Cannot mix collect/append and " + state.result-family + " result statements.")
def collection-stack = state.result[0]
`append-to(?collection-stack, ?value)`
defun append-to(stack is stack, value is sequence)
def state := start-iteration(value)
until end?(value, state)
stack.push := next(value, state)
state := advance(value, state)
Previous page Table of Contents Next page