我遇到了解析器可移植性问题:使用byacc
与bison --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下运行。)
我仍然觉得有一些简单的东西,我没有看到其他人都知道的。
答案 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(显然不是这种情况),转移到推送解析器。这完全是为了满足您的需求:交互式循环而不是批量解析。