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