Shift - 减少简单(?)语法中的冲突

时间:2012-04-05 15:17:07

标签: bison shift-reduce-conflict

我试图在野牛中描述一种语法,但我不确定它是否可以完成。我的语法是这样的:

%token A B C D SEP

%%

items          : /* empty */
               | items_nonempty
               ;

items_nonempty : item
               | items_nonempty SEP item
               ;

item           :       B
               |       B       SEP D
               |       B SEP C
               |       B SEP C SEP D
               | A SEP B
               | A SEP B       SEP D
               | A SEP B SEP C
               | A SEP B SEP C SEP D
               ;

items”是item元素的(可能为空)序列,由SEP标记分隔。

每个项目最多包含4个令牌(A B C D),按此顺序,由SEP分隔。商品中的ACD代币是可选的。

请注意在每个项目中以及项目本身之间重复使用相同的分隔符令牌。

我希望目标语法清晰。我认为它是明确的,但我不确定它是否足够受限于野牛可以解析 - 不幸的是,我的解析器知识非常生疏。

使用给定的语法,bison报告4个移位/减少冲突。看看'输出',我了解它们出现的位置和原因;但我不知道如何(以及如果)编写预期的语法来摆脱S / R冲突。

我不愿意使用%expect声明。同样,我不愿意让我的扫描仪使用分隔符标记而不是将它们传递给解析器。

非常感谢任何关于如何消毒这种语法的提示。

4 个答案:

答案 0 :(得分:1)

基本问题是所写的语法需要两个前瞻标记来决定它何时找到item的结尾并因此可以减少它,或者如果SEP之后还有另一部分当前项目1}}它被视为前瞻的下一个角色。

您可以尝试多种方法

  • 使用btyacc或bison的GLR支持有效地获得更多前瞻。

  • 编写语法以接受任意单个项目列表,然后使用post-pass将它们重新组合成1-4个项目,至少有1个B并拒绝格式错误的集合(这是冈瑟的建议)

  • 使用扫描程序做更多的预测 - 而不是返回简单的SEP令牌,返回SEP_BEFORE_A_OR_BSEP_NOT_BEFORE_A_OR_B,具体取决于{{1}之后的下一个令牌是的。

  • 合并扫描仪中的标记 - 将SEPSEP_C作为单个标记返回(分隔符后跟SEP_DC

答案 1 :(得分:0)

语法确实是明确的,它是LL(7)和(没有经过验证)我相信它是LR(2),大概甚至是LALR(2)。因此,如果您有其中任何一个的发电机,它就可以完成这项工作。

前瞻-1冲突产生于在项目之间和项目内使用相同的分隔符,如果您放弃分隔符或项目结构,它们将消失。

所以你能做的就是解析两次,使用不同的语法。在第一遍中,您可以验证分隔符是否正确定位,语法可能类似于

items          :
               | items_nonempty
               ;
items_nonempty : item
               | items_nonempty SEP item
               ;
item           : A
               | B
               | C
               | D
               ;

在第二个(也是更重要的)传递中,您将验证项目结构。这可能是

items          :
               | items_nonempty
               ;
items_nonempty : item
               | items_nonempty item
               ;
item           : B
               | B D
               | B C
               | B C D
               | A B
               | A B D
               | A B C
               | A B C D 
               ;

你可以在词法分析器中忽略分隔符。

上述两种都是LALR(1),前者是LL(1),后者是LL(4),但可以通过某种因子将其设为LL(1)。

这将是一个独立于野牛或词法分析工具可用的特殊产品的解决方案。我期待着了解他们可以做些什么。

答案 2 :(得分:0)

这有一个转变/减少冲突:

%token A B C D SEP

%%

items
    : /* empty */
    | items_nonempty
    ;

items_nonempty
    : item
    | items_nonempty SEP item
    ;

item
    : opt_a B opt_c_d_list
    ;

opt_a
    :   /* Nothing */
    |   A SEP
    ;

opt_c_d_list
    :   /* Nothing */
    |   opt_c_d_list c_or_d
    ;

c_or_d
    :   SEP C
    |   SEP D
    ;

opt_a规则将S / R计数从4更改为2.剩下的问题是相同的SEP在B之后分离C或D,并且Yacc无法向前看。您需要进行语义检查才能取缔“B SEP D SEP C”;上面的规则允许这样做。

你能考虑修改你的标记器,在读取SEP C?时返回C,在读取SEP D时返回D?您甚至可以在flex中使用词法反馈和开始条件,这样在读取B时,您可以翻转一个开关,使SEP C返回为C,而SEP D仅返回D.如果是可能,以下明确的语法可以解决没有S / R冲突的问题:

%token A B C D SEP

%%

items
    : /* empty */
    | items_nonempty
    ;

items_nonempty
    : item
    | items_nonempty SEP item
    ;

item
    : opt_a B opt_c opt_d
    ;

opt_a
    :   /* Nothing */
    |   A SEP
    ;

opt_c
    :   /* Nothing */
    |   C
    ;

opt_d
    :   /* Nothing */
    |   D
    ;

答案 3 :(得分:0)

您可以将跟随其他令牌的SEP添加到单个规则中。写得非常简洁,你的语法可以这样表达:

%token A B C D SEP
%%
items : /* empty */ | item | itemsSEP item ;
item : a B | a b C | a b c D ;
itemsSEP : itemSEP | itemsSEP itemSEP ;
itemSEP : a b c d ;
a : /* empty */ | A SEP ;
b : B SEP ;
c : /* empty */ | C SEP ;
d : /* empty */ | D SEP ;

所以现在我有一个项目itemSEP后跟一个分隔符,但item代表最后一个没有分隔符的项目。它们由小写的单字母非终端组成,每个终端也包括以下分隔符,并且处理一些可选的参数。只有item的最后一个参数始终是原始终端,因为在该终端之后不会有分隔符。

用这种方式表达的语法,你不会遇到任何shift-reduce冲突,因为语法现在是LALR(1)。在每一步中,它都会确切知道要应用的减少量,即使该规则的要点是摆脱一个SEP,所以我们可以进一步查看一个令牌。