谁能给我一个LL解析和LR解析的简单例子?


在较高的级别上,LL解析和LR解析之间的区别在于LL解析器从开始符号开始,并尝试应用结果来到达目标字符串,而LR解析器从目标字符串开始,并尝试返回开始符号。

An LL parse is a left-to-right, leftmost derivation. That is, we consider the input symbols from the left to the right and attempt to construct a leftmost derivation. This is done by beginning at the start symbol and repeatedly expanding out the leftmost nonterminal until we arrive at the target string. An LR parse is a left-to-right, rightmost derivation, meaning that we scan from the left to right and attempt to construct a rightmost derivation. The parser continuously picks a substring of the input and attempts to reverse it back to a nonterminal.

在LL解析过程中,解析器会连续在两个操作之间进行选择:

预测:根据最左边的非终结符和一些前瞻令牌,选择应该应用哪个结果以更接近输入字符串。 匹配:将最左边的猜测终端符号与输入中最左边的未使用的符号匹配。

举个例子,给定下面的语法:

S→e E→t + E E→t T→int

然后给定字符串int + int + int, LL(2)解析器(使用两个lookahead标记)将解析该字符串,如下所示:

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解析器中,有两个动作:

Shift:将下一个输入标记添加到缓冲区中以供考虑。 Reduce:通过反转一个结果,将该缓冲区中的终结符和非终结符集合减少到某个非终结符。

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

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

The two parsing algorithms you mentioned (LL and LR) are known to have different characteristics. LL parsers tend to be easier to write by hand, but they are less powerful than LR parsers and accept a much smaller set of grammars than LR parsers do. LR parsers come in many flavors (LR(0), SLR(1), LALR(1), LR(1), IELR(1), GLR(0), etc.) and are far more powerful. They also tend to have much more complex and are almost always generated by tools like yacc or bison. LL parsers also come in many flavors (including LL(*), which is used by the ANTLR tool), though in practice LL(1) is the most-widely used.

作为一个无耻的插件,如果你想了解更多关于LL和LR解析的知识,我刚刚教完一门编译器课程,并且在课程网站上有一些关于解析的讲义和讲座幻灯片。如果你认为有用的话,我很乐意详细说明其中的任何一个。


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的文章。


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

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

LL看到一个源代码,其中包含函数,其中包含表达式。 LR看到表达式,它属于函数,结果是完整的源。


与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不能处理它。

所以你必须用不自然的方式写你的语法。