]> git.lizzy.rs Git - metalua.git/commitdiff
improved walker: scope callbacks, more flexible callbacks in cfg (by tag, by kind...
authorFabien Fleutot <metalua@gmail.com>
Thu, 19 Feb 2009 10:14:26 +0000 (11:14 +0100)
committerFabien Fleutot <metalua@gmail.com>
Thu, 19 Feb 2009 10:14:26 +0000 (11:14 +0100)
src/lib/metalua/walk.mlua
src/lib/metalua/walk/bindings.mlua [new file with mode: 0644]

index 0030e716b9591c09dacd8864ab8f799c520bd64c..b8ffca21e058adc650ea0c2efbcf67c6c406ac92 100644 (file)
@@ -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 (file)
index 0000000..f457e81
--- /dev/null
@@ -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