variable capture bug in match extension
[metalua.git] / junk / hygienic2.lua
blobd0ca8c349d1e77212d9ba5c77b9d7917a067c142
1 --------------------------------------------------------------------------------
2 --
3 -- (c) Fabien Fleutot 2007, published under the MIT license.
4 --
5 --
6 -- API:
7 -- ----
8 -- * freevars.block(ast)
9 -- * freevars.expr(ast)
10 -- * freevars.stat(ast)
12 --------------------------------------------------------------------------------
14 require 'std'
15 require 'walk'
16 require 'freevars'
18 -{ extension 'match' }
20 --------------------------------------------------------------------------------
21 -- Return the string->boolean hash table of the names of all free variables
22 -- in 'term'. 'kind' is the name of an entry in module 'walk', presumably
23 -- one of 'expr', 'stat' or 'block'.
24 --------------------------------------------------------------------------------
25 local function alpha (kind, term)
26 local cfg = { expr = { }, stat = { }, block = { } }
28 -----------------------------------------------------------------------------
29 -- Monkey-patch the scope add method, so that it associates a unique name
30 -- to bound vars.
31 -----------------------------------------------------------------------------
32 local scope = scope:new()
33 function scope:add(vars)
34 for v in values(vars) do self.current[v] = mlp.gensym(v) end
35 end
37 -----------------------------------------------------------------------------
38 -- Check identifiers; add functions parameters to scope
39 -----------------------------------------------------------------------------
40 function cfg.expr.down(x)
41 match x with
42 | `Splice{...} -> return 'break' -- don't touch user parts
43 | `Id{ name } ->
44 local alpha = scope.current[name]
45 if alpha then x[1] = alpha end
46 | `Function{ params, _ } -> scope:push(); scope:add (params)
47 | _ -> -- pass
48 end
49 end
51 -----------------------------------------------------------------------------
52 -- Close the function scope opened by 'down()'
53 -----------------------------------------------------------------------------
54 function cfg.expr.up(x)
55 match x with `Function{...} -> scope:pop() | _ -> end
56 end
58 -----------------------------------------------------------------------------
59 -- Create a new scope and register loop variable[s] in it
60 -----------------------------------------------------------------------------
61 function cfg.stat.down(x)
62 match x with
63 | `Splice{...} -> return 'break'
64 | `Forin{ vars, ... } -> scope:push(); scope:add(vars)
65 | `Fornum{ var, ... } -> scope:push(); scope:add{var}
66 | `Localrec{ vars, ... } -> scope:add(vars)
67 | `Repeat{ block, cond } -> -- 'cond' is in the scope of 'block'
68 scope:push()
69 for s in values (block) do walk.stat(cfg)(s) end -- no new scope
70 walk.expr(cfg)(cond)
71 scope:pop()
72 return 'break' -- No automatic walking of subparts
73 | _ -> -- pass
74 end
75 end
77 -----------------------------------------------------------------------------
78 -- Close the scopes opened by 'up()'
79 -----------------------------------------------------------------------------
80 function cfg.stat.up(x)
81 match x with
82 | `Forin{ ... } | `Fornum{ ... } -> scope:pop() -- `Repeat has no up().
83 | `Local{ vars, ... } -> scope:add(vars)
84 | _ -> -- pass
85 end
86 end
88 -----------------------------------------------------------------------------
89 -- Create a separate scope for each block
90 -----------------------------------------------------------------------------
91 function cfg.block.down() scope:push() end
92 function cfg.block.up() scope:pop() end
94 walk[kind](cfg)(term)
95 return freevars
96 end
98 --------------------------------------------------------------------------------
99 -- A wee bit of metatable hackery. Just couldn't resist, sorry.
100 --------------------------------------------------------------------------------
101 freevars = setmetatable ({ scope=scope }, { __index = |_, k| |t| fv(k, t) })