Sample v4 generated visitor
I have a prototype working for the automatic parse tree construction and automatic visitor generation. Imagine we have the following simple grammar:
grammar T; s : i=ifstat ; ifstat : 'if' '(' INT ')' ID '=' ID ';' ;
The usual startup code looks like:
TLexer t = new TLexer(new ANTLRFileStream(args[0])); CommonTokenStream tokens = new CommonTokenStream(t); TParser p = new TParser(tokens); p.s(); // invoke the start rule, s
To make it create a parse tree, all we have to do is set a flag in the parser and then ask for the rule context object. The rule context object is actually a node in the parse tree, implementing interface ParseTree:
... p.setBuildParseTrees(true); TParser.sContext tree = p.s(); System.out.println(tree.toStringTree(p));
So, given the following input:
if(3)a=x;
we would see something like the following:
(s (ifstat if ( 3 ) a = x ;))
It prints s as the root node and ifstat as the 1st child and then all tokens as children of ifstat node.
Ok, so what else can we do with the parse tree other than print it out? We want to listen in on the various enter and exit rule events, as well as potentially listening in on token visitation events. ANTLR generates 2 more files than it does now in v3: a listener and a blank listener that implements that interface:
public interface TListener extends ParseTreeListener { void enterRule(TParser.sContext ctx); void exitRule(TParser.sContext ctx); void enterRule(TParser.ifstatContext ctx); void exitRule(TParser.ifstatContext ctx); }
The blank implementation is something convenient to override:
public class BlankTListener implements TListener { public void enterRule(TParser.sContext ctx) { } public void exitRule(TParser.sContext ctx) { } public void enterRule(TParser.ifstatContext ctx) { } public void exitRule(TParser.ifstatContext ctx) { } public void enterEveryRule(ParserRuleContext ctx) { } public void exitEveryRule(ParserRuleContext ctx) { } public void visitToken(Token token) { } }
Note that if you want to track what happens as the visitor enters every single rule, you can override method enterEveryRule instead of putting code in every single enterRule variation. To visit the tree, just create a ParseTreeVisitor, pass in a listener, and tell the visitor to visit:
ParseTreeVisitor visitor = new ParseTreeVisitor(); TListener listener = new BlankTListener() { public void enterEveryRule(ParserRuleContext ctx) { System.out.println("enter rule "+TParser.ruleNames[ctx.ruleIndex]); } public void exitRule(TParser.sContext ctx) { // specific to rule s System.out.println("exit rule s"); } }; visitor.visit(listener, tree);
This seems like a pretty simple but powerful mechanism. It also is well within the comfort zone of the average programmer because they are providing callbacks instead of embedding actions within a grammar. The other benefit of this is that programmers can call the visitor multiple times without it reparsing the input.
It seems like a lot of extra code to generate in these extra 2 files, but I guess it can be totally ignored if you don't want it. It's great to have all of this infrastructure generated automatically.