Antlr4 Visitor真的是访客吗?

发布于 2025-01-10 11:08:44 字数 2004 浏览 1 评论 0原文

我一直在学习如何使用 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

晨曦慕雪 2025-01-17 11:08:44

除了提到 visit 方法实际上是 tree.accept(this) 的便捷包装器的评论之外,您可能还想看看 AbstractParseTreeVisitorvisitChildren 的默认实现确实会根据情况调用 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 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 *Visitors 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).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文