【问题标题】:What is the difference between LL and LR parsing?LL和LR解析有什么区别?
【发布时间】:2011-08-23 23:04:47
【问题描述】:

谁能给我一个 LL 解析与 LR 解析的简单示例?

【问题讨论】:

    标签: algorithm parsing compiler-construction ll lr


    【解决方案1】:

    在高层次上,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. Reduce:通过反转产生式,将此缓冲区中的终端和非终端集合减少到某个非终端。

    例如,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) 等),并且功能更强大。它们也往往更复杂,并且几乎总是由yaccbison 等工具生成。 LL 解析器也有多种形式(包括 ANTLR 工具使用的 LL(*)),但实际上 LL(1) 是最广泛使用的。

    作为一个无耻的插件,如果您想了解更多关于 LL 和 LR 解析的知识,我刚刚完成了编译器课程的教学,并在课程网站上提供了some handouts and lecture slides on parsing。如果您认为有用,我很乐意详细说明其中的任何一个。

    【讨论】:

    • 你的演讲幻灯片非常出色,很容易成为我见过的最有趣的解释:) 这才是真正激发兴趣的东西。
    • 我也必须对幻灯片发表评论!现在正在经历所有这些。有很大帮助!谢谢!
    • 真的很喜欢幻灯片。我不认为您可以发布项目文件的非 Windows 版本(以及用于 pp2 的scanner.l 文件)? :)
    • 我可以对 Matt 的出色总结答案做出贡献的一件事是,任何可以被 LL(k) 解析器解析的语法(即,向前看“k”终端来决定下一个解析操作) 可以被 LR(1) 解析器解析。这暗示了 LR 解析优于 LL 解析的惊人力量。资料来源:UCSC 的编译器课程,由 LALR() 解析器的创建者 F. DeRemer 博士讲授。
    • 优秀的资源!感谢您提供幻灯片、讲义和项目。
    【解决方案2】:

    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 解析器输出后序遍历。

    有关深入的解释、示例和结论,请查看 Haberman 的article

    【讨论】:

      【解决方案3】:

      LL采用自上而下,LR采用自下而上。

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

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

      【讨论】:

        【解决方案4】:

        与 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 写你的语法。

        【讨论】:

          猜你喜欢
          • 2021-02-05
          • 1970-01-01
          • 2013-11-08
          • 2011-02-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-03-30
          相关资源
          最近更新 更多