Antlr4 Visitor真的是访客吗?
我一直在学习如何使用 Antlr4 的访问者创建 AST,在阅读了 Terrance Parr 的书以及专门针对 Antlr 访问者的 AST 生成主题的多个论坛后,似乎执行此操作的标准方法涉及覆盖 Antlr 生成的访问方法像这样(来自 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());
}
}
在本书和许多线程中,覆盖基本访问者的方法通常是在访问者本身内调用访问()。看起来,访问者本身控制着解析树的遍历。
然而,当我在其他地方查看访问者模式通常如何实现时,访问方法几乎从不在其内部调用访问()。通常,accept 方法在节点或元素内使用来管理树结构的遍历,并在该节点的子节点上调用 Accept 来执行此操作,而访问者主要处理该特定树节点上的特定访问所发生的操作。
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();
}
}
在这个特定示例中,PrintAddition() 是“ access()”方法被accept调用来执行操作。
我是否误解了访客模式? antlr4访客不是标准访客吗?或者在 Antlr 访客的背后发生了更多我不明白的事情。对我来说,访问者模式实现的简化描述是在节点的子节点上使用“accept”方法来遍历树,同时调用访问者对该节点的子节点执行操作。
我感谢有关该主题的任何帮助,如果有任何不清楚的地方,我深表歉意。
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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
除了提到
visit
方法实际上是tree.accept(this)
的便捷包装器的评论之外,您可能还想看看 AbstractParseTreeVisitor。visitChildren
的默认实现确实会根据情况调用visit*(ctx)
方法来递归地导航解析树。因此,您可以拥有一个不必重写所有visit*(cox)
方法的Visitor
类,让 Visitor 负责导航子树。也就是说,如果您想让 ANTLR 运行时完全为您处理导航,那么
*Listener
可以为您提供该功能(它不会返回像*Visitor
这样的值) code> 确实如此,但这更适合遍历整个解析树)。当我们想要通过遍历子树获取值时,和/或当我们想要执行类似解释器的操作时,我们倾向于使用 *Visitor 。如果您正在实现解释器,那么您当然希望控制访问哪些子项(以处理条件分支)以及访问子项的次数(用于迭代)。
Along with the comments that mention that the
visit
method is really a convenience wrapper aroundtree.accept(this)
, you may want to take a look at AbstractParseTreeVisitor. The default implementation ofvisitChildren
does, indeed take care of recursively navigating your parse tree calling thevisit*(ctx)
methods as appropriate. So you can have aVisitor<T>
class that does not necessarily override all of thevisit*(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).