奇怪的转变/减少冲突的明确(我认为)语法

时间:2017-02-24 18:57:37

标签: bison

所以在我的语言中我想要点语法表达式:

myObject.myProperty
myObject.myProperty.subProperty

我想要声明

Object myObject = 1

此外,对象类型可以是命名空间:

Object.SubObject mySubObject = 1

简化语法如下:

program:
    declaration;
    | expression;

declaration: 
    TOKEN_IDENTIFIER TOKEN_IDENTIFIER '=' TOKEN_INTEGER;
    | TOKEN_IDENTIFIER '.' TOKEN_IDENTIFIER TOKEN_IDENTIFIER '=' TOKEN_INTEGER;

expression:
    TOKEN_IDENTIFIER;
    | expression '.' TOKEN_IDENTIFIER;

不幸的是,用Bison编译这个语法给出了一个降低移位的冲突。看看状态机输出,在我看来,Bison解释它的方式有误。以下是状态1,它是读取第一个标识符后的状态:

State 1

    3 declaration: "identifier" . "identifier" '=' "integer"
    4            | "identifier" . '.' "identifier" "identifier" '=' "integer"
    5 expression: "identifier" .

    "identifier"  shift, and go to state 5
    '.'           shift, and go to state 6

    "end of code"  reduce using rule 5 (expression)
    '.'            [reduce using rule 5 (expression)]

状态6(读取点时的默认移位状态)仅用于声明:

State 6

    4 declaration: "identifier" '.' . "identifier" "identifier" '=' "integer"

    "identifier"  shift, and go to state 10

在我看来,在状态1中,没有可能在读取点时减少。它应该向前看,如果它看到两个标识符紧接着(中间没有点),那么它应该转换到仅声明状态,但如果它看到第二个点或代码结束,则它会减少到表达式。事实上,声明的规则是唯一可以并排找到两个标识符的实例,而且两者之间没有点可以消除语法的歧义,因此没有shift-reduce错误。

我用ielr和canonical-lr尝试了相同的结果(不知道这是否重要)。

有什么想法吗?我对它应该如何工作的解释不正确吗?

2 个答案:

答案 0 :(得分:2)

  

它应该向前看,如果它看到两个标识符紧接着......

LALR(1)中的1表示"前瞻的一个标记"。因此,解析不会在前瞻中看到两个标识符,因为它只能看到一个标识符。

  

我认为ielr选项应该可以解决这个问题。

不。 ielr解析器是LR(1)(差不多)。 canonical-lr是LR(1)(正是)。在所有情况下,它都是相同的1

简单的解决方案是使用GLR解析器(%glr-parser),它不限于一个前瞻标记,因为它根本不使用前瞻。相反,它会并行维护所有可能的解析堆栈,直到它找到一种方法来确定哪一个是有效的,或者它知道无法解决歧义。显然,保持多个堆栈会有性能损失,但它可能不会成为编译器的瓶颈。 GLR解析器可以使用任何明确的语法而无需修改,并且在大多数情况下,没有必要修改解析器操作,因此它通常是一个不错的选择。

否则,您可以添加一个看似多余的"生产(这个短语来自流行的编译器文本):

expression: TOKEN_IDENTIFIER
          | compound_expression
compound_expression
          : TOKEN_IDENTIFIER '.' TOKEN_IDENTIFIER
          | compound_expression '.' TOKEN_IDENTIFIER

但是,该解决方案并不能很好地扩展到更复杂的语法。

答案 1 :(得分:1)

在LALR(1)解析器中处理此类事物的常规方法是分解导致冲突的表达式和声明之间的共同点。在您的情况下,这是一个可选的作用域名称,可以是声明中的类型名称,也可以是表达式中的字段引用。所以你重构为

name: TOKEN_IDENTIFIER
    | name '.' TOKEN_IDENTIFIER

declaration: name TOKEN_IDENTIFIER '=' expression

expression: name
          | ... other expression rules

这样做的原因是它现在可以在开始时识别name而不关心它是declaration还是expression的开头 - 该决定是推迟到完整name之后看到下一个标记。

请注意,如果您的规则允许两个连续的表达式之间没有令牌,那么这仍然会失败。

此外,这个语法与原始语法略有不同,因为它允许在声明中使用多个范围的作用域,例如

Object.SubObject.SubSubObject mySubSubObject = 1

虽然你的语法只允许0或一个范围级别。