-------------------------------------------------------------------------------- -- -- This library walks AST to gather information about the identifiers -- in it. It classifies them between free variables and bound -- variables, and keeps track of which AST node created a given bound -- variable occurence. -- -- walk_id (kind, ast) -- -- Input: -- * an AST kind: 'expr', 'stat', 'block', 'expr_list', 'binder_list', 'guess' -- * an AST of the corresponding kind. -- -- > string, AST -- -- Output: a table with two fields, 'bound' and 'free'; -- * free associates the name of each free variable with the list of -- all its occurences in the AST. That list is never empty. -- * bound associates each stat or expr binding a new variable with -- the occurences of that/those new variable(s). -- -- > { free = table (string, AST and `Id{ }); -- > bound = table (AST, table(AST and `Id{ })) } -- -- How it works -- ============ -- Walk the tree to: -- * locate open variables, and keep pointers on them so that they can -- be alpha converted. -- * locate variable bindings, so that we can find bound variables -- * locate bound variables, keep them in association with their -- binder, again in order to alpha-convert them. -- -- Special treatments: -- * `Function `Local `Localrec `Fornum `Forin have binders; -- `Local takes effect from the next statement, -- `Localrec from the current statement, -- `Function and other statments inside their bodies. -- * `Repeat has a special scoping rule for its condition. -- * blocks create temporary scopes -- * `Splice must stop the walking, so that user code won't be -- converted -- -------------------------------------------------------------------------------- -{ extension 'match' } -{ extension 'log' } require 'metalua.walk' require 'metalua.walk.scope' -- variable lists auto-create empty list as values by default. local varlist_mt = { __index = function (self, key) local x={ }; self[key] = x; return x end } local function _walk_id (kind, supercfg, ast, ...) assert(walk[kind], "Inbalid AST kind selector") assert(type(supercfg=='table'), "Config table expected") assert(type(ast)=='table', "AST expected") local cfg = { expr = { }; block = { }; stat = { } } local scope = scope:new() local visit_bound_var, visit_free_var if not supercfg.id then printf("Warning, you're using the id walker without id visitor. ".. "If you know what you want do to, then you're probably doing ".. "something else...") visit_bound_var = || nil visit_free_var = || nil else visit_free_var = supercfg.id.free or || nil visit_bound_var = supercfg.id.bound or || nil end ----------------------------------------------------------------------------- -- Check identifiers; add functions parameters to scope ----------------------------------------------------------------------------- function cfg.expr.down(x, ...) -- Execute the generic expression walker; if it breaks. -- don't do the id walking. if supercfg.expr and supercfg.expr.down then local r = supercfg.expr.down(x, ...) if r then return r end end local parents = {...} match x with | `Id{ name } -> local binder, r = scope.current[name] -- binder :: ast which bound var if binder then --$log( 'walk.id found a bound var:', x, binder) r = visit_bound_var(x, binder, unpack(parents)) else --$log( 'walk.id found a free var:', x, scope.current) r = visit_free_var(x, unpack(parents)) end if r then return r end | `Function{ params, _ } -> scope:push (params, x) | `Stat{ block, expr } -> ------------------------------------------------------------- -- 'expr' is in the scope of 'block': create the scope and -- walk the block 'manually', then prevent automatic walk -- by returning 'break'. ------------------------------------------------------------- scope:push() for stat in values (block) do walk.stat(cfg, stat, x, ...) end walk.expr(cfg, expr, x, unpack(parents)) scope:pop() return 'break' | _ -> -- pass end end ----------------------------------------------------------------------------- -- Close the function scope opened by 'down()' ----------------------------------------------------------------------------- function cfg.expr.up(x, ...) match x with `Function{...} -> scope:pop() | _ -> end if supercfg.expr and supercfg.expr.up then supercfg.expr.up(x, ...) end end ----------------------------------------------------------------------------- -- Create a new scope and register loop variable[s] in it ----------------------------------------------------------------------------- function cfg.stat.down(x, ...) -- Execute the generic statement walker; if it breaks. -- don't do the id walking. if supercfg.stat and supercfg.stat.down then local r = supercfg.stat.down(x, ...) if r then return r end end match x with | `Forin{ vars, ... } -> scope:push (vars, x) | `Fornum{ var, ... } -> scope:push ({var}, x) | `Localrec{ vars, ... } -> scope:add (vars, x) | `Repeat{ block, expr } -> ------------------------------------------------------------- -- 'expr' is in the scope of 'block': create the scope and -- walk the block 'manually', then prevent automatic walk -- by returning 'break'. ------------------------------------------------------------- scope:push() for stat in values (block) do walk.stat(cfg, stat, x, ...) end walk.expr(cfg, expr, x, ...) scope:pop() return 'break' | _ -> -- pass end end ----------------------------------------------------------------------------- -- Close the scopes opened by 'up()' ----------------------------------------------------------------------------- function cfg.stat.up(x, ...) match x with | `Forin{ ... } | `Fornum{ ... } -> scope:pop() | `Local{ vars, ... } -> scope:add(vars, x) | _ -> -- pass -- `Repeat has no up(), because it 'break's. end if supercfg.stat and supercfg.stat.up then supercfg.stat.up(x, ...) end end ----------------------------------------------------------------------------- -- Create a separate scope for each block ----------------------------------------------------------------------------- function cfg.block.down(x, ...) if supercfg.block and supercfg.block.down then local r = supercfg.block.down(x, ...) if r then return r end end scope:push() end function cfg.block.up(x, ...) scope:pop() if supercfg.block and supercfg.block.up then supercfg.block.up(x, ...) end end cfg.binder = supercfg.binder walk[kind](cfg, ast, ...) end local mt = { __index = |_,k| |...| _walk_id(k, ...) } walk_id = setmetatable({ }, mt)