在 Happy/Bison 中解决此 Shift/Reduce 冲突
Solving this Shift/Reduce Conflict in Happy/Bison
我正在 Happy 中制作一个简单的解析器(Bison 相当于 Haskell),我偶然发现这些规则中存在 shift/reduce 冲突:
ClassBlock :
"{" ClassAttributes ClassConstructor ClassFunctions "}" {ClassBlock }
ClassAttributes :
{- empty -} { ClassAttributesEmpty }
| ClassAttributes ClassAttribute {ClassAttributes }
ClassAttribute :
"[+]" Variable {ClassAttributePublic }
| "[-]" Variable {ClassAttributePrivate }
ClassFunctions :
{- empty -} { ClassFunctionsEmpty }
| ClassFunctions ClassFunction {ClassFunctions }
ClassFunction :
"[+]" Function {ClassFunctionPublic }
| "[-]" Function {ClassFunctionPrivate }
ClassConstructor :
{- empty -} { ClassConstructorEmpty }
| TypeFuncParams var_identifier Params Block {ClassConstructor }
TypeFuncParams :
Primitive ClosingBracketsNoIdentifier { TypeFuncParamsPrimitive }
| class_identifier ClosingBracketsNoIdentifier { TypeFuncParamsClassId }
| ListType {TypeFuncParamsList }
信息文件指出 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 }
我不太确定如何解决这个问题。我尝试使用优先规则来消除警告,但没有达到我的预期。
下面是一个简化的语法,它表现出同样的问题。
为了构建它,我删除了
- 所有操作
- 来自所有非终端名称的前缀 "Class"
我还简化了大部分规则。我这样做是为了说明如何按照 Whosebug 指南的建议构建 minimal, complete, verifiable example,这样可以更轻松地关注问题,同时仍允许进行实际试验。 (我用的是bison,不开心,但是语法很相似。)
Block : "{" Attributes Constructor Functions "}"
Attributes : {- empty -} | Attributes Attribute
Constructor: {- empty -} | "constructor"
Functions : {- empty -} | Functions Function
Attribute : "[+]" "attribute"
Function : "[+]" "function"
现在,让我们来玩解析器,假设我们已经(以某种方式)识别出可以匹配 Attributes
的前缀。 (Attributes
可以匹配空字符串,所以我们可以在输入的开头。)假设下一个标记是 [+]
.
在这一点上,我们无法判断 [+]
稍后是否会成为 Attribute
的开头,或者它是否是 Function
的开头空 Constructor
。但是,为了继续解析,我们需要知道这一点。
如果我们已经完成了属性并即将开始函数,那么此时我们必须减少空的非终结符 Constructor
。除非我们现在这样做,否则我们无法继续识别 Function
。另一方面,如果我们没有看到最后一个 Attribute
但我们确实减少了一个 Constructor
,那么解析最终会失败,因为下一个 Attribute
不能跟在 [=21= 之后] 我们只是减少了。
在这种情况下,通过将选项分解到使用非终端的地方来删除空产生式通常很有用:
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
还是 Function
,此时不再需要咨询 oracle。
但是,由于多种原因,该解决方案不是很漂亮,其中最重要的是它仅适用于连接两个可选列表。如果我们想添加另一个也可以以 [+]
标记开头的不同类型项目的列表,则需要不同的解决方案..
许多语言都使用的最简单的方法是允许程序员混合各种列表元素。您可能会认为这种风格很糟糕,但并不总是需要通过将其作为语法错误来谴责这种糟糕的风格。
一个简单的解决方案是:
Block : "{" Things "}"
Things : {- empty -} | Things Attribute | Things Function | Things Constructor
Attribute : "[+]" "attribute"
Constructor: "constructor"
Function : "[+]" "function"
但这并没有限制一个块最多只能有一个构造函数,这似乎是一个语法要求。但是,只要 Constructor
不能以 [+]
开头,您就可以通过以下方式实施 "at most one Constructor" 限制:
Block : "{" Things Constructor Things "}"
| "{" Things "}"
Things : {- empty -} | Things Attribute | Things Function
Attribute : "[+]" "attribute"
Constructor: "constructor"
Function : "[+]" "function"
我正在 Happy 中制作一个简单的解析器(Bison 相当于 Haskell),我偶然发现这些规则中存在 shift/reduce 冲突:
ClassBlock :
"{" ClassAttributes ClassConstructor ClassFunctions "}" {ClassBlock }
ClassAttributes :
{- empty -} { ClassAttributesEmpty }
| ClassAttributes ClassAttribute {ClassAttributes }
ClassAttribute :
"[+]" Variable {ClassAttributePublic }
| "[-]" Variable {ClassAttributePrivate }
ClassFunctions :
{- empty -} { ClassFunctionsEmpty }
| ClassFunctions ClassFunction {ClassFunctions }
ClassFunction :
"[+]" Function {ClassFunctionPublic }
| "[-]" Function {ClassFunctionPrivate }
ClassConstructor :
{- empty -} { ClassConstructorEmpty }
| TypeFuncParams var_identifier Params Block {ClassConstructor }
TypeFuncParams :
Primitive ClosingBracketsNoIdentifier { TypeFuncParamsPrimitive }
| class_identifier ClosingBracketsNoIdentifier { TypeFuncParamsClassId }
| ListType {TypeFuncParamsList }
信息文件指出 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 }
我不太确定如何解决这个问题。我尝试使用优先规则来消除警告,但没有达到我的预期。
下面是一个简化的语法,它表现出同样的问题。
为了构建它,我删除了
- 所有操作
- 来自所有非终端名称的前缀 "Class"
我还简化了大部分规则。我这样做是为了说明如何按照 Whosebug 指南的建议构建 minimal, complete, verifiable example,这样可以更轻松地关注问题,同时仍允许进行实际试验。 (我用的是bison,不开心,但是语法很相似。)
Block : "{" Attributes Constructor Functions "}"
Attributes : {- empty -} | Attributes Attribute
Constructor: {- empty -} | "constructor"
Functions : {- empty -} | Functions Function
Attribute : "[+]" "attribute"
Function : "[+]" "function"
现在,让我们来玩解析器,假设我们已经(以某种方式)识别出可以匹配 Attributes
的前缀。 (Attributes
可以匹配空字符串,所以我们可以在输入的开头。)假设下一个标记是 [+]
.
在这一点上,我们无法判断 [+]
稍后是否会成为 Attribute
的开头,或者它是否是 Function
的开头空 Constructor
。但是,为了继续解析,我们需要知道这一点。
如果我们已经完成了属性并即将开始函数,那么此时我们必须减少空的非终结符 Constructor
。除非我们现在这样做,否则我们无法继续识别 Function
。另一方面,如果我们没有看到最后一个 Attribute
但我们确实减少了一个 Constructor
,那么解析最终会失败,因为下一个 Attribute
不能跟在 [=21= 之后] 我们只是减少了。
在这种情况下,通过将选项分解到使用非终端的地方来删除空产生式通常很有用:
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
还是 Function
,此时不再需要咨询 oracle。
但是,由于多种原因,该解决方案不是很漂亮,其中最重要的是它仅适用于连接两个可选列表。如果我们想添加另一个也可以以 [+]
标记开头的不同类型项目的列表,则需要不同的解决方案..
许多语言都使用的最简单的方法是允许程序员混合各种列表元素。您可能会认为这种风格很糟糕,但并不总是需要通过将其作为语法错误来谴责这种糟糕的风格。
一个简单的解决方案是:
Block : "{" Things "}"
Things : {- empty -} | Things Attribute | Things Function | Things Constructor
Attribute : "[+]" "attribute"
Constructor: "constructor"
Function : "[+]" "function"
但这并没有限制一个块最多只能有一个构造函数,这似乎是一个语法要求。但是,只要 Constructor
不能以 [+]
开头,您就可以通过以下方式实施 "at most one Constructor" 限制:
Block : "{" Things Constructor Things "}"
| "{" Things "}"
Things : {- empty -} | Things Attribute | Things Function
Attribute : "[+]" "attribute"
Constructor: "constructor"
Function : "[+]" "function"