【问题标题】:Is it possible to write a recursive-descent parser for this grammar?是否可以为此语法编写递归下降解析器?
【发布时间】:2016-07-06 03:00:51
【问题描述】:

来自this question,一种涉及二元运算符 (+ - * /) 的表达式语法,它不允许使用外括号:

top_level   : expression PLUS term
            | expression MINUS term
            | term TIMES factor
            | term DIVIDE factor
            | NUMBER
expression  : expression PLUS term
            | expression MINUS term
            | term
term        : term TIMES factor
            | term DIVIDE factor
            | factor
factor      : NUMBER
            | LPAREN expression RPAREN

这个语法是 LALR(1)。因此,我能够使用PLY(yacc 的 Python 实现)为语法创建自下而上的解析器。

出于比较的目的,我现在想尝试为相同的语言构建一个自顶向下的递归下降解析器。我已经改变了语法,删除了左递归并应用了左因子:

top_level   : expression top_level1
            | term top_level2
            | NUMBER
top_level1  : PLUS term
            | MINUS term
top_level2  : TIMES factor
            | DIVIDE factor
expression  : term expression1
expression1 : PLUS term expression1
            | MINUS term expression1
            | empty
term        : factor term1
term1       : TIMES factor term1
            | DIVIDE factor term1
            | empty
factor      : NUMBER
            | LPAREN expression RPAREN

如果没有top_level 规则,这个语法就是LL(1),所以编写一个递归下降解析器会相当简单。不幸的是,包括top_level,语法不是LL(1)。

  1. 此语法是否有“LL”分类(例如 LL(k)、LL(*))?
  2. 是否可以为此语法编写递归下降解析器?那怎么做? (需要回溯吗?)
  3. 是否可以简化此语法以简化递归下降方法?

【问题讨论】:

    标签: parsing grammar context-free-grammar ll lr


    【解决方案1】:

    语法不是有限前瞻的 LL,但语言是 LL(1),因为存在 LL(1) 语法。务实地讲,即使不修改语法,递归下降解析器也很容易编写。

    1. 此语法是否有“LL”分类(例如 LL(k)、LL(*))?

    如果α是expression的推导,β是term的推导,γ是factor的推导,那么top_level可以推导这两个句子(α)+β 和句子 (α)*γ (但它不能导出句子 ( α).) 然而,(α) 是expression 和term 的可能推导,所以它在遇到 ) 之后的符号之前,不可能决定使用哪个产生式 top_level。由于 α 可以是任意长度,所以没有 k 的前瞻 k 足以区分这两个产生式。有些人可能称之为 LL(∞),但这对我来说似乎不是一个非常有用的语法类别。 (LL(*) 是,afaik,Terence Parr 发明的解析策略的名称,而不是一类语法的公认名称。)我只想说语法不是 LL(k) 对于任何k。

    1. 是否可以为此语法编写递归下降解析器?那怎么做? (需要回溯吗?)

    当然。甚至没有那么难。

    第一个符号必须是NUMBER或(。如果是NUMBER,我们预测(调用)expression。如果是( kbd>,我们消费它,调用expression,消费下面的)(或者声明一个错误,如果下一个符号不是右括号),然后调用expression1或@987654333 @ 然后expression1,取决于下一个符号是什么。同样,如果下一个符号与expression1 或term1 的第一个集合不匹配,我们声明一个语法错误。注意上述策略确实根本不需要top_level* 产品。

    由于这显然可以在不回溯的情况下工作,因此可以作为编写 LL(1) 语法的基础。

    1. 是否可以简化此语法以简化递归下降方法?

    我不确定下面的语法是否更简单,但它确实对应于上面描述的递归下降解析器。

    top_level   : NUMBER optional_expression_or_term_1
                | LPAREN expression RPAREN expression_or_term_1
    optional_expression_or_term_1: empty
                | expression_or_term_1
    expression_or_term_1
                : PLUS term expression1
                | MINUS term expression1
                | TIMES factor term1 expression1
                | DIVIDE factor term1 expression1
    expression  : term expression1
    expression1 : PLUS term expression1
                | MINUS term expression1
                | empty
    term        : factor term1
    term1       : TIMES factor term1
                | DIVIDE factor term1
                | empty
    factor      : NUMBER
                | LPAREN expression RPAREN
    

    我留下了两个观察结果,你完全可以忽略它们(特别是第二个是 100% 的意见)。

    首先,我觉得禁止(1+2) 却允许(((1)))+2 或((1+2))+3 似乎很奇怪。但毫无疑问,你有你的理由。 (当然,您可以通过在 factor 的第二个产生式中将 expression 替换为 top_level 来轻松禁止多余的双括号。

    其次,在我看来,第三部分中 LL(1) 语法中涉及的跳环只是另一个原因,可以问为什么有任何理由使用 LL 语法。 LR(1) 文法更易读,与语言的句法结构对应更清晰。生成的递归下降解析器的逻辑可能更容易理解,但对我来说这似乎是次要的。

    【讨论】:

    • +1 表示在很容易找到更强大的解析器生成器时使用 LL(1)。为什么要买一个不必要的头痛?
    • 还有另一种简单的拒绝 (....) 方法,基于观察到任何有用的语言语法必须至少接受该语言,并且可能接受更多(很难完美)。所以我们通常用解析器做的是“接受(稍微)太多,并拒绝多余的”(通过在解析器之外使用一些机器)。拒绝解析 (.... ) 现在很容易:使用允许它的简单语法进行解析,并拒绝任何在顶层具有 (...) 的解析。
    • @IraBaxter:是的,我想提一下,但形式语法很简单,手工构建的 RDP 的代码最终会与您最终得到的代码非常相似拒绝策略。 (恕我直言,你需要一个很好的理由来禁止外括号,以证明花时间回答这个问题是合理的:))
    • 在你的最终语法中,expression_or_term1 -> expression1 -> empty,所以top_level -> '(' expression ')',原来的语法中没有……
    • @chrisdodd:很好。这也造成了歧义。我会修复它,但它不会很漂亮。
    【解决方案2】:

    要生成语法 LL(1),您需要完成左分解 top_level。 您停在:

    top_level   : expression top_level1
                | term top_level2
                | NUMBER
    

    expression 和 term 在它们的 FIRST 集中都有 NUMBER,所以它们必须首先被替换为左因子:

    top_level   : NUMBER term1 expression1 top_level1
                | NUMBER term1 top_level2
                | NUMBER
                | LPAREN expression RPAREN term1 expression1 top_level1
                | LPAREN expression RPAREN term1 top_level2
    

    然后你可以把它留到

    top_level   : NUMBER term1 top_level3
                | LPAREN expression RPAREN term1 top_level4
    
    top_level3  : expression1 top_level1
                | top_level2
                | empty
    
    top_level4  : expression1 top_level1
                | top_level2
    

    请注意,这仍然不是 LL(1),因为存在具有重叠 FIRST 和 FOLLOW 集的 epsilon 规则(term1,expression1)。因此,您也需要将这些因素考虑在内以使其成为 LL(1)

    【讨论】:

    • 谢谢。看起来,如果左分解的方式稍有不同(top_level3 和 top_level4 都以 term1 开头),进一步的转换将导致 rici 给出的 LL(1) 语法(top_level3 变为optional_expression_or_term_1 和 top_level4 变为 expression_or_term_1)。
    猜你喜欢
    • 1970-01-01
    • 2014-06-01
    • 2011-01-26
    • 1970-01-01
    • 2015-06-01
    • 2018-03-25
    • 1970-01-01
    • 2011-03-18
    • 1970-01-01
    相关资源
    最近更新 更多