有人能给我一个LL解析与LR解析的简单例子吗?
答案 0 :(得分:434)
在较高的层次上,LL解析和LR解析之间的区别在于LL解析器从起始符号开始并尝试应用生成来到达目标字符串,而LR解析器从目标字符串开始并尝试返回在开始的符号。
LL分析是从左到右,最左边的派生。也就是说,我们从左到右考虑输入符号并尝试构造最左边的推导。这是通过从起始符号开始并反复展开最左边的非终结符直到我们到达目标字符串来完成的。 LR解析是从左到右,最右边的推导,意味着我们从左到右扫描并尝试构造最右边的推导。解析器不断选择输入的子串并尝试将其反转回非终结符。
在LL解析期间,解析器会在两个操作之间连续选择:
举个例子,给出这个语法:
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解析器中,有两个操作:
作为示例,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解析器也有很多种类(包括LL(*),ANTLR
工具使用),但实际上LL(1)是最广泛使用的。
作为一个无耻的插件,如果您想了解有关LL和LR解析的更多信息,我刚刚完成了编译器课程的教学,并在课程网站上有some handouts and lecture slides on parsing。如果你觉得它有用,我很乐意详细说明它们。
答案 1 :(得分:46)
Josh Haberman在他的文章LL and LR Parsing Demystified中声称LL解析直接与Polish Notation对应,而LR对应Reverse Polish Notation。 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解析器输出后序遍历。
对于深入的解释,例子和结论可以查看哈伯曼的article。
答案 2 :(得分:5)
LL使用自上而下,而LR使用自下而上的方法。
如果你解析一个编程语言:
答案 3 :(得分:1)
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以不自然的方式编写语法。
答案 4 :(得分: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)