Berkeley Yacc与GNU Bison:不同的容忍度w.r.t尾随令牌

时间:2015-08-15 06:01:55

标签: bison yacc

我遇到了解析器可移植性问题:使用byaccbison --yacc进行翻译时的行为不同。

Bison生成的解析器允许我调用yyparse来提取输入标记序列的一些前缀,它可以通过语法规则从起始符号派生。这是因为Bison生成的解析器具有$default reduce操作。我认为,这意味着“对于任何未被其他操作提及的前瞻令牌(因为它们与语法不匹配),执行此缩减”。

相比之下,对于相同的状态和规则集,Berkeley Yacc没有这样的默认缩减规则。同样的减少是特定匹配$end符号的关键。 换句话说,在Berkeley-Yacc生成的解析器中,规则有效地“锚定”到最后,就像带有$的正则表达式一样。

(注意:Bison生成的解析器仍然将语法作为一个整体锚定到最后,但它只通过匹配起始符号的顶级规则$end来实现这一点;它不会将$end匹配扩展到从属规则!)

差异很重要,因为我不止一次致电yyparse来提取连续的短语单位。这适用于Bison,因为我YYACCEPT之前有机会减少到需要隐式$end标记的起始符号。 (嗯,它并不完全适用于“开箱即用”:但是有一定的技巧可以使它工作)。使用Berkeley Yacc时,由于未在导致起始符号的从属规则中看到$end而导致的语法错误意味着整个方案在水中已经死亡。

是否有某种方法可以让Berkeley Yacc对前瞻标记值进行默认缩减,这些值与语法定义的延续或语法终止不匹配?换句话说,要填充LALR(1)表中该状态的所有未使用的条目,默认值会减少吗?

想法:我想也许正在提取的短语单元可能被卷入重复规则中。我可以通过这样一个新引入的填充规则来解析“expr的序列”,而不是尝试解析单个expr;但是在获得一个之后立即YYACCEPT,沿着:

start : exprs { $$ = $1; };

exprs : expr { $$ = $1; YYACCEPT; /* Got just the one expr I really wanted! */ } 
      | exprs expr { /* Never reached! Byacc fooled! */ }
      ;

我会以这种方式尝试,但是知道为什么两个Yacc实现之间存在如此差距,以及如何直接克服它仍然是个好消息。

编辑:在我的项目中工作的黑客就是这个伪代码:

start : expr { YYACCEPT; } byacc_fool { /*notreached*/ abort(); }

byacc_fool : expr { abort(); }
           | /*nothing*/ { abort(); }
           ;

所有回归测试均通过Byacc或Bison传递。

没有达到任何虚拟动作;它们的目的是创建一个语法规则,允许expr跟随另一个expr。但这是以这样的方式实现的,它实际上不会消耗第二个expr;在YYACCEPT调用时,只有一个先行令牌从任何后续expr中消耗掉。 (我有一个解决方案可以在每次连续的yyparse调用之前恢复该令牌;并且该hack现在可以在Byacc下运行。)

我仍然觉得有一些简单的东西,我没有看到其他人都知道的。

1 个答案:

答案 0 :(得分:1)

FWIW,我认为分析略有不正确,并且在$default上有误导性。

$default是一种压缩机制:让我们为所有剩余的前瞻(包括那些无效的前瞻)执行此减少,而不是为减少相同的一长串前瞻。这不允许解析器接受更多句子;可以证明错误将被其他一些规则所捕获。它既不允许解析器接受更长的前缀:错误将在完全相同的点捕获;然而,可能还会进行一些减少。作为交换,您的表格较小,因为您编码的案例较少。今天它可能是一个无用的优化,但当时的空间非常重要。

我相信Bison和Byacc都实现了这种技术(实际上Bison和Byacc是同一个程序的一个分支,并且仍然共享很多代码)。

但是,在Bison历史的某个时刻,添加了初始规则:$accept: exp $end,其中exp表示用户的起始符号。在处理此规则之前,在Bison(生成器)和生成的解析器中硬编码,甚至手动调整表以处理exp$end而不使用此“规则0”。我改变了这一点,因为(i)LR解析器的理论确实使用了这个规则,因此当教学理论让Bison显示不同的表时,这是一个问题,并且(ii)代码更简单,更短,使用此规则0。

作为一个意想不到的后果(许多年前that change已经完成,这是我第一次听说它是可观察的),$end令牌被视为另一个,并且受制于在$default行动中融合。这是你可以区分Byacc和Bison之间的区别。

话虽这么说,我没有比你提出的另一种解决办法:)除了,如果你准备好只移动到Bison(显然不是这种情况),转移到推送解析器。这完全是为了满足您的需求:交互式循环而不是批量解析。