24.8.1.5 适配简易解析器

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->prec2resolvers 参数解决即可。 这通常是因为语法本身存在歧义:冲突不影响语法描述的程序集合, 仅影响程序的解析方式。 分隔符与可结合中缀运算符便属于典型场景, 此时可添加类似 '((assoc "|")) 的解析规则。 另一个典型场景是经典的 悬垂 else 问题, 可使用 '((assoc "else" "then")) 解决。 冲突真实存在且无法真正解决、但在实际使用中几乎不会引发问题时,也可采用此方式。

最后,很多时候即便尽力重构语法,仍会残留部分冲突。 不必气馁:解析器无法变得更智能,但你可以让词法分析器尽可能灵活。 因此解决方案是:找到冲突涉及的标记, 将其中一个拆分为两个(或更多)不同标记。 例如,若语法需要区分 "begin" 的两种不兼容用法, 可让词法分析器根据识别到的 "begin" 类型返回不同标记(如 "begin-fun""begin-plain")。 这会将区分不同场景的工作转移给词法分析器, 使其需要根据上下文文本寻找特定线索完成判断。


emacs

Emacs

org-mode

Orgmode

Donations

打赏

Copyright

© Jasper Hsu

Creative Commons

Creative Commons

Attribute

Attribute

Noncommercial

Noncommercial

Share Alike

Share Alike