SMIE 采用的解析技术不允许标记在不同语境下表现出不同行为。 对多数编程语言而言,这会在转换 BNF 语法时产生优先级冲突。
有时,可通过略微调整语法写法规避这类冲突。 例如,对 Modula-2 而言,直观的 BNF 语法可能如下:
...
(inst ("IF" exp "THEN" insts "ELSE" insts "END")
("CASE" exp "OF" cases "END")
...)
(cases (cases "|" cases)
(caselabel ":" insts)
("ELSE" insts))
...
但这会为 "ELSE" 带来冲突:
一方面,IF 规则隐含(诸多关系中)"ELSE" = "END";
另一方面,由于 "ELSE" 出现在 cases 内部,
而 cases 位于 "END" 左侧,因此又有 "ELSE" > "END"。
可通过以下方式解决该冲突:
...
(inst ("IF" exp "THEN" insts "ELSE" insts "END")
("CASE" exp "OF" cases "END")
("CASE" exp "OF" cases "ELSE" insts "END")
...)
(cases (cases "|" cases) (caselabel ":" insts))
...
或
...
(inst ("IF" exp "THEN" else "END")
("CASE" exp "OF" cases "END")
...)
(else (insts "ELSE" insts))
(cases (cases "|" cases) (caselabel ":" insts) (else))
...
不过,调整语法以解决冲突也存在弊端, 因为 SMIE 假定语法反映代码的逻辑结构, 因此更推荐让 BNF 尽量贴近预期的抽象语法树。
另一些情况下,经仔细分析后你可能判定这类冲突并不严重,
直接通过 smie-bnf->prec2 的 resolvers 参数解决即可。
这通常是因为语法本身存在歧义:冲突不影响语法描述的程序集合,
仅影响程序的解析方式。
分隔符与可结合中缀运算符便属于典型场景,
此时可添加类似 '((assoc "|")) 的解析规则。
另一个典型场景是经典的 悬垂 else 问题,
可使用 '((assoc "else" "then")) 解决。
冲突真实存在且无法真正解决、但在实际使用中几乎不会引发问题时,也可采用此方式。
最后,很多时候即便尽力重构语法,仍会残留部分冲突。
不必气馁:解析器无法变得更智能,但你可以让词法分析器尽可能灵活。
因此解决方案是:找到冲突涉及的标记,
将其中一个拆分为两个(或更多)不同标记。
例如,若语法需要区分 "begin" 的两种不兼容用法,
可让词法分析器根据识别到的 "begin" 类型返回不同标记(如 "begin-fun" 和 "begin-plain")。
这会将区分不同场景的工作转移给词法分析器,
使其需要根据上下文文本寻找特定线索完成判断。