-
Notifications
You must be signed in to change notification settings - Fork 211
expander design
The main data types are Syntax
and TermTree
:
Syntax :: {
token :: Token,
context :: Context,
mark :: (Int?) -> Syntax
rename :: (Syntax or [Syntax], Str) -> Syntax
push_dummy_rename :: (Str) -> Syntax
swap_dummy_rename :: (Syntax, Str, Str) -> Syntax
}
The Syntax
object holds the token (a holdover data structure from esprima which knows about the token's value and line numbers) and its context (a structure of marks and renames used for hygiene) along with a couple of methods to help with hygiene.
Each Syntax
object is in one-to-one correlation with an individual token.
In contrast, the TermTree
object holds a collection of of Syntax
objects in a closer and closer approximation of the final AST.
TermTree :: {
destruct :: () -> [Syntax]
}
It's basically an AST, just not as complete as the final AST generated by esprima. Productions in the grammar then are represented by individual term trees:
BinOp extends TermTree :: {
left :: TermTree
op :: TermTree
right :: TermTree
}
The main entry point into the expander is, appropriately enough, expand
:
expand :: ([Syntax], Map, Map) -> [TermTree]
function expand(toks, env, ctx) { ... }
The expand
function is primarily responsible for handling hygiene. Most of its work happens when dealing with functions (renaming all the parameters and whatnot). The env
param is a mapping from identifiers to macro definitions and ctx
is a mapping of names to names (used for hygiene).
The expand
function delegates to two sub expand like functions:
expandToTermTree :: ([Syntax], Map) -> {terms: [TermTree], env: Map}
Responsible for converting the syntax to TermTrees and loading any macro definitions it finds into the new env
map.
expandTermTreeToFinal :: (TermTree, Map, Map) -> TermTree
Responsible for packing each term tree into its final state. This means recursively expanding sub trees and handling various hygiene tasks.
These are roughly analogous to parse1
and parse2
in the honu paper.
The enforest
function deals with transforming Syntax
objects to a TermTree
:
enforest :: ([Syntax], Map) -> {
result :: TermTree
rest :: [Syntax]
}
The main contracts for honu:
parse :: ([Syntax], Env) -> AST
parse1 :: ([Syntax], Env) -> ([TermTree], Env)
parse2 :: ([TermTree], Env) -> AST
enforest :: ([Syntax], Env) -> (TermTree, [Syntax])
data TermTree = Macro Str [Syntax]
| Expr [TermTree]
| Bop TermTree Str TermTree
| Fun Str [TermTree] [Syntax]
| Block [Syntax]
| Id Syntax
| Lit Syntax
| Call [TermTree] [TermTree]
Some partial pattern matching definitions in a haskelly notation:
parse :: ([Syntax], Env) -> AST
parse stx env = let (terms, env') = parse1 stx env
in parse2 terms env'
parse1 :: [Syntax] -> Env -> ([TermTree], Env)
parse1 [] env = ([], env)
parse1 in env =
case (enforest in env) of
(Macro n b, rest) -> parse1 rest (update n (loadm b))
(Expr e, rest) -> (Expr e) : (parse1 rest env)
-- case when var declaration (somewhat complicated)
parse2 :: [TermTree] -> Env -> AST
parse2 forms env = map parse2' forms
parse2' (Fun n vs b) env = FunAST n vs (parse b (aug env vs))
parse2' (Block b) env = BlockAST (parse b env)
parse2' (Id i) env = IdAST i
parse2' (Lit l) env = LitAST l
parse2' (Call l r) env = CallAST (parse2 l env) (parse2 r env)
parse2' (Bop l o r) env = BopAST (parse2 l env) o (parse2 r env)
And the enforest
function itself:
data Syntax = LitR Int
| IdR Str
| BinopR Str
| ParenR [Syntax]
| ...
enforest :: [Syntax] -> Env -> (TermTree, [Syntax])
enforest (LitR n:rest) env = enforest (Lit n:rest) env
enforest (IdR i:rest) env
| lookup i env == Var id = enforest (Id id:rest) env
| lookup i env == Macro t = enforest (t rest) env
enforest (ltree:(IdR i):rest) env
| lookup i env == BopR op = case enforest rest env of
(rtree, rest) -> enforest (Bop ltree op rtree:rest) env
-- similar for uop
enforest (ltree:ParenR args:rest)
| -- each arg must enforest to have no `rest`
= enforest (Call ltree argtrees:rest) env