Antlr4的监听器和访问者 - 应该实现哪一个?

64
我正在阅读《The Definitive Antlr 4 Reference》,并理解了关于Listeners和Visitors的工作原理。这本书特别好地解释了Listeners与SAX解析器的关系,并在每个实现过程中明确指出哪些方法将被调用。我也可以看到,Listeners非常适合将输入转换为输出,但我想知道何时使用Listener和何时使用Visitor(或者在某些情况下它们都应该使用?)。
我的特定意图是创建一个解释器(类似Cucumber/TinyBasic Interpreter并带有一些自定义调用),该解释器将检查语法错误,并在自定义函数出现错误时停止执行而不进行恢复 - 如果有人碰巧知道antlr的完整实现,我会很高兴看到这样的东西。
感谢您提前给予任何建议。

5
@OP,请问你能否发布一个更新,讲一下你实际使用了什么,以及得到了哪些收获。谢谢。 - Dinesh
5个回答

81

以下是我认为相关的书中引用:

侦听器机制和访问者机制之间最大的区别在于,ANTLR提供的Walker对象调用了监听器方法,而访问者方法必须通过显式的访问调用来遍历其子节点。如果忘记在节点的子项上调用visit(),则这些子树将不会被访问。

在访问者模式中,您有能力指导树形遍历,而在监听器中,您只能对树形遍历作出反应。


53

如果你打算直接使用解析器输出进行解释,访问者是一个不错的选择。你可以完全控制遍历过程,所以在条件语句中只有一个分支被访问,循环可以被访问n次等等。

如果你将输入翻译成更低级别的形式,例如虚拟机指令,两种模式都可能很有用。

你可以看一下《Language Implementation Patterns》,它介绍了基本解释器实现。

我主要使用访问者模式,因为它更加灵活。


31

这两种模式之间还有一个重要的区别:访问器使用调用栈来管理树遍历,而监听器使用在堆上分配的显式堆栈,由walker进行管理。这意味着对于访问器来说,大输入可能会使栈溢出,而对于监听器来说则没有问题。

如果您的输入可能是无限制的,或者您可能在调用树中非常深地调用您的访问器,那么您应该使用监听器,而不是访问器,或者至少验证解析树不是太深。一些公司的编码惯例因为这个原因而不鼓励甚至明令禁止非尾递归。


你有这方面的资料吗?我想了解一下这两种方法,以确定哪种更适合我的情况。谢谢! - andersonjwan
你有这方面的资料吗?我想了解一些关于这两种方法的更多信息,以便确定哪种对我的情况更好。谢谢! - undefined
@andersonjwan 你可以阅读Terence Parr的《The Definitive ANTLR 4 Reference》。 - Alex Reinking
谢谢回复!我正在阅读这本书,但我更想知道关于调用栈和显式栈概念的来源。这在书中有提到吗?还是在库的实现细节中找到的? - andersonjwan

4

来自书中,第120页。

如果我们需要应用程序特定的返回值,访问者模式非常有效,因为我们可以使用内置的Java返回值机制。如果我们不想显式地调用访问子节点的方法,我们可以切换到监听器机制。不幸的是,这意味着放弃使用Java方法返回值的简洁性。

这就是为什么我使用访问者模式的原因。


3
我使用Golang进行了基准测试。Listener比Visitor稍微快一些,但没有明显的优势。
cpu: Intel(R) Xeon(R) CPU E5-2630 v4 @ 2.20GHz
BenchmarkCalcVisitorCases
BenchmarkCalcVisitorCases-16                5342        224675 ns/op
BenchmarkCalcListenerCases
BenchmarkCalcListenerCases-16               4676        230239 ns/op
BenchmarkCalculatorVisitorCases
BenchmarkCalculatorVisitorCases-16          2678        428312 ns/op
BenchmarkCalculatorListenerCases
BenchmarkCalculatorListenerCases-16         2149        540031 ns/op

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接