【问题标题】:Grammar conflict with same prefix具有相同前缀的语法冲突
【发布时间】:2020-06-06 23:14:45
【问题描述】:

这是我对 for 语句的语法:

FOR x>0 {
    //somthing
}

// or

FOR x = 0; x > 0; x++ {
   //somthing
}

它有相同的前缀FOR,我想在InitExpression之后打印for_begin标签, 但是FOR之后的代码将因为冲突而变得无用。

    ForStmt
            : FOR   {
                                printf("for_begin_%d:\n", n);
                       } Expression {
                                                           printf("ifeq for_exit_%d\n", n);
                                                 } ForBlock
           | FOR ForClause ForBlock
    ;

ForClause
            : InitExpression ';'   {
                                                            printf("for_begin_%d:\n", n);
                                                    }  Expression  ';'  Expression  { printf("ifeq for_exit_%d\n", n); }
    ;

我曾尝试将其更改为:

ForStart
      : FOR
      | FOR InitExpression
;

或使用标志来提及打印for_begin 标签的位置, 但也未能解决冲突。

如何让它不冲突?

【问题讨论】:

    标签: parsing grammar yacc


    【解决方案1】:

    解析器如何知道它看到的是FOR 语句的哪个替代项?

    虽然InitExpression 可能具有可识别的形式,例如赋值语句,但不能在条件表达式中使用。这让我觉得这对于实际目的来说太严格了——除了直接赋值之外,你可能会做很多事情来初始化循环——但撇开它不谈,这意味着最早可以明确识别InitExpression 是赋值运算符可见。如果您的语言中的左值只能是简单的标识符,那将使其成为继FOR 之后的第二个前瞻标记,但在大多数有用的语言中,左值可能比简单的标识符复杂得多,因此InitExpression 很可能无法通过有限前瞻来明确识别。

    但更有可能的是,这两种形式的唯一显着区别是第一种形式的表达式后面跟着一个块(我想它不能以分号开头),而第二种形式的第一个表达式后面跟着一个分号。所以解析器知道它在第一个表达式的末尾解析什么,而不是更早。

    通常,这不会造成问题。如果不是插入标签的 MidRule Action,解析器在到达第一个表达式的末尾之前不必做出归约决定,此时它需要决定是否将第一个表达式归约为InitExpressionExpression。但此时,前瞻记号既可以是分号,也可以是块的第一个记号,所以前瞻记号可以指导决策。

    但中期规则行动使这成为不可能。在移动紧跟在FOR 令牌之后的令牌之前,必须减少或不减少中间规则操作,并且 - 正如您的示例所示 - 在这两种情况下,前瞻令牌可能是相同的 (i)。

    从根本上说,问题在于您想要构建一个一次性编译器,而不是仅仅将输入解析为 AST,然后遍历 AST 以生成汇编代码(可能在对 AST 进行一些其他遍历以执行其他分析并允许代码优化)。一次性代码生成器依赖于 Mid-Rule Actions,而 Mid-Rule Actions 反过来很容易产生无法解决的解析冲突。这个问题太臭了,有a chapter in the bison manual dedicated to it,很值得一读。

    所以没有好的解决方案。但是在这种情况下,有一个简单的解决方案,因为您要执行的操作只是插入一个标签,而插入一个恰好从未使用过的标签不会以任何方式影响最终将执行的代码.所以你不妨在FOR语句之后立即插入一个标签,不管你是否需要它,然后在InitExpression之后插入另一个标签,如果事实证明有这样的事情。在到达条件表达式的末尾之前,您实际上不需要知道要使用哪个标签,这要晚得多。

    正如我已经链接到的 Bison 手册章节中所解释的,这不能使用中间规则操作来完成,因为 Bison 不会尝试将中间规则操作相互比较。即使两个动作恰好相同,Bison 仍然需要决定执行哪一个,从而产生冲突。因此,您不需要使用 MRA,而是需要将动作存放在标记非终端中 - 一个右侧为空的非终端,仅用于触发动作。

    这会使语法看起来像这样:

    ForLabel
          : %empty             { $$ = n; printf("for_begin_%d:\n", n++); }             
    ForStmt
          : FOR
              ForLabel[label]
              Expression       { printf("ifeq for_exit_%d\n", label); }
              ForBlock         { printf("jmp for_begin_%d\n", label);
                                 printf("for_exit_%d:\n", label); }
          | FOR
              ForLabel
              InitExpress ';'
              ForLabel[label]
              Expression ';'
              Expression       { printf("ifeq for_exit_%d\n", label); }
              ForBlock         { printf("jmp for_begin_%d\n", label);
                                 printf("for_exit_%d:\n", label); }
    ;
    

    [label] 为语义值命名,避免使用相当神秘且可能不正确的$2$6。参见方便的 Bison 手册中的Named References。)

    【讨论】:

    • 嗯,虽然没有好的解决方案,但确实解决了我的问题。我将尝试将其更改为解析为 AST 的形式。感谢您的详尽回答!
    猜你喜欢
    • 1970-01-01
    • 2019-11-12
    • 2020-08-09
    • 2020-06-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-15
    • 2018-09-15
    相关资源
    最近更新 更多