Question:
I’ve been learning how to make an AST with Antlr4’s visitor and after reading Terrance Parr’s book, and multiple forums on the topic of AST generation specifically with Antlr visitors, it seems that the standard approach for doing this involves overriding the Antlr generated visit methods like this (From The Definitive Antlr 4 Reference).public static class EvalVisitor extends LExprBaseVisitor<Integer> {
public Integer visitMult(LExprParser.MultContext ctx) {
return visit(ctx.e(0)) * visit(ctx.e(1));
}
public Integer visitAdd(LExprParser.AddContext ctx) {
return visit(ctx.e(0)) + visit(ctx.e(1));
}
public Integer visitInt(LExprParser.IntContext ctx) {
return Integer.valueOf(ctx.INT().getText());
}
}
In this book, and many threads, typically the way to approach overriding the base visitor is to call visit() within the visitor itself. Seemingly, the visitor itself governs the traversal of the parse tree.However, when I look anywhere else about how the visitor pattern is typically implemented, the visit method almost never calls visit() within itself. Usually the accept method is used within a node or element to govern the traversal of a tree structure and calls accept on the node’s children to do so, while the visitor mainly handles what operations occur for that particular visit, on that particular tree’s node.
Wikipedia Example, but reduced for most important parts
public class ExpressionPrintingVisitor
{
public void PrintAddition(Addition addition)
{
double leftValue = addition.Left.GetValue();
double rightValue = addition.Right.GetValue();
var sum = addition.GetValue();
Console.WriteLine("{0} + {1} = {2}", leftValue, rightValue, sum);
}
}
public class Addition : Expression
{
public Expression Left { get; set; }
public Expression Right { get; set; }
public Addition(Expression left, Expression right)
{
Left = left;
Right = right;
}
public override void Accept(ExpressionPrintingVisitor v)
{
Left.Accept(v);
Right.Accept(v);
v.PrintAddition(this);
}
public override double GetValue()
{
return Left.GetValue() + Right.GetValue();
}
}
In this particular example, PrintAddition() is the “visit()” method being called by accept to perform the operation.Am I misunderstanding the visitor pattern? Is the antlr4 visitor not a standard visitor? Or is more happening under the hood of the Antlr visitor that I’m not understanding. To me, the simplified description of the visitor pattern implementation is using an “accept” method on a node’s children to traverse a tree, while calling the visitor to perform operations on the children of that node.
I appreciate any help on the topic, and apologize if anything is unclear.
Answer:
Along with the comments that mention that thevisit
method is really a convenience wrapper around tree.accept(this)
, you may want to take a look at AbstractParseTreeVisitor. The default implementation of visitChildren
does, indeed take care of recursively navigating your parse tree calling the visit*(ctx)
methods as appropriate. So you can have a Visitor<T>
class that does not necessarily override all of the visit*(cox)
methods and let’s the Visitor take care of navigating the sub-tree.That said, if you want to have the ANTLR runtime completely handle the navigation for you, then the
*Listener
gives you that capability (It doesn’t return a value like a *Visitor
does, but that is more appropriate for traversing an entire parse tree).We tend to prefer
*Visitor
s when we want to obtain a value from traversing a sub-tree, and/or when we want to do something like an interpreter. If you’re implementing an interpreter, then you certainly want to take control of which children are visited (to handle conditional branching) as well as how many times children are visited (for iteration).If you have better answer, please add a comment about this, thank you!
Leave a Review