--- /dev/null
+--------------------------------------------------------------------------------
+--
+-- (c) Fabien Fleutot 2007, published under the MIT license.
+--
+--
+-- API:
+-- ----
+-- * freevars.block(ast)
+-- * freevars.expr(ast)
+-- * freevars.stat(ast)
+--
+--------------------------------------------------------------------------------
+
+require 'std'
+require 'walk'
+require 'freevars'
+
+-{ extension 'match' }
+
+--------------------------------------------------------------------------------
+-- Return the string->boolean hash table of the names of all free variables
+-- in 'term'. 'kind' is the name of an entry in module 'walk', presumably
+-- one of 'expr', 'stat' or 'block'.
+--------------------------------------------------------------------------------
+local function alpha (kind, term)
+ local cfg = { expr = { }, stat = { }, block = { } }
+
+ -----------------------------------------------------------------------------
+ -- Monkey-patch the scope add method, so that it associates a unique name
+ -- to bound vars.
+ -----------------------------------------------------------------------------
+ local scope = scope:new()
+ function scope:add(vars)
+ for v in values(vars) do self.current[v] = mlp.gensym(v) end
+ end
+
+ -----------------------------------------------------------------------------
+ -- Check identifiers; add functions parameters to scope
+ -----------------------------------------------------------------------------
+ function cfg.expr.down(x)
+ match x with
+ | `Splice{...} -> return 'break' -- don't touch user parts
+ | `Id{ name } ->
+ local alpha = scope.current[name]
+ if alpha then x[1] = alpha end
+ | `Function{ params, _ } -> scope:push(); scope:add (params)
+ | _ -> -- pass
+ end
+ end
+
+ -----------------------------------------------------------------------------
+ -- Close the function scope opened by 'down()'
+ -----------------------------------------------------------------------------
+ function cfg.expr.up(x)
+ match x with `Function{...} -> scope:pop() | _ -> end
+ end
+
+ -----------------------------------------------------------------------------
+ -- Create a new scope and register loop variable[s] in it
+ -----------------------------------------------------------------------------
+ function cfg.stat.down(x)
+ match x with
+ | `Splice{...} -> return 'break'
+ | `Forin{ vars, ... } -> scope:push(); scope:add(vars)
+ | `Fornum{ var, ... } -> scope:push(); scope:add{var}
+ | `Localrec{ vars, ... } -> scope:add(vars)
+ | `Repeat{ block, cond } -> -- 'cond' is in the scope of 'block'
+ scope:push()
+ for s in values (block) do walk.stat(cfg)(s) end -- no new scope
+ walk.expr(cfg)(cond)
+ scope:pop()
+ return 'break' -- No automatic walking of subparts
+ | _ -> -- pass
+ end
+ end
+
+ -----------------------------------------------------------------------------
+ -- Close the scopes opened by 'up()'
+ -----------------------------------------------------------------------------
+ function cfg.stat.up(x)
+ match x with
+ | `Forin{ ... } | `Fornum{ ... } -> scope:pop() -- `Repeat has no up().
+ | `Local{ vars, ... } -> scope:add(vars)
+ | _ -> -- pass
+ end
+ end
+
+ -----------------------------------------------------------------------------
+ -- Create a separate scope for each block
+ -----------------------------------------------------------------------------
+ function cfg.block.down() scope:push() end
+ function cfg.block.up() scope:pop() end
+
+ walk[kind](cfg)(term)
+ return freevars
+end
+
+--------------------------------------------------------------------------------
+-- A wee bit of metatable hackery. Just couldn't resist, sorry.
+--------------------------------------------------------------------------------
+freevars = setmetatable ({ scope=scope }, { __index = |_, k| |t| fv(k, t) })