In
a previous post, I've begun to write a system to keep the AST / source code of a program in sync together; as a result, we were able to associate a source skeleton (string with holes in it for children nodes) to any given AST node, provided that this node was generated from sources.
To make this useful, we must be able to generate source code from modified and/or generated AST parts; this post will describe how to do so. Be warned, there's nothing really fancy about it, it's pretty much brute force, systematic code walking. The resulting code is available in the Metalua repository, there:
src/samples/synth.mlua
First, I won't bother to use the
walk library that comes with Metalua. This lib is a support for the visitor pattern, which is useful if you want to leave alone most of the AST nodes, or if you want to do pretty much the same thing on each node. In short, it saves its users from writing the tree traversal code for each kind of node, but this won't save much work if you have to do something different for each kind of node anyway.
So everything will be embedded in a
synth class: this class will:
- keep a generated source code accumulator;
- keep track of the current indentation level;
- Support some basic code creation methods: adding strings (:acc()), properly indented newlines (:nl()), handling indentation level (:nlindent() and :nldedent()),
- and actually walk the AST nodes (:node() and :list()).
I won't go into a painstaking description of each of these methods, and the source file above is extensively commented anyway. The only noteworthy point is the way
:node() works: its job is to properly generate source from an AST node, and it does so by delegating to a method specialized for the given node's tag. That's one of those cases where Lua's "Everything is a table" paradygm is useful: an AST whose tag is "Foo" is going to be rendered by a method
synth:Foo(ast, ...). There's no need to establish an explicit tag->method association, the method's name
is the association. So
:node() dispatches nodes to those specialized methods, which perform series of calls to
:acc(),
:nl(),
:nlindent(),
:nldedent()),
:node() and
:list().
Now Lua supports some syntax sugar, and there are some patterns which should be recognized and printed in a nicer way:
- "foo['bar']" should be printed "foo.bar" when bar is a valid identifier name;
- "x.y.z = function() ... end" should be printed "function x.y.z() ... end";
- "x.y.z.methodname = function (self, ...) end" should be printed "function x.y.z:methodname (...) end";
- "{ ['foo'] = bar }" should be printed "{ foo = bar }" if foo is a valid identifier name;
- "foo('bar')", "foo({bar})", "foo:bar('gnat')" and "foo:bar({gnat})" should be respectively printed "foo 'bar'", "foo{ bar }", "foo:bar 'gnat'" and "foo:bar{ gnat }";
- Unnecessary parentheses around operators should be avoided.
All of these patterns, plus a couple of trivial ones, are easily detected with structural pattern matching: This is the main reason why synth's code is significantly nicer in Metalua than it would have been in plain Lua. As a result, it's a fairly good example of advanced patterns usage, for a purpose that will be intuitive to all Lua programmer.
This example will now have, in a future post, to be mixed with the "weaver" from a previous post. This weaver caused an error when it hadn't enough information to retrieve an AST's code skeleton. Now instead of causing an error, it should ask the synthetizer to produce decent looking source code for such AST parts. We'll see that it still hides a couple of non-trivial issues :)
Finally, I'd like to state that this program is useful by itself, as opposed to the previous one: the source it produces is readable, significantly more so than big chunks of ASTs, and as such it is a nice way to debug code generated by hairy macros; I've written it 3 days ago and I'm already dependent on it! It will certainly be added, under a form or another, to the compiler's optional outputs.
8 comments:
Post a Comment