歧义文法
复习
- 上下文无关文法:产生式与推导
- 表达式的优先级与结合性:约定运算顺序
- 语法树:结构的树形表示
TL;DR
- 歧义文法指同一串输入能得到多棵语法树
- 歧义会让编译器“不知道该按哪种解释”
- 常见成因是缺少优先级或结合性的约定
- 消除歧义可以靠改写文法或补充规则
正文
前面说 1 - 2 - 3 必须约定结合性。如果不约定,会发生什么?答案是:文法变成歧义的(ambiguous)。
一棵输入,多棵树
歧义文法,指的是这样一类文法:存在某个输入串,它可以被推导出不止一棵语法树。
回到 1 - 2 - 3。如果文法写成:
表达式 → 表达式 - 表达式
表达式 → 数字
那么 1 - 2 - 3 既可以被推成 (1 - 2) - 3 的树,也可以被推成 1 - (2 - 3) 的树。两棵都“合法”,但含义不同、结果也不同。
为什么危险
对编译器来说,歧义是件麻烦事,因为它不知道该听哪一棵树。
语法树决定了语义:(1-2)-3 得 -4,1-(2-3) 得 2,完完全全是两个程序。如果编译器“随便选一棵”,那么同一段代码在不同编译器上可能算出不同结果,这显然不能接受。一个合格的文法,不该让人和机器都无所适从。
顺带一提,还有个有名的歧义例子叫“悬空 else”:if A if B S1 else S2 里,那个 else 到底配哪个 if?不同配法会产生不同的语法树。
怎么消除歧义
消除歧义,通常有两种思路:
- 改写文法:把它改成无歧义的等价形式。比如给表达式分层,用层次体现优先级和结合性,
1 - 2 - 3就只剩一种树了 - 补充约定:在文法之外,明确规定优先级、结合性等规则,让分析器遇到选择时按约定来
前面讲优先级与结合性,本质上就是在消除表达式的歧义。可见这两个话题是紧密相连的。
一句话总结:好的文法,应当让每个合法输入都只有唯一一棵语法树。 这也是设计程序语言时,必须认真对待的一环。
思考题 1
为什么歧义文法对编译器是危险的?
思考题 2
消除歧义,常见有哪些办法?
小结
知识点
- 歧义文法指同一输入可得到多棵语法树
- 歧义会导致语义不唯一
- 常见成因是缺少优先级/结合性约定
- 可通过改写文法或补充规则消除歧义
参考资料
- Wikipedia(zh):歧义文法:存在多棵语法树的文法
- Wikipedia(zh):悬空else问题:经典的语法歧义例子
思考题答案(仅供参考)
思考题 1
因为歧义意味着同一段代码可以对应多棵语法树,而不同语法树往往语义不同、结果不同。编译器若无法唯一确定该用哪棵树,就可能产生与预期不符甚至跨平台不一致的行为。
思考题 2
常见办法有两种:一是改写文法,把它变换成无歧义的等价形式,例如给表达式分层来体现优先级和结合性;二是补充文法之外的约定(如优先级、结合性规则),让分析器按约定做出唯一选择。
协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
封面图
设计师 | 南国微雪