]> git.lizzy.rs Git - metalua.git/blob - src/lib/metalua/walk.mlua
Merge remote branch 'origin/master'
[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 -- TODO: document scopes, relaxed cfg descriptions
139 -- -----------------------------------------------
140 --
141 -- Examples of cfg structures:
142 --
143 -- { Id = f1, Local = f2 }
144 -- f
145 -- { up = f1, down = f2 }
146 -- { scope = { up = f1, down = f2 }, up = f1, down = f2 }
147 -- { stat = f1, expr = { up = f1 } }
148 --
149 --
150 --------------------------------------------------------------------------------
151
152 -{ extension "match" }
153
154 walk = { traverse = { }; tags = { }; debug = false }
155
156 --------------------------------------------------------------------------------
157 -- Standard tags: can be used to guess the type of an AST, or to check
158 -- that the type of an AST is respected.
159 --------------------------------------------------------------------------------
160 walk.tags.stat = table.transpose{ 
161    'Do', 'Set', 'While', 'Repeat', 'Local', 'Localrec', 'Return',
162    'Fornum', 'Forin', 'If', 'Break', 'Goto', 'Label',
163    'Call', 'Invoke' }
164 walk.tags.expr = table.transpose{
165    'Paren', 'Call', 'Invoke', 'Index', 'Op', 'Function', 'Stat',
166    'Table', 'Nil', 'Dots', 'True', 'False', 'Number', 'String', 'Id' }
167
168 local function scope (cfg, dir)
169    local h = cfg.scope and cfg.scope[dir]
170    if h then h() end
171 end
172
173 --------------------------------------------------------------------------------
174 -- These [walk.traverse.xxx()] functions are in charge of actually going through
175 -- ASTs. At each node, they make sure to call the appropriate walker.
176 --------------------------------------------------------------------------------
177 function walk.traverse.stat (cfg, x, ...)
178    if walk.debug then printf("traverse stat %s", table.tostring(x)) end
179    local log = {...}
180    local B  = |y| walk.block       (cfg, y, x, unpack(log))
181    local S  = |y| walk.stat        (cfg, y, x, unpack(log))
182    local E  = |y| walk.expr        (cfg, y, x, unpack(log))
183    local EL = |y| walk.expr_list   (cfg, y, x, unpack(log))
184    local I  = |y| walk.binder_list (cfg, y, x, unpack(log))
185    local function BS(y)
186       scope (cfg, 'down'); B(y); scope (cfg, 'up')
187    end
188
189    match x with
190    | {...} if x.tag == nil -> for y in ivalues(x) do walk.stat(cfg, y, ...) end
191                               -- no tag --> node not inserted in the history log
192    | `Do{...}                    -> BS(x)
193    | `Set{ lhs, rhs }            -> EL(lhs); EL(rhs)
194    | `While{ cond, body }        -> E(cond); BS(body)
195    | `Repeat{ body, cond }       -> scope(cfg, 'down'); B(body); E(cond); scope(cfg, 'up')
196    | `Local{ lhs }               -> I(lhs)
197    | `Local{ lhs, rhs }          -> EL(rhs); I(lhs)
198    | `Localrec{ lhs, rhs }       -> I(lhs); EL(rhs)
199    | `Fornum{ i, a, b, body }    -> E(a); E(b); I{i}; BS(body)
200    | `Fornum{ i, a, b, c, body } -> E(a); E(b); E(c); I{i}; BS(body)
201    | `Forin{ i, rhs, body }      -> EL(rhs); I(i); BS(body)
202    | `If{...}                    -> for i=1, #x-1, 2 do E(x[i]); BS(x[i+1]) end
203                                     if #x%2 == 1 then BS(x[#x]) end
204    | `Call{...}|`Invoke{...}|`Return{...} -> EL(x)
205    | `Break | `Goto{ _ } | `Label{ _ }    -> -- nothing
206    | { tag=tag, ...} if walk.tags.stat[tag]-> 
207       walk.malformed (cfg, x, unpack (log))
208    | _ ->  
209       walk.unknonw (cfg, x, unpack (log))
210    end
211 end
212
213 function walk.traverse.expr (cfg, x, ...)
214    if walk.debug then printf("traverse expr %s", table.tostring(x)) end
215    local log = {...}
216    local B  = |y| walk.block       (cfg, y, x, unpack(log))
217    local S  = |y| walk.stat        (cfg, y, x, unpack(log))
218    local E  = |y| walk.expr        (cfg, y, x, unpack(log))
219    local EL = |y| walk.expr_list   (cfg, y, x, unpack(log)) 
220    local I  = |y| walk.binder_list (cfg, y, x, unpack(log))
221    match x with
222    | `Paren{ e }               -> E(e)
223    | `Call{...} | `Invoke{...} -> EL(x)
224    | `Index{ a, b }            -> E(a); E(b)
225    | `Op{ opid, ... }          -> E(x[2]); if #x==3 then E(x[3]) end
226    | `Function{ params, body } -> I(params); scope(cfg, 'down'); B(body); scope (cfg, 'in')
227    | `Stat{ b, e }             -> scope(cfg, 'down'); B(b); E(e); scope (cfg, 'in')
228    | `Table{ ... }             ->
229       for i = 1, #x do match x[i] with
230          | `Pair{ k, v } -> E(k); E(v)
231          | v            -> E(v)
232       end end
233    |`Nil|`Dots|`True|`False|`Number{_}|`String{_}|`Id{_} -> -- nothing 
234    | { tag=tag, ...} if walk.tags.expr[tag]-> 
235       walk.malformed (cfg, x, unpack (log))
236    | _ ->  
237       walk.unknonw (cfg, x, unpack (log))
238    end
239 end
240
241 function walk.traverse.block (cfg, x, ...)
242    assert(type(x)=='table', "traverse.block() expects a table")
243    for y in ivalues(x) do walk.stat(cfg, y, x, ...) end
244 end
245
246 function walk.traverse.expr_list (cfg, x, ...)
247    assert(type(x)=='table', "traverse.expr_list() expects a table")
248    -- x doesn't appear in the log
249    for y in ivalues(x) do walk.expr(cfg, y, ...) end
250 end
251
252 ----------------------------------------------------------------------
253 -- Generic walker generator.
254 -- * if `cfg' has an entry matching the tree name, use this entry
255 -- * if not, try to use the entry whose name matched the ast kind
256 -- * if an entry is a table, look for 'up' and 'down' entries
257 -- * if it is a function, consider it as a `down' traverser.
258 ----------------------------------------------------------------------
259 local walker_builder = |cfg_field, traverse| function (cfg, x, ...)
260    local sub_cfg = type (x)=='table' and x.tag and cfg[x.tag] 
261       or cfg[cfg_field] or cfg
262    local broken, down, up = false
263    if type(sub_cfg)=='table' then
264       down, up = sub_cfg.down, sub_cfg.up
265    elseif type(sub_cfg)=='function' or sub_cfg=='break' then
266       down, up = sub_cfg, nil
267    else error "Invalid walk config" end
268
269    if down then
270       if down=='break' then broken='break'
271       else broken = down (x, ...) end
272       assert(not broken or broken=='break', 
273              "Map functions must return 'break' or nil")
274    end
275    if not broken and traverse then traverse (cfg, x, ...) end
276    if up then up (x, ...) end
277 end
278
279 ----------------------------------------------------------------------
280 -- Declare [walk.stat], [walk.expr], [walk.block] and [walk.expr_list]
281 ----------------------------------------------------------------------
282 for w in values{ "stat", "expr", "block", "expr_list",
283    "malformed", "unknown" } do
284    walk[w] = walker_builder (w, walk.traverse[w])
285 end
286
287 ----------------------------------------------------------------------
288 -- Walk a list of `Id{...} (mainly a helper function actually).
289 ----------------------------------------------------------------------
290 function walk.binder_list (cfg, x, ...)
291    local f = cfg.binder 
292    if f then for v in ivalues(x) do f(v, ...) end end
293 end
294
295 ----------------------------------------------------------------------
296 -- Tries to guess the type of the AST then choose the right walkker.
297 ----------------------------------------------------------------------
298 function walk.guess (cfg, x, ...)
299    assert(type(x)=='table', "arg #2 in a walker must be an AST")
300    if walk.tags.expr[x.tag] then return walk.expr(cfg, x, ...)  end
301    if walk.tags.stat[x.tag] then return walk.stat(cfg, x, ...)  end
302    if not x.tag             then return walk.block(cfg, x, ...) end
303    error ("Can't guess the AST type from tag "..(x.tag or '<none>')) 
304 end