解决这个转变/减少快乐/野牛的冲突

时间:2018-02-14 19:50:42

标签: haskell grammar bison happy

我在Happy中创建了一个简单的解析器(Bison相当于Haskell),我偶然发现了这些规则中的转换/减少冲突:

ClassBlock :
        "{" ClassAttributes  ClassConstructor ClassFunctions "}" {ClassBlock $2 $3 $4}

ClassAttributes :
        {- empty -} { ClassAttributesEmpty }
      | ClassAttributes ClassAttribute {ClassAttributes $1 $2}

ClassAttribute :
        "[+]" Variable {ClassAttributePublic $2 }
      | "[-]" Variable {ClassAttributePrivate $2 }

ClassFunctions :
        {- empty -} { ClassFunctionsEmpty }
      | ClassFunctions ClassFunction {ClassFunctions $1 $2}

ClassFunction :
        "[+]" Function {ClassFunctionPublic $2}
      | "[-]" Function {ClassFunctionPrivate $2}

ClassConstructor :
       {- empty -} { ClassConstructorEmpty }
      | TypeFuncParams var_identifier Params Block {ClassConstructor $1 $2 $3 $4}

TypeFuncParams :
      Primitive ClosingBracketsNoIdentifier { TypeFuncParamsPrimitive $1 $2}
    | class_identifier ClosingBracketsNoIdentifier { TypeFuncParamsClassId $1 $2}
    | ListType {TypeFuncParamsList $1}

info文件说明了shift / reduce冲突:

ClassBlock -> "{" ClassAttributes . ClassConstructor ClassFunctions "}"    (rule 52)
    ClassAttributes -> ClassAttributes . ClassAttribute    (rule 54)

    "[+]"          shift, and enter state 85
            (reduce using rule 61)

    "[-]"          shift, and enter state 86
            (reduce using rule 61)

规则61就是这个:

ClassConstructor :
   {- empty -} { ClassConstructorEmpty }

我不确定如何解决这个问题。我尝试使用优先规则来使警告静音,但它并没有像我预期的那样有效。

1 个答案:

答案 0 :(得分:4)

下面是一个简化的语法,它表现出同样的问题。

要构建它,我删除了

  • 所有行动
  • 前缀" Class"来自所有非终结名字

我还简化了大部分规则。我这样做是为了说明如何构建minimal, complete, verifiable example,如StackOverflow指南所示,这样可以更容易地关注问题,同时仍然允许进行实际的试验。 (我用过野牛,不开心,但语法非常相似。)

Block      : "{" Attributes Constructor Functions "}"
Attributes : {- empty -} | Attributes Attribute
Constructor: {- empty -} | "constructor"
Functions  : {- empty -} | Functions Function
Attribute  : "[+]" "attribute"
Function   : "[+]" "function"

现在,让我们玩解析器,并假设我们(以某种方式)确定了一个可以匹配Attributes的前缀。 (Attributes可以匹配空字符串,因此我们可以在输入的开头。)并假设下一个标记是[+]

此时,我们无法判断[+]后来是Attribute的开头,还是Function的开头是否为空{ {1}}。但是,我们需要知道为了继续解析。

如果我们已完成属性并即将开始使用函数,那么我们必须减少空的非终结Constructor。除非我们现在这样做,否则我们不能继续承认Constructor。另一方面,如果我们还没有看到最后一个Function,但我们确实减少了Attribute,那么解析最终会失败,因为下一个Constructor无法跟随Attribute我们刚刚减少了。

在这种情况下,通过将选项分解到使用非终端的位置来删除空产品通常很有用:

Constructor

但仅仅删除Block : "{" Attributes "constructor" Functions "}" | "{" Attributes Functions "}" Attributes : {- empty -} | Attributes Attribute Functions : {- empty -} | Functions Function Attribute : "[+]" "attribute" Function : "[+]" "function" 就不够了。为了开始解析函数列表,我们需要首先减少空Constructor以提供Functions递归的基本情况,因此我们仍然需要猜测Functions开始的位置为了找到正确的解析。如果我们将两个列表写为右递归而不是左递归,我们需要一个空的Functions来终止Attributes递归的递归。

在这种特殊情况下,我们可以做的是使用左右递归的巧妙组合:

Attributes

通过使第一个列表为左递归而第二个列表为右递归,我们避免了减少两个列表之间的空非终端的需要。反过来,这允许解析器在看到短语之后决定短语是Block : "{" Attributes "constructor" Functions "}" | "{" Attributes Functions "}" Attributes : {- empty -} | Attributes Attribute Functions : {- empty -} | Function Functions Attribute : "[+]" "attribute" Function : "[+]" "function" 还是Attribute,此时不再需要咨询oracle。

但是,由于多种原因,该解决方案并不十分漂亮,其中最重要的原因是它只适用于两个可选列表的串联。如果我们想要添加另一个也可以从Function令牌开始的不同类型的项目列表,则需要一个不同的解决方案。

许多语言使用的最简单的方法是允许程序员混合各种列表元素。您可能会考虑这种糟糕的风格,但并不总是需要通过使其成为语法错误来惩罚不良风格。

一个简单的解决方案是:

[+]

但是这并不会将Block限制为最多一个Constructor,这似乎是一个语法要求。但是,只要Block : "{" Things "}" Things : {- empty -} | Things Attribute | Things Function | Things Constructor Attribute : "[+]" "attribute" Constructor: "constructor" Function : "[+]" "function" 无法以Constructor开头,您就可以实现"最多一个构造函数"限制:

[+]