Parsing 左递归解析 说明:

Parsing 左递归解析 说明:,parsing,recursion,compiler-construction,recursive-descent,left-recursion,Parsing,Recursion,Compiler Construction,Recursive Descent,Left Recursion,在阅读这本书的过程中,我遇到了以下描述上下文无关语法的规则: 1. statements ::= expression; 2. | expression; statements 3. expression ::= expression + term 4. | term 5. term ::= term * factor 6. | factor 7. factor ::= number 8

在阅读这本书的过程中,我遇到了以下描述上下文无关语法的规则:

1. statements ::= expression;
2.                | expression; statements
3. expression ::= expression + term
4.                | term
5. term       ::= term * factor
6.                | factor
7. factor     ::= number
8.                | (expression)
识别一个或多个语句列表的语法,每个语句 它是后跟分号的算术表达式。报表由一个 一系列分号分隔的表达式,每个表达式由一系列数字组成 用星号(用于乘法)或加号(用于加法)分隔

下面是语法:

1. statements ::= expression;
2.                | expression; statements
3. expression ::= expression + term
4.                | term
5. term       ::= term * factor
6.                | factor
7. factor     ::= number
8.                | (expression)
这本书指出这种递归语法有一个主要问题。几个产品的右侧显示在左侧,如产品3(此属性称为左递归),某些解析器(如递归下降解析器)无法处理左递归产品。它们只是永远循环

您可以通过考虑解析器在替换具有多个右侧的非终端时如何决定应用特定产品来理解问题。这个简单的例子在作品7和8中很明显。解析器可以通过查看下一个输入符号来选择在扩展因子时应用哪个产品。如果此符号是一个数字,则编译器应用产品7并用数字替换因子。如果下一个输入符号是开括号,则解析器 将使用生产8。然而,产品5和产品6之间的选择不能以这种方式解决。在生产6的情况下,术语的右侧以一个因子开始,在tum中,该因子以数字或左括号开始。因此 当替换一个术语并且下一个输入符号是数字或左括号时,解析器希望应用产品6。产品5-另一个右侧以一个术语开始,该术语可以以因子开始,可以以数字或左括号开始,这些符号与选择产品6时使用的符号相同


问题: 这本书的第二句话让我完全不知所措。因此,通过使用一些语句的示例(例如)
5+(7*4)+14

  • 因子和项之间有什么区别?用同样的例子
  • 为什么递归下降解析器不能处理左递归生成?(解释第二段引语)
  • 规则因子与字符串“1*3”匹配,但规则项不匹配(尽管它将匹配“(1*3)”。本质上,每个规则代表一个优先级。
    表达式
    包含优先级最低的运算符、
    因子
    次低的运算符和
    术语
    最高的运算符。如果您是术语,并且要使用优先级较低的运算符,则需要添加括号

  • 如果使用递归函数实现递归下降解析器,则类似于
    a::=b“*”c | d
    的规则可能实现如下:

    // Takes the entire input string and the index at which we currently are
    // Returns the index after the rule was matched or throws an exception
    // if the rule failed
    parse_a(input, index) {
      try {
        after_b = parse_b(input, index)
        after_star = parse_string("*", input, after_b)
        after_c = parse_c(input, after_star)
        return after_c
      } catch(ParseFailure) {
        // If one of the rules b, "*" or c did not match, try d instead
        return parse_d(input, index)
      }
    }
    

    这样的事情会很好(实际上你可能不想使用递归函数,但是你所用的方法仍然是类似的)。现在,让我们考虑左递归规则<代码>::=:“*”B C C <代码>代替:

    parse_a(input, index) {
      try {
        after_a = parse_a(input, index)
        after_star = parse_string("*", input, after_a)
        after_b = parse_c(input, after_star)
        return after_b
      } catch(ParseFailure) {
        // If one of the rules a, "*" or b did not match, try c instead
        return parse_c(input, index)
      }
    }
    
  • 现在函数
    parse_a
    做的第一件事是在同一个索引处再次调用自身。这个递归调用将再次调用自身。这将无限期地继续,或者更确切地说,直到堆栈溢出,整个程序崩溃。如果我们使用更有效的方法代替递归函数,我们将我会得到一个无限循环,而不是堆栈溢出。无论哪种方式,我们都不会得到我们想要的结果

  • 因子和术语的区别是什么?用同样的例子
  • 我不会给出同样的例子,因为它不会让你清楚地了解你对什么有疑问

    鉴于

    term       ::= term * factor | factor
    factor     ::= number | (expression)
    
    现在,假设我让你们找到表达式2*3*4中的因子和项。 现在,左相联乘法将计算为:-

    (2*3)*4
    
    正如你所看到的,这里(2*3)是术语,因子是4(一个数字)。同样地,你可以将这种方法扩展到任何级别,从而得出关于术语的概念

    根据给定的语法,如果在给定的表达式中有一个乘法链,那么它的子部分,留下一个因子,就是一个项,而这个项又产生另一个子部分——另一个项,留下另一个因子,依此类推。表达式就是这样计算的

  • 为什么递归下降解析器不能处理左递归结果?(解释第二段)
  • 您的第二条语句本质上非常清楚。递归下降解析器是一种自上而下的解析器,由一组相互递归的过程(或非递归的等价过程)构建而成,其中每个过程通常实现语法的一个结果

    之所以这样说,是因为很明显,如果非终端继续扩展到自身,递归下降解析器将进入无限循环

    类似地,谈到递归下降解析器,即使有回溯——当我们试图扩展一个非终端时,我们最终可能会发现自己在没有消耗任何输入的情况下再次尝试扩展同一个非终端

    A-> Ab
    
    在这里,非端子A在扩展的同时可以继续扩展为

    A-> AAb -> AAAb -> ... -> infinite loop of A.
    

    因此,在使用递归下降解析器时,我们会阻止左递归产生。

    另一方面,我建议您继续查询,作为编译器理论主题的参考。这是每个编译器爱好者的必读书籍。您能想出一个表达式(十进制数)吗这可能会导致无限循环(例如)?不,不是这样。实际上是“在扩展术语时,如果非终端没有找到任何匹配项,它将继续扩展,从而可能导致无限循环”。你可以先假设这样的语法,然后考虑允许无限循环的表达式。就像我在回答中提到的那样。用一个