1 --------------------------------------------------------------------------------
3 -- This library walks AST to gather information about the identifiers
4 -- in it. It classifies them between free variables and bound
5 -- variables, and keeps track of which AST node created a given bound
11 -- * an AST kind: 'expr', 'stat', 'block', 'expr_list', 'binder_list', 'guess'
12 -- * an AST of the corresponding kind.
16 -- Output: a table with two fields, 'bound' and 'free';
17 -- * free associates the name of each free variable with the list of
18 -- all its occurences in the AST. That list is never empty.
19 -- * bound associates each stat or expr binding a new variable with
20 -- the occurences of that/those new variable(s).
22 -- > { free = table (string, AST and `Id{ });
23 -- > bound = table (AST, table(AST and `Id{ })) }
28 -- * locate open variables, and keep pointers on them so that they can
29 -- be alpha converted.
30 -- * locate variable bindings, so that we can find bound variables
31 -- * locate bound variables, keep them in association with their
32 -- binder, again in order to alpha-convert them.
34 -- Special treatments:
35 -- * `Function `Local `Localrec `Fornum `Forin have binders;
36 -- `Local takes effect from the next statement,
37 -- `Localrec from the current statement,
38 -- `Function and other statments inside their bodies.
39 -- * `Repeat has a special scoping rule for its condition.
40 -- * blocks create temporary scopes
41 -- * `Splice must stop the walking, so that user code won't be
44 --------------------------------------------------------------------------------
46 -{ extension 'match' }
49 require 'metalua.walk'
50 require 'metalua.walk.scope'
52 -- variable lists auto-create empty list as values by default.
53 local varlist_mt = { __index = function (self, key)
54 local x={ }; self[key] = x; return x
57 local function _walk_id (kind, supercfg, ast, ...)
59 assert(walk[kind], "Inbalid AST kind selector")
60 assert(type(supercfg=='table'), "Config table expected")
61 assert(type(ast)=='table', "AST expected")
63 local cfg = { expr = { }; block = { }; stat = { } }
64 local scope = scope:new()
66 local visit_bound_var, visit_free_var
67 if not supercfg.id then
68 printf("Warning, you're using the id walker without id visitor. "..
69 "If you know what you want do to, then you're probably doing "..
71 visit_bound_var = || nil
72 visit_free_var = || nil
74 visit_free_var = supercfg.id.free or || nil
75 visit_bound_var = supercfg.id.bound or || nil
78 -----------------------------------------------------------------------------
79 -- Check identifiers; add functions parameters to scope
80 -----------------------------------------------------------------------------
81 function cfg.expr.down(x, ...)
82 -- Execute the generic expression walker; if it breaks.
83 -- don't do the id walking.
84 if supercfg.expr and supercfg.expr.down then
85 local r = supercfg.expr.down(x, ...)
86 if r then return r end
90 local binder, r = scope.current[name] -- binder :: ast which bound var
92 --$log( 'walk.id found a bound var:', x, binder)
93 r = visit_bound_var(x, binder, ...)
95 --$log( 'walk.id found a free var:', x, scope.current)
96 r = visit_free_var(x, ...)
98 if r then return r end
99 | `Function{ params, _ } -> scope:push (params, x)
100 | `Stat{ block, expr } ->
101 -------------------------------------------------------------
102 -- 'expr' is in the scope of 'block': create the scope and
103 -- walk the block 'manually', then prevent automatic walk
104 -- by returning 'break'.
105 -------------------------------------------------------------
107 for stat in values (block) do walk.stat(cfg, stat, x, ...) end
108 walk.expr(cfg, expr, x, ...)
116 -----------------------------------------------------------------------------
117 -- Close the function scope opened by 'down()'
118 -----------------------------------------------------------------------------
119 function cfg.expr.up(x, ...)
120 match x with `Function{...} -> scope:pop() | _ -> end
121 if supercfg.expr and supercfg.expr.up then supercfg.expr.up(x, ...) end
124 -----------------------------------------------------------------------------
125 -- Create a new scope and register loop variable[s] in it
126 -----------------------------------------------------------------------------
127 function cfg.stat.down(x, ...)
128 -- Execute the generic statement walker; if it breaks.
129 -- don't do the id walking.
130 if supercfg.stat and supercfg.stat.down then
131 local r = supercfg.stat.down(x, ...)
132 if r then return r end
135 | `Forin{ vars, ... } -> scope:push (vars, x)
136 | `Fornum{ var, ... } -> scope:push ({var}, x)
137 | `Localrec{ vars, ... } -> scope:add (vars, x)
138 | `Repeat{ block, expr } ->
139 -------------------------------------------------------------
140 -- 'expr' is in the scope of 'block': create the scope and
141 -- walk the block 'manually', then prevent automatic walk
142 -- by returning 'break'.
143 -------------------------------------------------------------
145 for stat in values (block) do walk.stat(cfg, stat, x, ...) end
146 walk.expr(cfg, expr, x, ...)
153 -----------------------------------------------------------------------------
154 -- Close the scopes opened by 'up()'
155 -----------------------------------------------------------------------------
156 function cfg.stat.up(x, ...)
158 | `Forin{ ... } | `Fornum{ ... } -> scope:pop()
159 | `Local{ vars, ... } -> scope:add(vars, x)
161 -- `Repeat has no up(), because it 'break's.
163 if supercfg.stat and supercfg.stat.up then supercfg.stat.up(x, ...) end
166 -----------------------------------------------------------------------------
167 -- Create a separate scope for each block
168 -----------------------------------------------------------------------------
169 function cfg.block.down(x, ...)
170 if supercfg.block and supercfg.block.down then
171 local r = supercfg.block.down(x, ...)
172 if r then return r end
176 function cfg.block.up(x, ...)
178 if supercfg.block and supercfg.block.up then supercfg.block.up(x, ...) end
180 cfg.binder = supercfg.binder
181 walk[kind](cfg, ast, ...)
184 local mt = { __index = |_,k| |...| _walk_id(k, ...) }
185 walk_id = setmetatable({ }, mt)