LL和LR解析之间有什么区别?


Answers:


483

在较高的层次上,LL解析和LR解析之间的区别是LL解析器从开始符号开始,并尝试应用产生式来到达目标字符串,而LR解析器从目标字符串开始,并尝试返回到开始字符串符号。

LL解析是从左到右,最左边的推导。也就是说,我们考虑从左到右的输入符号,并尝试构造最左侧的导数。这是通过从开始符号开始并反复扩展最左边的非终结符来完成的,直到我们到达目标字符串为止。LR解析是从左到右,最右边的派生,这意味着我们从左到右扫描并尝试构建最右边的派生。解析器连续选择输入的子字符串,并尝试将其反向返回非终端。

在LL解析期间,解析器在两个动作之间连续选择:

  1. 预测:基于最左边的非终结符和一些先行标记,选择应应用哪种生成方式以更接近输入字符串。
  2. 匹配:将最左侧的猜测终端符号与输入的最左侧未使用符号匹配。

例如,给出以下语法:

  • S→E
  • E→T + E
  • E→T
  • T→ int

然后给定字符串int + int + int,LL(2)解析器(使用两个先行标记)将按如下方式解析字符串:

Production       Input              Action
---------------------------------------------------------
S                int + int + int    Predict S -> E
E                int + int + int    Predict E -> T + E
T + E            int + int + int    Predict T -> int
int + E          int + int + int    Match int
+ E              + int + int        Match +
E                int + int          Predict E -> T + E
T + E            int + int          Predict T -> int
int + E          int + int          Match int
+ E              + int              Match +
E                int                Predict E -> T
T                int                Predict T -> int
int              int                Match int
                                    Accept

请注意,在每个步骤中,我们都会查看生产中最左边的符号。如果它是终端,则将其匹配,如果它是非终端,则可以通过选择规则之一来预测它的状态。

在LR解析器中,有两个操作:

  1. Shift:将输入的下一个令牌添加到缓冲区以供考虑。
  2. 减少:通过反转生产,将该缓冲区中的末端和非末端集合减少回某些非末端。

例如,一个LR(1)解析器(带有一个前瞻标记)可以解析相同的字符串,如下所示:

Workspace        Input              Action
---------------------------------------------------------
                 int + int + int    Shift
int              + int + int        Reduce T -> int
T                + int + int        Shift
T +              int + int          Shift
T + int          + int              Reduce T -> int
T + T            + int              Shift
T + T +          int                Shift
T + T + int                         Reduce T -> int
T + T + T                           Reduce E -> T
T + T + E                           Reduce E -> T + E
T + E                               Reduce E -> T + E
E                                   Reduce S -> E
S                                   Accept

您提到的两种解析算法(LL和LR)具有不同的特性。LL解析器往往更易于手工编写,但是它们不如LR解析器强大,并且接受的语法集比LR解析器小得多。LR解析器有多种形式(LR(0),SLR(1),LALR(1),LR(1),IELR(1),GLR(0)等),并且功能更强大。它们也往往更加复杂,并且几乎总是由诸如yacc或的工具生成的bison。LL解析器也有很多风格(包括ANTLR工具使用的LL(*)),但实际上LL(1)是使用最广泛的。

作为一个无耻的插件,如果您想了解有关LL和LR解析的更多信息,我刚刚完成了编译器课程的教学,在课程网站上提供了一些有关解析的讲义和讲座幻灯片。如果您认为有用的话,我很乐于详细介绍。


40
您的演讲幻灯片是惊人的,很容易成为我所见过的最有趣的解释:)这实际上是激发兴趣的一种方式。
kizzx2 2011年

1
我也必须在幻灯片上发表评论!现在经历所有这些。很有帮助!谢谢!
kornfridge

真的很喜欢幻灯片。我不认为您可以发布非Windows版本的项目文件(以及pp2的scanner.l文件)?:)
Erik P.

1
我能为Matt出色的摘要答案做出一件事的是,可以由LL(k)解析器解析的任何语法(即,向前看“ k”终端来决定下一个解析动作)都可以由LR( 1)解析器。这暗示了LR解析比LL解析具有不可思议的强大功能。资料来源:由LALR()解析器的创建者F. DeRemer博士教授的UCSC编译器课程。
JoGusto

1
极好的资源!感谢您提供幻灯片,讲义,项目。
P. Hinker

58

乔什•哈伯曼(Josh Haberman)在他的文章LL和LR解析神秘的文章中声称LL解析直接对应于波兰符号,而LR对应于反向波兰符号。PN和RPN之间的差异是遍历方程的二叉树的顺序:

方程的二叉树

+ 1 * 2 3  // Polish (prefix) expression; pre-order traversal.
1 2 3 * +  // Reverse Polish (postfix) expression; post-order traversal.

根据Haberman,这说明了LL和LR解析器之间的主要区别:

LL和LR解析器的工作方式之间的主要区别是LL解析器输出解析树的前序遍历,而LR解析器输出后序遍历。

有关深入的解释,请参见示例和结论,以查看Haberman的文章


9

LL使用自上而下的方法,而LR使用自下而上的方法。

如果您解析一种编程语言:

  • LL看到一个源代码,其中包含函数,其中包含表达式。
  • LR看到属于函数的表达式,从而得到完整的源代码。

6

与LR相比,LL解析受到了限制。这是LL解析器生成器的噩梦:

Goal           -> (FunctionDef | FunctionDecl)* <eof>                  

FunctionDef    -> TypeSpec FuncName '(' [Arg/','+] ')' '{' '}'       

FunctionDecl   -> TypeSpec FuncName '(' [Arg/','+] ')' ';'            

TypeSpec       -> int        
               -> char '*' '*'                
               -> long                 
               -> short                   

FuncName       -> IDENTIFIER                

Arg            -> TypeSpec ArgName         

ArgName        -> IDENTIFIER 

直到';'为止,FunctionDef看起来完全像FunctionDecl。或遇到“ {”。

LL解析器无法同时处理两个规则,因此必须选择FunctionDef或FunctionDecl。但是要知道哪个是正确的,它必须提前寻找一个';'。要么 '{'。在语法分析时,前瞻(k)似乎是无限的。在解析时,它是有限的,但可能很大。

LR解析器不必提前,因为它可以同时处理两个规则。因此LALR(1)解析器生成器可以轻松处理此语法。

给定输入代码:

int main (int na, char** arg); 

int main (int na, char** arg) 
{

}

LR解析器可以解析

int main (int na, char** arg)

不在乎遇到“;”之前要识别什么规则 或“ {”。

LL解析器挂在'int'上,因为它需要知道识别哪个规则。因此,它必须提前查找“;” 要么 '{'。

LL解析器的另一个噩梦是语法的递归。左递归在语法上是很正常的事情,对于LR解析器生成器来说没有问题,但是LL无法处理它。

因此,您必须使用LL以不自然的方式编写语法。


0

最左派的例子: 无上下文的文法G产生

z→xXY(规则:1)X→Ybx(规则:2)Y→bY(规则:3)Y→c(规则:4)

用最左边的导数计算字符串w ='xcbxbc'。

z⇒xXY(规则:1)⇒xYbxY(规则:2)⇒xcbxY(规则:4)⇒xcbxbY(规则:3)⇒xcbxbc(规则:4)


最右派生示例: K→aKK(规则:1)A→b(规则:2)

用最正确的派生方法计算字符串w ='aababbb'。

K⇒aKK(规则:1)⇒aKb(规则:2)⇒aaKKb(规则:1)⇒aaKaKKb(规则:1)⇒aaKaKbb(规则:2)⇒aaKabbb(规则:2)⇒aababbb(规则:2)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.