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
,这样我们就可以看得更远