在ANTLR中删除左递归

在ANTLR中删除左递归,antlr,compiler-theory,Antlr,Compiler Theory,如中所述,有两种方法可以删除左递归 使用一些过程修改原始语法以删除左递归 写语法本来就没有左递归 人们通常使用什么来删除(没有)ANTLR的左递归?我在解析器中使用了flex/bison,但我需要使用ANTLR。使用ANTLR(或Generaral中的LL解析器)唯一让我担心的是左递归移除 实际上,在ANTLR中删除左递归有多严重?这是使用ANTLR的一个障碍吗?或者,在ANTLR社区没有人关心它 我喜欢ANTLR上一代的想法。就快速简便地获取AST而言,哪种方法(在两种删除左递归方法中)

如中所述,有两种方法可以删除左递归

  • 使用一些过程修改原始语法以删除左递归
  • 写语法本来就没有左递归
人们通常使用什么来删除(没有)ANTLR的左递归?我在解析器中使用了flex/bison,但我需要使用ANTLR。使用ANTLR(或Generaral中的LL解析器)唯一让我担心的是左递归移除

  • 实际上,在ANTLR中删除左递归有多严重?这是使用ANTLR的一个障碍吗?或者,在ANTLR社区没有人关心它
  • 我喜欢ANTLR上一代的想法。就快速简便地获取AST而言,哪种方法(在两种删除左递归方法中)更可取
补充 我用下面的语法做了一些实验

E -> E + T|T T -> T * F|F F -> INT | ( E ) E->E+T | T T->T*F | F F->INT |(E) 在左递归移除之后,我得到了下面的一个

E -> TE' E' -> null | + TE' T -> FT' T' -> null | * FT' E->TE' E'->null |+TE' T->FT' T'->null |*FT' 我可以提出以下ANTLR表示。尽管它相对来说非常简单和直接,但似乎没有左递归的语法应该是更好的方法

grammar T; options { language=Python; } start returns [value] : e {$value = $e.value}; e returns [value] : t ep { $value = $t.value if $ep.value != None: $value += $ep.value } ; ep returns [value] : {$value = None} | '+' t r = ep { $value = $t.value if $r.value != None: $value += $r.value } ; t returns [value] : f tp { $value = $f.value if $tp.value != None: $value *= $tp.value } ; tp returns [value] : {$value = None} | '*' f r = tp { $value = $f.value; if $r.value != None: $value *= $r.value } ; f returns [int value] : INT {$value = int($INT.text)} | '(' e ')' {$value = $e.value} ; INT : '0'..'9'+ ; WS: (' '|'\n'|'\r')+ {$channel=HIDDEN;} ; 语法T; 选择权{ 语言=Python; } 开始返回[值] :e{$value=$e.value}; e返回[值] :t ep { $value=$t.value 如果$ep.value!=无: $value+=$ep.value } ; ep返回[值] :{$value=None} |“+”TR=ep { $value=$t.value 如果$r.value!=无: $value+=$r.value } ; t返回[值] :f tp { $value=$f.value 如果$tp.value!=无: $value*=$tp.value } ; tp返回[值] :{$value=None} |'*'f r=tp { $value=$f.value; 如果$r.value!=无: $value*=$r.value } ; f返回[int值] :INT{$value=INT($INT.text)} |“('e')”{$value=$e.value} ; INT:'0'..'9'+; WS:(“”|’\n’|’\r')+{$channel=HIDDEN;};
如果您正在编写语法,那么您当然会尝试编写它以避免特定解析器生成器的陷阱

通常,根据我的经验,我会得到一些感兴趣的(遗留)语言的参考手册,它已经包含了语法或铁路图,它就是这样

在这种情况下,从语法中删除左递归几乎是手工完成的。左递归删除工具没有市场,如果你有一个,它将专门用于与你的语法不匹配的语法语法

在很多情况下,做这个移除主要是一件汗水的事情,而且通常不会有很多汗水。因此,通常的方法是拿出你的语法刀,并在它

我不认为删除左递归会改变ANTLR获取树的方式。您必须首先删除左递归,否则ANTLR(或您正在使用的任何LL解析器生成器)将无法接受您的语法

我们中有些人不希望解析器生成器对我们可以编写的上下文无关语法设置任何严格的约束。在本例中,您希望使用GLR解析器生成器之类的工具,它可以轻松地处理左递归或右递归。不讲道理的人甚至可以坚持自动生成AST,而语法编写者则不费吹灰之力。有关可以同时执行这两项操作的工具,请参见

从实际意义上讲,这有多严重 在ANTLR中删除左递归?是 这是使用ANTLR的一个障碍吗

我认为你对左递归有误解。它是语法的属性,而不是解析器生成器的属性,也不是解析器生成器和规范之间的交互的属性。当规则右侧的第一个符号等于对应于规则本身的非终结符时,就会发生这种情况

要理解这里的固有问题,您需要了解递归下降(LL)解析器的工作原理。在LL解析器中,每个非终结符号的规则由对应于该规则的函数实现。假设我有这样一个语法:

S -> A B
A -> a
B -> b
boolean eat(char x) {
  // if the next character is x, advance the stream and return true
  // otherwise, return false
}

boolean S() {
  if (!A()) return false;
  if (!B()) return false;
  return true;
}

boolean A(char symbol) {
  return eat('a');
}

boolean B(char symbol) {
  return eat('b');
}
boolean A() {
  if (!A()) return false;  // stack overflow!  We continually call A()
                           // without consuming any input.
  eat('c');
  return true;
}
然后,解析器将(大致)如下所示:

S -> A B
A -> a
B -> b
boolean eat(char x) {
  // if the next character is x, advance the stream and return true
  // otherwise, return false
}

boolean S() {
  if (!A()) return false;
  if (!B()) return false;
  return true;
}

boolean A(char symbol) {
  return eat('a');
}

boolean B(char symbol) {
  return eat('b');
}
boolean A() {
  if (!A()) return false;  // stack overflow!  We continually call A()
                           // without consuming any input.
  eat('c');
  return true;
}
但是,如果我将语法更改为以下内容,会发生什么

S -> A B
A -> A c | null
B -> b
大概,我希望这个语法表示一种类似
c*b
的语言。LL解析器中的相应函数如下所示:

S -> A B
A -> a
B -> b
boolean eat(char x) {
  // if the next character is x, advance the stream and return true
  // otherwise, return false
}

boolean S() {
  if (!A()) return false;
  if (!B()) return false;
  return true;
}

boolean A(char symbol) {
  return eat('a');
}

boolean B(char symbol) {
  return eat('b');
}
boolean A() {
  if (!A()) return false;  // stack overflow!  We continually call A()
                           // without consuming any input.
  eat('c');
  return true;
}
所以,我们不可能有左递归。将语法改写为:

S -> A B
A -> c A | null
B -> b
解析器的变化如下:

boolean A() {
  if (!eat('c')) return true;
  A();
  return true;
}

(免责声明:这是我对LL解析器的基本近似,仅用于此问题的演示目的。它有明显的错误。)

考虑一些典型的参数列表:

parameter_list: parameter
              | parameter_list ',' parameter
              ;
由于您不关心诸如优先级或与参数的关联性之类的问题,因此很容易将其转换为正确的递归,代价是添加额外的产品:

parameter_list: parameter more_params
              ;

more_params:
           | ',' parameter more_params
           ;
对于最严重的情况,你可能想花一些时间在龙书上。在进行快速检查时,这主要在第4章中介绍


就严肃性而言,我很确定ANTLR根本不会接受包含左递归的语法,这会将其归入“绝对必要性”类别。

我不能代表ANTLR说话,但一般来说,消除形式左递归的步骤如下:

A -> A B
  -> B
将其更改为:

A -> B+
(注意
B
必须至少出现一次)

或者,如果ANTLR不支持Kleene闭包,您可以执行以下操作:

A -> B B'

B' -> B B'
   -> 

如果您提供了一个冲突规则的示例,我可以提供一个更好、更具体的答案。

这只是正交相关的,但我刚刚发表了一篇关于一种新解析方法的论文预印本,我称之为“pika解析”(c.f.packrat解析)它直接处理左递归语法,而不需要重写规则

写得很好