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 exit is name read: _exit or ; ditto, create on demand _exit := name("for-exit", this.macro-context) ;; The following slots contain useful context macro-context = macro-context previous-context = previous-context scope = scope ;; 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, 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 ordinary values. def parse-for-clause(tokens, error?) def token = next-after-newline(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-after-newline(tokens) parser(tokens, state) else ;; Look for an iteration clause, starting with a typed-variable def var = parse-typed-variable(tokens, error?) if var def token = next-after-newline(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-after-newline(tokens) parser(tokens, var, state) else wrong-token-error(tokens, "an iteration clause name") elseif 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. defparser for-statement { ?clause is for-clause & ~^ , }+ ?:loop-body => ;; Get the parts of the loop-body def body = loop-body.body def result-1 = loop-body.result or `false` def cleanup = loop-body.cleanup def exit-1 = loop-body.exit def cleanup-clause = cleanup and `cleanup: ?cleanup` def exit-clause-1 = exit-1 and `exit: ?exit-1` ;; Get the stuff defined by any result statements that were used def exit-2 = state._exit def exit-clause-2 = exit-2 and `exit: ?exit-2` def result-2 = state.result ;; Get the stuff defined by iteration and endtest clauses if state.endtests.length = 0 state.endtests.push := `true` def vars = state.vars.bottom-up def inits = state.inits.bottom-up def steps = state.steps.bottom-up def endtests = state.endtests.bottom-up def setup = state.setup.bottom-up ;; Put it all together `block ?exit-clause-1 block ?exit-clause-2 { ^ ?setup }* def loop({ ?vars &, }*) if { ?endtests & and }* ?body loop({ ?steps &, }*) loop({ ?inits &, }*) ?result-1 ?result-2 ?cleanup-clause` ;; 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, 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. def add-for-result-statements(scope, previous-context, 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 constant-definition(scope, name(nam, previous-context), macro(fcn(_, 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) `do def ?parser(?=tokens is token-stream, ?=state is for-statement) def ?=macro-context = unique-macro-context() ?block *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) `def ?parser(?=tokens is token-stream, ?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) `def ?parser(?=tokens is token-stream, ?=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 expressions are the values to return define-for-result return [ ~^ { ?value & ~^ , }+ ] => def exit = state.exit `?exit( { ?value &, }* )` ;; 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)` def 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