【问题标题】:Shift/reduce conflict in Happy grammar快乐语法中的移位/减少冲突
【发布时间】:2017-02-01 23:52:54
【问题描述】:

我有以下(严重剥离)快乐语法

%token
   '{'   { Langle }
   '}'   { Rangle }
   '..'  { DotDot }
   '::'  { ColonColon }
   '@'   { At }
   mut   { Mut }
   ident { Ident }

 %%

 pattern
   : binding_mode ident at_pat  { error "identifier pattern" }
   | expr_path                  { error "constant expression" }
   | expr_path '{' '..' '}'     { error "struct pattern" }

 binding_mode
   : mut                        { }
   |                            { }

 at_pat
   : '@' pat                    { }
   |                            { }

 expr_path
   : expr_path '::' ident       { }
   | ident                      { }

在模式中的标识符周围存在移位/减少冲突。默认情况下,Happy 选择转移,但在这种情况下,这不是我想要的:它会尝试将所有内容都塞进constant expression,即使它可能是identifier pattern

我读过优先级/关联性是解决此类问题的方法,但我添加的任何内容都无法使语法朝着正确的方向发展(公平地说,我一直在拍摄在黑暗中)。

使用一些明显的标记化,我想要:

  • x 产生 identifier pattern
  • mut x 产生 identifier pattern
  • std::pi 产生 constant expression
  • point{..} 产生 struct pattern
  • std::point{..} 产生 struct pattern

基本上,除非有 {:: 令牌等待使用,否则标识符应该转到 identifier pattern 案例。


如果我的问题不清楚,我深表歉意 - 部分问题是我很难确定问题所在。 :(

【问题讨论】:

  • 我想你错过了一些作品。如图所示,没有冲突(也没有 constant_expression)
  • @rici 很可能就是这种情况。如果是这样,我将删除问题并在重新发布之前更仔细地查看它!不应该有constant_expression 生产——这只是我遇到patternexpr_path 变体后选择打印的消息。而且我认为应该至少存在一个移位/减少冲突 - 只给Ident,我可以从pattern 转换为expr_path 变体,或者完全减少binding_mode ident at_pat
  • 好吧,如果你将结果描述为生产而不是行动,对我来说会更清楚;我倾向于忽略行动。您是否打算将朴素的标识符作为一种模式?你为什么不直接写语法说呢?
  • 是的,我的意图是让朴素的标识符成为模式。我没有这样写,因为我不相信其他部分不是问题的一部分。请随时编辑问题以使其更清楚 - 我不假装在编写语法方面有任何实际经验,更不用说调试它们了。

标签: haskell grammar parser-generator happy ambiguous-grammar


【解决方案1】:

首先,了解什么是转变很重要。移位是接受下一个输入标记并将其放入解析器堆栈的结果(它最终将成为生产的一部分,但还没有必要知道是哪一个。)减少是采用零个或多个标记离开堆栈的顶部,与某些产品的右侧相匹配,并用左侧替换它们。

当解析器决定从binding_mode ident at_pat 中创建一个identifier patternat_pat 为空时,它不会移动;它正在减少。事实上,它减少了两次:首先将零堆叠符号减少为空的at_pat,然后将前三个堆叠符号减少为identifier pattern。如果没有binding_mode,它可以将ident 减少为expr_path,然后将expr_path 减少为constant_expression。所以这将是一个减少/减少冲突。

但是还有另一个问题,正是因为binding_mode 可以为空。当解析器看到ident时,它不知道binding_mode是否可能,所以它不知道是减少一个空的binding_mode还是移动ident。那是一个转变/减少冲突。由于它更喜欢 shift 而不是 reduce,因此它选择转移 ident,这意味着无法生成空的 binding_mode,这反过来又排除了 reduce/reduce 冲突(并完全阻止了 ident @ pat 被识别。)

因此,为了解决所有这些问题,我们首先需要避免减少空的binding_mode。我们通过通常的可空产生式消除算法来做到这一点,该算法涉及制作右侧的两个副本,一个具有可空非终结符,另一个没有;然后我们删除可以为空的产生式。一旦我们这样做了,reduce/reduce 冲突就会出现。

为避免 reduce/reduce 冲突,我们需要明确首选哪种生产方式。减少/减少冲突不能通过优先声明解决,因为优先算法总是涉及生产(可以减少)和终端(可以转移)之间的比较。所以解析必须是明确的,这意味着我们需要说一个裸露的ident 是一个模式,而一个不是identexpr_path 是一个常量表达式。这给我们留下了以下内容:

(请注意,我使用非终端来标记pattern 的三个不同产生式,而不是依赖于动作。对我来说,这更容易思考和阅读。)

pattern: identifier_pattern | constant_expression | struct_pattern

这里是空生产消除:

identifier_pattern:   ident at_pat 
                  |   binding_mode ident at_pat

这是对身份的明确禁止:

constant_expression:  complex_expr_path 

struct_pattern:       expr_path '{' '..' '}'

binding_mode 不再可以为空:

binding_mode: mut

at_pat
   : '@' pat
   | %empty

这里我们创建了两个不同的 expr_path:

complex_expr_path
   : complex_expr_path '::' ident
   | ident '::' ident

expr_path: ident | complex_expr_path

我希望该解决方案与您的原始语法有一些关系。

【讨论】:

  • 是的。这通常是我要尝试的想法(complex_expr_path)。我想我可以用我的语法来完成这项工作。谢谢你这么详细的回答!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多