我试图在野牛中描述一种语法,但我不确定它是否可以完成。我的语法是这样的:
%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
代币是可选的。
请注意在每个项目中以及项目本身之间重复使用相同的分隔符令牌。
我希望目标语法清晰。我认为它是明确的,但我不确定它是否足够受限于野牛可以解析 - 不幸的是,我的解析器知识非常生疏。
使用给定的语法,bison报告4个移位/减少冲突。看看'输出',我了解它们出现的位置和原因;但我不知道如何(以及如果)编写预期的语法来摆脱S / R冲突。
我不愿意使用%expect
声明。同样,我不愿意让我的扫描仪使用分隔符标记而不是将它们传递给解析器。
非常感谢任何关于如何消毒这种语法的提示。
答案 0 :(得分:1)
基本问题是所写的语法需要两个前瞻标记来决定它何时找到item
的结尾并因此可以减少它,或者如果SEP
之后还有另一部分当前项目1}}它被视为前瞻的下一个角色。
您可以尝试多种方法
使用btyacc或bison的GLR支持有效地获得更多前瞻。
编写语法以接受任意单个项目列表,然后使用post-pass将它们重新组合成1-4个项目,至少有1个B
并拒绝格式错误的集合(这是冈瑟的建议)
使用扫描程序做更多的预测 - 而不是返回简单的SEP
令牌,返回SEP_BEFORE_A_OR_B
或SEP_NOT_BEFORE_A_OR_B
,具体取决于{{1}之后的下一个令牌是的。
合并扫描仪中的标记 - 将SEP
和SEP_C
作为单个标记返回(分隔符后跟SEP_D
或C
)
答案 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
,所以我们可以进一步查看一个令牌。