loop for i to 10
print i
end
In the parser we encounter loop
. It's not a function or a special form, but it is a macro.
We pass it off to the macro transformer function, which spits out an AST node, and we add that,
instead of the original (illegal) code.
The loop macro can be defined like:
defmacro loop is
atom is for
atom var
atom is to
expr end-i
expr-body rest
body
begin
print concat concat atom->str " is: " quote var
defun iter n is
if != n 0 then
begin
set! var end-i
rest (* substitute in the loop body *)
end
iter - n 1
end
end
iter end-i
end
end
Macros can use other defined macros, such as here with begin
.
Internally, macros will be transformer functions of a form:
val macroTransformer (toks : Consumable) : expr
That is, take a stream of tokens as input, and return an AST node.
However, this transformer will need to make the required calls to parse
in order to ensure macro uses in their bodies are properly parsed.
In the interpreter, we store ASTs instead of token streams, so this works out.
The parse
function will take in streams of tokens and output an AST node.
If we parse the body of the macro above, we'll evaluate any macros it uses and
splice it into the resulting AST, which will then be stored internally for the macro.
type macro = {name : string; transformer : (Consumable -> expr) }
where transformer
is a closure that handles the body AST.