删除ANTLR中的左递归

时间:2010-06-08 17:34:30

标签: antlr compiler-theory

Removing left recursion中所述,有两种方法可以删除左递归。

  • 使用某些过程修改原始语法以删除左递归
  • 写出最初没有左递归的语法

人们通常用什么来移除(没有)ANTLR的左递归?我使用flex / bison进行解析器,但我需要使用ANTLR。我唯一担心的是使用ANTLR(或者是普通的LL解析器)就是去除递归。

  • 在实际意义上,删除ANTLR中的左递归有多严重?这是使用ANTLR的一个显示器吗?或者,ANTLR社区中没有人关心它?
  • 我喜欢AN生成AST的想法。在获得AST快速简便的方法方面,哪种方法(在2个删除左递归方法中)更可取?

我用以下语法做了一些实验。

E -> E + T|T
T -> T * F|F
F -> INT | ( E )

删除左递归后,我得到以下一个

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

5 个答案:

答案 0 :(得分:7)

考虑类似典型参数列表的内容:

parameter_list: parameter
              | parameter_list ',' parameter
              ;

由于您不关心任何类似优先级或与参数的关联性,因此转换为正确递归相当容易,但会增加额外的生产费用:

parameter_list: parameter more_params
              ;

more_params:
           | ',' parameter more_params
           ;

对于最严重的情况,您可能希望在龙书中花一些时间。快速检查一下,主要在第4章中介绍。

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

答案 1 :(得分:4)

  

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

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

要理解这里的固有问题,您需要了解递归下降(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');
}

但是,如果我将语法更改为以下内容会发生什么?

S -> A B
A -> A c | null
B -> b

据推测,我希望这个语法代表像c*b这样的语言。 LL解析器中的相应函数如下所示:

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解析器的基本近似,仅用于此问题的演示目的。它有明显的错误。)

答案 2 :(得分:2)

我不能代表ANTLR,但一般来说,消除表单左递归的步骤:

A -> A B
  -> B

将其更改为:

A -> B+

(请注意,B必须至少出现一次)

或者,如果ANTLR不支持Kleene闭包,你可以这样做:

A -> B B'

B' -> B B'
   -> 

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

答案 3 :(得分:1)

如果您正在编写语法,那么当然您会尝试编写它以避免特定解析器生成器的陷阱。

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

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

在许多情况下,这种移除主要是汗水问题,并且通常没有大量的移除。所以通常的方法就是拿出你的语法刀,然后就可以了。

我不认为如何删除左递归更改ANTLR如何获取树。你必须先做左递归,或者ANTLR(你正在使用的LL解析器生成器)根本不接受你的语法。

我们这些人不希望解析器生成器对我们可以为无上下文语法编写的内容施加任何严格的约束。在这种情况下,您希望使用类似GLR解析器生成器的东西,它可以轻松处理左递归或右递归。不合理的人甚至可以坚持自动生成AST而不需要语法编写器。对于可以同时执行这两项操作的工具,请参阅DMS Software Reengineering Toolkit

答案 4 :(得分:0)

这只是正交相关的,但是我刚刚在一种新的解析方法上发表了论文的预印本,我称之为“ pika解析”(cf packrat解析),该方法直接处理左递归语法而无需重写规则。

https://arxiv.org/abs/2005.06444