Bison:在GLR解析器

时间:2018-06-13 09:47:03

标签: parsing bison glr

我正在使用GNU bison处理解析器,而我正面临一个有趣的问题。我的实际问题总体上有点不同而且不那么有趣,所以我会稍微说一点,所以答案一般会更有用。

我需要根据类型来区分表达式,比如字符串表达式中的算术表达式。他们的顶级非终结者有一些共同的祖先,比如

statement
    : TOK_PRINT expression TOK_SEMICOLON {...}
;
expression
    : arithmeticexpression {...}
    | stringexpression {...}
;

现在我需要能够在两种表达式中都有变量

arithmeticexpression
    : TOK_IDENTIFIER {...}
;
stringexpression
    : TOK_IDENTIFIER {...}
;

(在stringexpression情况下只允许使用string-type的变量,并且在arithmeticexpression情况下只允许使用int-或float-type的变量) 但这显然导致R / R冲突,这是语言中固有的 - 它是不可能解决的,因为语言含糊不清。

当然,我可以淡化语言,所以只有一般"表达"对象在parse-stack上传递,但是我必须在动作中做很多手动类型检查,我想避免。 而且,在我的实际用例中,通过语法到变量规则的路径是如此不同,以至于我不得不将语言降低到太多以至于我会失去很多语法规则(即失去很多结构信息)并且需要手动将解析引擎写入某些动作。

我读过有关GLR解析的内容,听起来它可以解决我的问题。我考虑使用此功能:在相应变量类型错误的路径中使用上述语法和YYERROR

arithmeticexpression
    : TOK_IDENTIFIER {
          if(!dynamic_cast<IntVariable*>(
              symbol_table[*$<stringvalue>1]))
              YYERROR;
      }
;
stringexpression
    : TOK_IDENTIFIER {
          if(!dynamic_cast<StringVariable*>(
              symbol_table[*$<stringvalue>1]))
              YYERROR;
      }
;

但是Bison Manual

  
      
  1. 在确定性GLR操作期间,YYERROR的效果是相同的   因为它在确定性解析器中的作用。
  2.   
  3. 延迟动作中的效果类似,但精确点   错误未定义;相反,解析器恢复到确定性   操作,选择一个未指定的堆栈,继续使用a   语法错误。
  4.   
  5. 在语义谓词中(参见Semantic Predicates)   非确定性解析,YYERROR默默地修剪调用的解析   测试。
  6.   

我不确定我是否理解这一点 - 我这样理解:

  1. 不适用于此,因为GLR解析不是确定性的
  2. 就是我在上面的代码中所拥有的,但是不应该这样做,因为YYERROR杀死了哪条路径是完全随机的(如果我错了,请纠正我)
  3. 不会解决我的问题,因为semantic predicates(不是语义行动!)必须在规则的开头(如果我错了,请纠正我) ,此时来自TOK_IDENTIFIER令牌的yylval是不可访问的(如果我错了,请纠正我),所以我无法查看符号表以找到变量的类型。
  4. 有没有人有这种情况的经验?我对手册的理解是错误的吗?你会如何解决这个问题?对我来说,这个问题似乎是如此自然,以至于我认为人们会经常遇到这样的问题,因为野牛会内置一个解决方案......

2 个答案:

答案 0 :(得分:1)

注意:至少从bison 3.0.1和bison 3.0.5开始,处理语义谓词操作时会出现一个错误,导致bison输出#line指令在一行中间,导致编译失败。 this answer中描述了一个简单的修复程序,并已提交到bison存储库的maint branch。 (从存储库中构建bison不适合胆小的人。但如果你不想自己编辑文件就可以从该存储库下载data/c.m4。)

让我们从GLR解析器中关于YYERROR的具体问题开始。

实际上,您可以在语义谓词中使用YYERROR,以便通过拒绝它所属的生产来消除解析的歧义。您不能在语义操作中执行此操作,因为在解析器决定使用唯一解析之前不会执行语义操作,此时不再存在歧义。

语义操作可以在规则中的任何位置发生,并且在缩减时立即执行,即使缩减不明确。因为它们不按顺序执行,所以它们不能引用任何前面的语义动作产生的值(并且语义谓词本身没有语义值,即使它们占用了堆栈槽)。此外,因为它们作为生产的一部分被推测性地有效地执行,这可能不是最终解析的一部分,所以它们不应该改变解析器状态。

语义谓词通过变量yychar可以访问前瞻标记(如果有)。如果yychar既不是YYEMPTY也不是YYEOF,则相关的语义值和位置信息分别位于yylvalyylloc中。 (见Lookahead Tokens)。但是,在使用此功能之前必须小心;如果语法中的那一点没有歧义,那么野牛解析器很可能已经执行了语义谓词而没有读取先行标记。

所以你的理解很接近,但不太正确:

  1. 为了提高效率,GLR解析器会识别解析中何时没有歧义,并在这些点使用正常的直接移位减少算法。在大多数语法中,模糊性很少并且可以快速解决,因此这种优化意味着对于大多数解析而言,可以避免与维护多个备选方案相关的开销。在这种模式下(当然,如果可能存在歧义,它不会适用),YYERROR会导致标准错误恢复,就像在减少操作时一样,在shift-reduce解析器中。

  2. 由于在解决歧义之前不会运行延迟操作,因此延迟操作中的YYERROR将有效地执行,就像它处于确定状态一样,如上一段所述。即使存在自定义合并过程也是如此;如果正在进行自定义合并, all 将丢弃参与自定义合并的备选方案。之后,正常的错误恢复算法将继续。我不认为错误恢复点是&#34;随机&#34;,但预测它将发生在哪里并不容易。

  3. 如上所述,语义谓词可以出现在规则中的任何位置,并且如果有,则可以访问先行令牌。 (Bison手册在这里有点误导:语义谓词不能包含YYERROR的调用,因为YYERROR是一个语句,而语义谓词块的内容是一个表达式。如果是语义谓词是假的,然后解析器执行YYERROR动作。)

  4. 实际上,这意味着你可以使用语义谓词而不是(或同样)动作,但基本上就像你提出的那样:

    arithmeticexpression
        : TOK_IDENTIFIER %?{
              dynamic_cast<IntVariable*>(symbol_table[*$1])
          }
    stringexpression
        : TOK_IDENTIFIER %?{
              dynamic_cast<StringVariable*>(symbol_table[*$1])
          }
    

    注意:我从$<stringvalue>1删除了类型声明,因为:

    1. 基本上不可读,至少对我而言,

    2. 更重要的是,就像显式演员一样,它不是类型安全的。

    3. 您应该声明令牌的语义类型:

      %type <stringvalue> ID
      

      这将允许野牛进行打字检查。

      是否为此目的使用语义谓词是一个好主意是另一个问题。

答案 1 :(得分:0)

这里的示例有效,但是当我将其与Bison: GLR-parsing of valid expression fails without error message的解决方案放在一起时,遇到了以下问题:

该变量可以由多个标识符确定。您可以有一个数组索引,也可以是另一个对象的成员。我通过使用另一个非终结符来对这种情况进行建模

lvalue
    : TOK_IDENTIFIER
    | lvalue '[' arithmeticexpression ']'
    | lvalue '.' TOK_IDENTIFIER

但是当我现在拥有

arithmeticexpression : lvalue
stringexpression : lvalue

并(尝试)从非终端“ lvalue”访问对象,如上所述,我遇到了分段错误。因此,看来这里的方法不适用于更复杂的情况。


我现在要做的是,如果变量类型错误,我将以语义ACTION访问对象并将$$设置为nullptr

arithmeticexpression
    : lvalue
    {
        $$ = nullptr;
        if(dynamic_cast<IntVariable*>($lvalue->getVariable()))
            $$ = new ArithmeticExpressionIntVariable($lvalue);
    }

然后我必须像这样传播nullptr(这是很多附加代码)

arithmeticexpression
    ...
    | arithmeticexpression[left] '+' arithmeticexpressionM[right]
    {
        $$ = nullptr;
        if($left && $right)
            $$ = new ArithmeticExpressionPlus($left, $right);
    }

现在,在

expression
    : arithmeticexpression
    | stringexpression
(note: I have "Expression* expr;" in the "%union{" declaration
and "%type <expression> expr" in the prologue)

我有一个歧义:可以用两种不同的方式来解析相同的输入文本,但是只有其中一种将具有值!= nullptr。此时,我需要一个自定义合并过程,该过程基本上只选择非null值。

为此,我在这样的野牛文件的序言中向前声明了它

static Expression* exprMerge (yy::semantic_type x0, yy::semantic_type x1);

并像这样在结尾中定义它

static Expression* exprMerge (YYSTYPE x0, YYSTYPE x1) \
{   
    /* otherwise we'd have a legitimate ambiguity */
    assert(!x0.expr || !x1.expr);
    return x0.expr ? x0.expr : x1.expr;
}

最后,我不得不告诉野牛使用此程序来解决歧义,

expression
    : arithmeticexpression  %merge <exprMerge>
    | stringexpression %merge <exprMerge>

老实说,我认为这是相当大的努力,如果bison在尝试合并路径而不是排除路径时测试了语义谓词(至少是规则背后的谓词),则这是不必要的。在它们合并之前。

但是至少这是有效的,并且比收集令牌并手动对其进行排序要省得多。