'Paren', 'Call', 'Invoke', 'Index', 'Op', 'Function', 'Stat',
'Table', 'Nil', 'Dots', 'True', 'False', 'Number', 'String', 'Id' }
+local function scope (cfg, dir)
+ local h = cfg.scope and cfg.scope[dir]
+ if h then h() end
+end
+
--------------------------------------------------------------------------------
-- These [walk.traverse.xxx()] functions are in charge of actually going through
-- ASTs. At each node, they make sure to call the appropriate walker.
local E = |y| walk.expr (cfg, y, x, unpack(log))
local EL = |y| walk.expr_list (cfg, y, x, unpack(log))
local I = |y| walk.binder_list (cfg, y, x, unpack(log))
+ local function BS(y)
+ scope (cfg, 'down'); B(y); scope (cfg, 'up')
+ end
+
match x with
| {...} if x.tag == nil -> for y in ivalues(x) do walk.stat(cfg, y, ...) end
-- no tag --> node not inserted in the history log
- | `Do{...} -> B(x)
+ | `Do{...} -> BS(x)
| `Set{ lhs, rhs } -> EL(lhs); EL(rhs)
- | `While{ cond, body } -> E(cond); B(body)
- | `Repeat{ body, cond } -> B(body); E(cond)
+ | `While{ cond, body } -> E(cond); BS(body)
+ | `Repeat{ body, cond } -> scope(cfg, 'down'); B(body); E(cond); scope(cfg, 'up')
| `Local{ lhs } -> I(lhs)
| `Local{ lhs, rhs } -> EL(rhs); I(lhs)
| `Localrec{ lhs, rhs } -> I(lhs); EL(rhs)
- | `Fornum{ i, a, b, body } -> E(a); E(b); I{i}; B(body)
- | `Fornum{ i, a, b, c, body } -> E(a); E(b); E(c); I{i}; B(body)
- | `Forin{ i, rhs, body } -> EL(rhs); I(i); B(body)
- | `If{...} -> for i=1, #x-1, 2 do E(x[i]); B(x[i+1]) end
- if #x%2 == 1 then B(x[#x]) end
+ | `Fornum{ i, a, b, body } -> E(a); E(b); I{i}; BS(body)
+ | `Fornum{ i, a, b, c, body } -> E(a); E(b); E(c); I{i}; BS(body)
+ | `Forin{ i, rhs, body } -> EL(rhs); I(i); BS(body)
+ | `If{...} -> for i=1, #x-1, 2 do E(x[i]); BS(x[i+1]) end
+ if #x%2 == 1 then BS(x[#x]) end
| `Call{...}|`Invoke{...}|`Return{...} -> EL(x)
| `Break | `Goto{ _ } | `Label{ _ } -> -- nothing
| {...} if walk.tags.stat[x.tag]->
| `Call{...} | `Invoke{...} -> EL(x)
| `Index{ a, b } -> E(a); E(b)
| `Op{ opid, ... } -> E(x[2]); if #x==3 then E(x[3]) end
- | `Function{ params, body } -> I(params); B(body)
- | `Stat{ b, e } -> B(b); E(e)
+ | `Function{ params, body } -> I(params); scope(cfg, 'down'); B(body); scope (cfg, 'in')
+ | `Stat{ b, e } -> scope(cfg, 'down'); B(b); E(e); scope (cfg, 'in')
| `Table{ ... } ->
for i = 1, #x do match x[i] with
| `Pair{ k, v } -> E(k); E(v)
----------------------------------------------------------------------
-- Generic walker generator.
+-- * if `cfg' has an entry matching the tree name, use this entry
+-- * if not, try to use the entry whose name matched the ast kind
+-- * if an entry is a table, look for 'up' and 'down' entries
+-- * if it is a function, consider it as a `down' traverser.
----------------------------------------------------------------------
-local walker_builder = |cfg_field, traverse| function (cfg, x, ...)
- local sub_cfg = cfg[cfg_field] or { }
- local broken = false
- if sub_cfg.down then
- if sub_cfg.down=='break' then broken='break'
- else broken = sub_cfg.down (x, ...) end
+local walker_builder = |cfg_field, traverse|
+function (cfg, x, ...)
+ local sub_cfg = type (x)=='table' and x.tag and cfg[x.tag] or cfg[cfg_field] or cfg
+ local broken, down, up = false
+ if type(sub_cfg)=='table' then
+ down, up = sub_cfg.down, sub_cfg.up
+ elseif type(sub_cfg)=='function' or sub_cfg=='break' then
+ down, up = sub_cfg, nil
+ else error "Invalid walk config" end
+
+ if down then
+ if down=='break' then broken='break'
+ else broken = down (x, ...) end
assert(not broken or broken=='break',
"Map functions must return 'break' or nil")
end
if not broken then traverse (cfg, x, ...) end
- if sub_cfg.up then sub_cfg.up (x, ...) end
+ if up then up (x, ...) end
end
----------------------------------------------------------------------
--- /dev/null
+require 'walk'
+
+function bindings(ast)
+ -- binders :: ast => name => occurences
+ -- unbound :: name => occurences
+ -- scope :: name => ast
+
+ local bound_id, unbound, cfg, scope = { }, { }, { scope={ } }, scope:new()
+
+ -- * id: identifier entering in scope
+ -- * ast: statement or expr carrying this id, on of:
+ -- Local, Localrec, Forin, Fornum, Function.
+ function cfg.binder (id, ast)
+ local id_name = id[1]
+ scope.current[id[1]] = ast
+ end
+
+ -- identifier occurence, not as a binder: reference this occurence
+ function cfg.Id (id)
+ local id_name = id[1]
+ local binder = scope.current[id_name]
+ if binder then bound_id[id] = binder
+ else
+ local occ = scope.current[id_name]free_id[id_name]
+ if occ then table.insert (occ, id)
+ else free_id[id_name] = { id } end
+ end
+ end
+
+ function cfg.scope.down() scope:push() end
+
+ function cfg.scope.up() scope:pop() end
+
+ walk.guess (cfg, ast)
+ return bound_id, free_id
+end
\ No newline at end of file