Antlr4 Visitor 真的是访客吗?

Is Antlr4 Visitor really a 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());
    }
}

在本书和许多主题中,覆盖基本访问者的典型方法是在访问者自身中调用 visit()。看起来,访问者本身控制着解析树的遍历。

但是,当我在其他地方查看访问者模式的典型实现方式时,visit 方法几乎从不在其自身内部调用 visit()。通常在节点或元素中使用 accept 方法来控制树结构的遍历,并在节点的子节点上调用 accept 来执行此操作,而访问者主要处理在该特定树的节点上针对该特定访问发生的操作。

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();    
    }
}

在此特定示例中,PrintAddition() 是 accept 调用以执行操作的“visit()”方法。

我是不是误解了访问者模式? antlr4访问者不是标准访问者吗?或者在我不理解的 Antlr 访问者的幕后发生了更多事情。对我来说,访问者模式实现的简化描述是在节点的子节点上使用“accept”方法遍历树,同时调用访问者对该节点的子节点执行操作。

非常感谢有关该主题的任何帮助,如果有任何不清楚的地方,我们深表歉意。

连同提到 visit 方法实际上是 tree.accept(this) 的便利包装的评论,您可能想看看 AbstractParseTreeVisitorvisitChildren 的默认实现确实负责递归导航您的解析树,并根据需要调用 visit*(ctx) 方法。所以你可以有一个 Visitor<T> class 不一定覆盖所有 visit*(cox) 方法,让访问者负责导航 sub-tree.

就是说,如果您想让 ANTLR 运行时完全为您处理导航,那么 *Listener 会为您提供该功能(它不会 return 像 *Visitor 可以,但这更适合遍历整个解析树)。

当我们想遍历一个sub-tree来获取一个值时,我们倾向于选择*Visitor,当我们想做类似解释器的事情时,and/or。如果您正在实施解释器,那么您当然希望控制访问了哪些子项(以处理条件分支)以及访问了多少次子项(用于迭代)。