上周试用木兰语言加 QtWebkit 实现简易网页浏览器(已开源在 Gitee ,81 行代码)时,发现需要复现带参数的 super:
super(演示, self).__init__()
实现过程中,又一次遇到了这个头疼的报错:
ParserGeneratorWarning: 1 shift/reduce conflict
去年碰到过几次,都是通过照着逆向工程设置词的优先级来规避,没有深究调试方法,这次决定下点功夫搞清楚缘由。
rply 的调试信息有限,貌似 七年前就是如此,包括 shift/reduce 在内的所有信息都极简,项目现在似乎也没有什么改进的迹象。由于它是参考 ply 重写的,于是查看了 ply 对歧义语法的调试信息示例。纠结了一下是把木兰用 ply 重写还是修改 rply 来获得类似调试信息,决定走后者。
这里记一下:任何框架或工具,包括编程语言,可调试性——即告知用户“哪儿出错了?”——对于可用性非常非常重要。任何的反馈信息,包括警告、报错,都应以用户可理解、问题可定位、提供解决方案为首要目标。
首先,分别创建了 ply 和 rply 的最简歧义演示。语法如下(ply 不支持在语法定义中使用中文标识符,下面是 rply 的表示):
表达式 : 数
表达式 : 表达式 减 表达式
测试表达式为 2 - 3 - 4
。在没有优先级设置时,会报警:1 shift/reduce conflict
而且输出:3
在 ply 的 parser.out 文件中,可以看到进一步细节:
state 4
(1) expression -> expression MINUS expression .
(1) expression -> expression . MINUS expression
! shift/reduce conflict for MINUS resolved as shift
$end reduce using rule 1 (expression -> expression MINUS expression .)
MINUS shift and go to state 3
! MINUS [ reduce using rule 1 (expression -> expression MINUS expression .) ]
根据 ply 官方文档,可以估摸出是第二个减号有歧义,可以先将 2-3 化简为“expression”,也可以将减号接在 3 后面,作为 expression . MINUS expression
的一部分。在有此类歧义时,默认 shift 而不化简。因此,待 3-4 都解析完后化为 expression,求值为 -1,再将 2-(-1) 化简求值为 3。
这里注意到,这个报警信息与具体的测试表达式无关。在语法分析器生成时,会根据语法定义生成所有可能的语法要素序列,在此基础上发现所有歧义。另一方面也好奇,是否能够进一步生成能够复现歧义的表达式用例,这样应该可以更方便开发者调试吧。
接下来,就是在 rply 源码内加入调试信息输出(开源在此)。可以看到下面的 5 个序列:
0[LRItem(S' -> . 表达式), LRItem(表达式 -> . 数), LRItem(表达式 -> . 表达式 减 表达式)]
1[LRItem(表达式 -> 数 .)]
2[LRItem(S' -> 表达式 .), LRItem(表达式 -> 表达式 . 减 表达式)]
3[LRItem(表达式 -> 表达式 减 . 表达式), LRItem(表达式 -> . 数), LRItem(表达式 -> . 表达式 减 表达式)]
4[LRItem(表达式 -> 表达式 减 表达式 .), LRItem(表达式 -> 表达式 . 减 表达式)]
以及歧义相关的序列和词:4 》》 '减',即:4[LRItem(表达式 -> 表达式 减 表达式 .), LRItem(表达式 -> 表达式 . 减 表达式)]
中的减号,句点含义与 ply 中相同,这样包含的信息已接近了 ply 的。
回头看 super 语法,整理后的输出如下:
词'('有歧义,默认进行 shift2
歧义序列:
超类: super .
超类: super . 实参部分
实参部分: . ( )
实参部分: . ( 各实参 )
仍然看不大清,简化了 实参部分
语法后,仍然有歧义:
词'('有歧义,默认进行 shift2
歧义序列:
超类: super .
超类: super . 实参部分
实参部分: . ( )
继续简化:
词'('有歧义,默认进行 shift2
歧义序列:
超类: super .
超类: super . ( )
到这里看出,其实很简单,没有优先级设置时,super()
有两种解析方式:
步骤 | 符号栈 | 词输入 | 行为 |
---|---|---|---|
1 | | super ( ) | 移入 super
2 | super | ( ) | 歧义:1)根据 超类: super
,可以直接消减(reduce)为 超类
;2) 根据 超类: super ( )
,可以移入 (
问题确定之后,就是如何确定优先级高低,待继续研究。