在http://blog.ptsecurity.com/2016/06/theory-and-practice-of-source-code.html#java--and-java8-grammars之后,我尝试在相当复杂的语法中减少左递归。据我了解,非原始形式的递归会导致内存和处理时间方面的性能问题。
因此,我试图在语法中重构这些规则以仅使用“原始”递归。当然,该博客文章是我唯一一次看到关于Antlr的“原始”递归短语。因此,我只是猜测其含义/意图。在我看来,这意味着一条规则最多仅将一个规则分支称为lhs。是吗?
此刻我有一个表达式规则,
expression
: expression DOUBLE_PIPE expression # ConcatenationExpression
| expression PLUS expression # AdditionExpression
| expression MINUS expression # SubtractionExpression
| expression ASTERISK expression # MultiplicationExpression
| expression SLASH expression # DivisionExpression
| expression PERCENT expression # ModuloExpression
...
;
...
包含许多子规则,这些子规则也回头引用expression
。但这是唯一具有直接递归的。
如果我理解正确,将它们重构为“原始”递归将类似于:
expression
: binaryOpExpression # BinaryOpExpression
...
;
binaryOpExpression
: expression DOUBLE_PIPE expression # ConcatenationExpression
| expression PLUS expression # AdditionExpression
| expression MINUS expression # SubtractionExpression
| expression ASTERISK expression # MultiplicationExpression
| expression SLASH expression # DivisionExpression
| expression PERCENT expression # ModuloExpression
;
首先,这是正确的重构吗?
其次,这真的有助于提高性能吗?归根结底,这仍然是相同的决定,所以我不太了解这如何提高性能(除了也许产生更少的ATNConfig对象)。
谢谢
答案 0 :(得分:0)
在这种情况下,我之前从未听说过“原始递归”,而作者可能只打算在ANTLR4中命名特定形式的递归。
事实是ANTLR4中有3种相关的递归形式:
a: ab | c;
a: b | c; b: c | d; c: a | e;
(在ANTLR4中不允许)a: ba | c;
。但是,“右递归”这个名称仅在二进制表达式的情况下才是正确的,但通常通常用来与左递归区分开。已经说过,很明显您的重写是错误的,因为它将创建ANLTR4不支持的间接左递归。从内存或性能的角度来看,直接左递归通常不是问题,因为ANTLR4会将它们转换为非递归的ATN规则图。
正确的递归可能会成为问题,因为它们是通过代码递归(运行时中的递归函数调用)实现的,这可能会很快耗尽CPU堆栈。我见过大表达式无法在单独线程中解析的情况,因为我无法将线程堆栈大小设置为更大的值(主线程堆栈大小通常可以通过链接器设置进行调整)。
后一种情况的唯一解决方案(我发现非常有用)是减少语法中相互调用的解析器规则的数量。当然,将某些表达式元素放在不同的规则中(例如andExpression
,orExpression
,bitExpression
等,是结构,可读性等问题,但这可能会导致很深的调用堆栈,这可能会耗尽CPU堆栈和/或需要大量时间来处理它们。