]> git.lizzy.rs Git - metalua.git/blob - src/lib/metalua/walk.mlua
b8ffca21e058adc650ea0c2efbcf67c6c406ac92
[metalua.git] / src / lib / metalua / walk.mlua
1 --------------------------------------------------------------------------------
2 -- Code walkers
3 --      "Make everything as simple as possible, but not simpler".
4 --
5 -- This library offers a generic way to write AST transforming
6 -- functions. Macros can take bits of AST as parameters and generate a
7 -- more complex AST with them; but modifying an AST a posteriori is
8 -- much more difficult; typical tasks requiring code walking are
9 -- transformation such as lazy evaluation or Continuation Passing
10 -- Style, but more mundane operations are required in more macros than
11 -- one would thing, such as "transform all returns which aren't inside
12 -- a nested function into an error throwing".
13 --
14 -- AST walking is an intrinsically advanced operation, and the
15 -- interface of this library, although it tries to remain as simple as
16 -- possible, is not trivial. You'll probably need to write a couple of
17 -- walkers with it before feeling comfortable.
18 --
19 --
20 -- We deal here with 3 important kinds of AST: statements, expressions
21 -- and blocks. Code walkers for these three kinds for AST are called
22 -- [walk.stat (cfg, ast)], [walk.expr (cfg, ast)] and [walk.block
23 -- (cfg, ast)] respectively. the [cfg] parameter describes what shall
24 -- happen as the AST is traversed by the walker, and [ast] is the tree
25 -- itself. 
26 --
27 -- An aparte to fellow functional programmers: although Lua has
28 -- got all the features that constitute a functional language, its
29 -- heart, and in particular it table data, is imperative. It's often
30 -- asking for trouble to work against the host language's nature, so
31 -- code walkers are imperative, cope with it. Or use table.deep_copy()
32 -- if you don't want issues with shared state.
33 --
34 -- Since walkers are imperative (i.e. they transform the tree in
35 -- place, rather than returning a fresh variant of it), you'll often
36 -- want to override a node, i.e. keep its "pointer identity", but
37 -- replace its content with a new one; this is done by
38 -- table.override(), and is conveniently abbreviated as
39 -- "target <- new_content".
40 --
41 -- So, [cfg] can contain a series of sub-tables fields 'expr', 'stat',
42 -- 'block'. each of them can contain a function up() and/or a function
43 -- down(). 
44 --
45 -- * down() is called when the walker starts visiting a node of the
46 --   matching kind, i.e. before any of its sub-nodes have been
47 --   visited.  down() is allowed to return either the string "break",
48 --   which means "don't go further down this tree, don't try to walk
49 --   its children", or nil, i.e. "please process with the children
50 --   nodes". 
51 --
52 --   There are two reasons why you might want down() to return
53 --   "break": either because you really weren't interested into the
54 --   children nodes,or because you wanted to walk through them in a
55 --   special way, and down() already performed this special walking.
56 --
57 -- * up() is called just before the node is left, i.e. after all of
58 --   its children nodes have been completely parsed, down and up. This
59 --   is a good place to put treatments which rely on sub-nodes being
60 --   already treated. Notice that if down() returned 'break', up() is
61 --   run immediately after.
62 --
63 -- In previous versions of this library, there were plenty of fancy
64 -- configurable ways to decide whether an up() or down() functions
65 -- would be triggered or not. Experience suggested that the best way
66 -- is to keep it simpler, as done by the current design: the functions
67 -- in sub-table expr are run on each expression node, and ditto for
68 -- stat and block; the user is expected to use the pattern matching
69 -- extension to decide whether to act or not on a given node.
70 --
71 -- Advanced features
72 -- =================
73 --
74 -- The version above is a strict subset of the truth: there are a
75 -- couple of other, more advanced features in the library.
76 --
77 -- Paths in visitor functions
78 -- --------------------------
79 -- First, up() and down() don't take only one node as a parameter, but
80 -- a series thereof: all the nested expr/stat/block nodes on the way
81 -- up to the ast's root. For instance, when a walker works on
82 -- +{ foo(bar*2+1) } an is on the node +{2}, up() and down() are called
83 -- with arguments (+{bar*2}, +{bar*2+1}, +{foo(bar*2+1)}).
84 --
85 -- `Call and `Invoke as statements
86 -- -------------------------------
87 -- `Call and `Invoke are normally expressions, but they can also
88 -- appear as statements. In this case, the cfg.expr.xxx() visitors
89 -- aren't called on them. Sometimes you want to consider tham as
90 -- expressions, sometimes not, and it's much easier to add a special
91 -- case in cfg.stat.xxx() visitors than to determine whether we're in
92 -- a statament's context in cfg.expr.xxx(),
93 --
94 -- Extra walkers
95 -- -------------
96 -- There are some second class walkers: walk.expr_list() and walk.guess(). 
97 --
98 -- * The first one walks through a list of expressions. Although used
99 --   internally by the other walkers, it remains a second class
100 --   citizen: the list it works on won't appear in the path of nested
101 --   ASTs that's passed to up() and down(). This design choice has
102 --   been made because there's no clear definition of what is or isn't
103 --   an expr list in an AST, and anyway such lists are probably not
104 --   part of metacoders' mental image of an AST, so it's been thought
105 --   best to let people pretend they don't exist.
106 --
107 -- * walk.guess() tries to guess the type of the AST it receives,
108 --   according to its tag, and runs the appropriate walker. Node which
109 --   can be both stats and exprs (`Call and `Invoke) are considered as
110 --   expr.
111 --
112 -- These three walkers, although used internally by the other walkers,
113 -- remain second class citizens: the lists they work on won't appear
114 -- in the path of nested ASTs that's passed to up() and down().
115 --
116 -- Tag dictionaries
117 -- ----------------
118 -- There are two public dictionaries, walk.tags.stat and
119 -- walk.tags.expr, which keep the set of all tags that can start a
120 -- statement or an expression AST. They're used by walk.guess, and
121 -- users sometimes need them as well, so they've been kept available.
122 --
123 -- Binder visitor
124 -- --------------
125 -- Finally, there's one last field in [cfg]: binder(). This function
126 -- is called on identifiers in a binder position, i.e. `Id{ } nodes
127 -- which create a scoped local variable, in `Function, `Fornum, `Local
128 -- etc. The main use case for that function is to keep track of
129 -- variables, captures, etc. and perform alpha conversions. In many
130 -- cases that work is best done through the library 'walk.id', which
131 -- understands the notions of scope, free variable, bound variable
132 -- etc. 
133 --
134 -- Binder visitors are called just before the variable's scope starts,
135 -- e.g. they're called after the right-hand-side has been visited in a
136 -- `Local node, but before in a `Localrec node.
137 --
138 --------------------------------------------------------------------------------
139
140 -{ extension "match" }
141
142 walk = { traverse = { }; tags = { }; debug = false }
143
144 --------------------------------------------------------------------------------
145 -- Standard tags: can be used to guess the type of an AST, or to check
146 -- that the type of an AST is respected.
147 --------------------------------------------------------------------------------
148 walk.tags.stat = table.transpose{ 
149    'Do', 'Set', 'While', 'Repeat', 'Local', 'Localrec', 'Return',
150    'Fornum', 'Forin', 'If', 'Break', 'Goto', 'Label',
151    'Call', 'Invoke' }
152 walk.tags.expr = table.transpose{
153    'Paren', 'Call', 'Invoke', 'Index', 'Op', 'Function', 'Stat',
154    'Table', 'Nil', 'Dots', 'True', 'False', 'Number', 'String', 'Id' }
155
156 local function scope (cfg, dir)
157    local h = cfg.scope and cfg.scope[dir]
158    if h then h() end
159 end
160
161 --------------------------------------------------------------------------------
162 -- These [walk.traverse.xxx()] functions are in charge of actually going through
163 -- ASTs. At each node, they make sure to call the appropriate walker.
164 --------------------------------------------------------------------------------
165 function walk.traverse.stat (cfg, x, ...)
166    if walk.debug then printf("traverse stat %s", table.tostring(x)) end
167    local log = {...}
168    local B  = |y| walk.block       (cfg, y, x, unpack(log))
169    local S  = |y| walk.stat        (cfg, y, x, unpack(log))
170    local E  = |y| walk.expr        (cfg, y, x, unpack(log))
171    local EL = |y| walk.expr_list   (cfg, y, x, unpack(log))
172    local I  = |y| walk.binder_list (cfg, y, x, unpack(log))
173    local function BS(y)
174       scope (cfg, 'down'); B(y); scope (cfg, 'up')
175    end
176
177    match x with
178    | {...} if x.tag == nil -> for y in ivalues(x) do walk.stat(cfg, y, ...) end
179                               -- no tag --> node not inserted in the history log
180    | `Do{...}                    -> BS(x)
181    | `Set{ lhs, rhs }            -> EL(lhs); EL(rhs)
182    | `While{ cond, body }        -> E(cond); BS(body)
183    | `Repeat{ body, cond }       -> scope(cfg, 'down'); B(body); E(cond); scope(cfg, 'up')
184    | `Local{ lhs }               -> I(lhs)
185    | `Local{ lhs, rhs }          -> EL(rhs); I(lhs)
186    | `Localrec{ lhs, rhs }       -> I(lhs); EL(rhs)
187    | `Fornum{ i, a, b, body }    -> E(a); E(b); I{i}; BS(body)
188    | `Fornum{ i, a, b, c, body } -> E(a); E(b); E(c); I{i}; BS(body)
189    | `Forin{ i, rhs, body }      -> EL(rhs); I(i); BS(body)
190    | `If{...}                    -> for i=1, #x-1, 2 do E(x[i]); BS(x[i+1]) end
191                                     if #x%2 == 1 then BS(x[#x]) end
192    | `Call{...}|`Invoke{...}|`Return{...} -> EL(x)
193    | `Break | `Goto{ _ } | `Label{ _ }    -> -- nothing
194    | {...} if walk.tags.stat[x.tag]-> 
195       printf("Warning: walk: malformed %s stat node: %s", x.tag, table.tostring(x,80))
196    | {...} -> print("Warning: walk: unknown stat node: "..table.tostring(x,80))
197    | _     -> print("Warning: walk: unexpected stat node of type "..type(x)
198                     ..": "..table.tostring(x,80))
199    end
200 end
201
202 function walk.traverse.expr (cfg, x, ...)
203    if walk.debug then printf("traverse expr %s", table.tostring(x)) end
204    local log = {...}
205    local B  = |y| walk.block       (cfg, y, x, unpack(log))
206    local S  = |y| walk.stat        (cfg, y, x, unpack(log))
207    local E  = |y| walk.expr        (cfg, y, x, unpack(log))
208    local EL = |y| walk.expr_list   (cfg, y, x, unpack(log)) 
209    local I  = |y| walk.binder_list (cfg, y, x, unpack(log))
210    match x with
211    | `Paren{ e }               -> E(e)
212    | `Call{...} | `Invoke{...} -> EL(x)
213    | `Index{ a, b }            -> E(a); E(b)
214    | `Op{ opid, ... }          -> E(x[2]); if #x==3 then E(x[3]) end
215    | `Function{ params, body } -> I(params); scope(cfg, 'down'); B(body); scope (cfg, 'in')
216    | `Stat{ b, e }             -> scope(cfg, 'down'); B(b); E(e); scope (cfg, 'in')
217    | `Table{ ... }             ->
218       for i = 1, #x do match x[i] with
219          | `Pair{ k, v } -> E(k); E(v)
220          | v            -> E(v)
221       end end
222    |`Nil|`Dots|`True|`False|`Number{_}|`String{_}|`Id{_} -> -- nothing 
223    | {...} if walk.tags.expr[x.tag]-> 
224       printf("Warning: walk: malformed %s expr node: %s", x.tag, table.tostring(x,80))
225    | {...} -> print("Warning: walk: unknown expr node: "..table.tostring(x,80))
226    | _     -> print("Warning: walk: unexpected expr node of type "..type(x)
227                     ..": "..table.tostring(x,80))
228    end
229 end
230
231 function walk.traverse.block (cfg, x, ...)
232    assert(type(x)=='table', "traverse.block() expects a table")
233    for y in ivalues(x) do walk.stat(cfg, y, x, ...) end
234 end
235
236 function walk.traverse.expr_list (cfg, x, ...)
237    assert(type(x)=='table', "traverse.expr_list() expects a table")
238    -- x doesn't appear in the log
239    for y in ivalues(x) do walk.expr(cfg, y, ...) end
240 end
241
242 ----------------------------------------------------------------------
243 -- Generic walker generator.
244 -- * if `cfg' has an entry matching the tree name, use this entry
245 -- * if not, try to use the entry whose name matched the ast kind
246 -- * if an entry is a table, look for 'up' and 'down' entries
247 -- * if it is a function, consider it as a `down' traverser.
248 ----------------------------------------------------------------------
249 local walker_builder = |cfg_field, traverse| 
250 function (cfg, x, ...)
251    local sub_cfg = type (x)=='table' and x.tag and cfg[x.tag] or cfg[cfg_field] or cfg
252    local broken, down, up = false
253    if type(sub_cfg)=='table' then
254       down, up = sub_cfg.down, sub_cfg.up
255    elseif type(sub_cfg)=='function' or sub_cfg=='break' then
256       down, up = sub_cfg, nil
257    else error "Invalid walk config" end
258
259    if down then
260       if down=='break' then broken='break'
261       else broken = down (x, ...) end
262       assert(not broken or broken=='break', 
263              "Map functions must return 'break' or nil")
264    end
265    if not broken then traverse (cfg, x, ...) end
266    if up then up (x, ...) end
267 end
268
269 ----------------------------------------------------------------------
270 -- Declare [walk.stat], [walk.expr], [walk.block] and [walk.expr_list]
271 ----------------------------------------------------------------------
272 for w in values{ "stat", "expr", "block", "expr_list" } do
273    walk[w] = walker_builder (w, walk.traverse[w])
274 end
275
276 ----------------------------------------------------------------------
277 -- Walk a list of `Id{...} (mainly a helper function actually).
278 ----------------------------------------------------------------------
279 function walk.binder_list (cfg, x, ...)
280    local f = cfg.binder 
281    if f then for v in ivalues(x) do f(v, ...) end end
282 end
283
284 ----------------------------------------------------------------------
285 -- Tries to guess the type of the AST then choose the right walkker.
286 ----------------------------------------------------------------------
287 function walk.guess (cfg, x, ...)
288    assert(type(x)=='table', "arg #2 in a walker must be an AST")
289    if walk.tags.expr[x.tag] then return walk.expr(cfg, x, ...)  end
290    if walk.tags.stat[x.tag] then return walk.stat(cfg, x, ...)  end
291    if not x.tag             then return walk.block(cfg, x, ...) end
292    error ("Can't guess the AST type from tag "..(x.tag or '<none>')) 
293 end