From: Fabien Fleutot Date: Thu, 19 Feb 2009 10:14:26 +0000 (+0100) Subject: improved walker: scope callbacks, more flexible callbacks in cfg (by tag, by kind... X-Git-Url: https://git.lizzy.rs/?a=commitdiff_plain;h=225718ce0210fe56bc15cf3328008cb48207ed78;p=metalua.git improved walker: scope callbacks, more flexible callbacks in cfg (by tag, by kind, common, optionnal up/down distinction...; first proto of a much simplified scope-aware Id analyzer based on this improved walker --- diff --git a/src/lib/metalua/walk.mlua b/src/lib/metalua/walk.mlua index 0030e71..b8ffca2 100644 --- a/src/lib/metalua/walk.mlua +++ b/src/lib/metalua/walk.mlua @@ -153,6 +153,11 @@ walk.tags.expr = table.transpose{ '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. @@ -165,21 +170,25 @@ function walk.traverse.stat (cfg, x, ...) 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]-> @@ -203,8 +212,8 @@ function walk.traverse.expr (cfg, x, ...) | `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) @@ -232,18 +241,29 @@ end ---------------------------------------------------------------------- -- 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 ---------------------------------------------------------------------- diff --git a/src/lib/metalua/walk/bindings.mlua b/src/lib/metalua/walk/bindings.mlua new file mode 100644 index 0000000..f457e81 --- /dev/null +++ b/src/lib/metalua/walk/bindings.mlua @@ -0,0 +1,36 @@ +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