Bison 移位减少简单(?)语法中的冲突

Bison 移位减少简单(?)语法中的冲突,bison,shift-reduce-conflict,Bison,Shift Reduce Conflict,我试图用野牛来描述一种语法,但我不确定它是否能做到。我想要的语法是: %token A B C D SEP %% items : /* empty */ | items_nonempty ; items_nonempty : item | items_nonempty SEP item ; item : B

我试图用野牛来描述一种语法,但我不确定它是否能做到。我想要的语法是:

%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
分隔。项目中的
A
C
D
标记是可选的

注意在每个项目内以及项目之间重复使用相同的分隔符标记SEP

我希望语法是清楚的。我认为它是明确的,但我很不确定它是否受到足够的限制,可以被bison解析——不幸的是,我的解析器知识已经相当生疏了

使用给定的语法,bison报告了4个移位/减少冲突。通过观察“输出”,我了解它们发生的位置和原因;但我不知道如何(如果)编写预期的语法来消除S/R冲突

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


任何关于如何清理该语法的提示都将不胜感激。

该语法确实是明确的,它是LL(7),并且(未经验证)我相信它是LR(2),甚至可能是LALR(2)。因此,如果你有一个发电机为这些,它会做的工作

lookahead-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 
               ;
在这里,lexer忽略了分隔符

上面两个都是LALR(1),前者是LL(1),后者是LL(4),但可以通过一些因子分解得到LL(1)


这将是一个独立于bison或lexer工具提供的特殊产品的解决方案。我期待了解他们可以做些什么。

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

%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之后分离a C或a D,而Yacc不能向前看。你需要一个语义检查来取缔'B SEP D SEP C';上述规则允许这样做

你能考虑修改你的记录器在读SEP C和D上读C吗?您甚至可以在

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
    ;

基本问题是,所编写的语法需要两个先行标记来决定何时找到
项的结尾,从而可以减少它,或者在
SEP
之后是否有当前项的另一部分,它将其视为先行的下一个字符

有很多方法可以尝试

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

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

  • 使用扫描仪进行更多的前瞻性操作——根据
    SEP
    后的下一个标记是什么,而不是返回简单的
    SEP\u-BEFORE\u或\u-B
    标记,而是返回
    SEP\u-BEFORE\u或
    SEP\u-BEFORE\u或\u-B

  • 在扫描器中组合标记--将
    SEP_C
    SEP_D
    作为单个标记返回(分隔符后跟
    C
    D


您可以将其他代币后面的
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 REDUCT冲突,因为现在的语法是LALR(1)。在每一步中,它都会确切地知道要应用什么样的缩减,即使该规则的要点是去掉一个
SEP
,这样我们就可以看得更远