我在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 }
我不确定如何解决这个问题。我尝试使用优先规则来使警告静音,但它并没有像我预期的那样有效。
答案 0 :(得分:4)
下面是一个简化的语法,它表现出同样的问题。
要构建它,我删除了
我还简化了大部分规则。我这样做是为了说明如何构建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
开头,您就可以实现"最多一个构造函数"限制:
[+]