6.5 语法分析器生成器(parser generator)
语法分析器生成器 把文法规范变成分析表或源码。它不会替你完成语言设计;它让设计可执行、可重复。文法文件是词法分析器(lexer)、语法分析器(parser)、抽象语法树(AST)、诊断与构建系统之间的重要接口。
实现路径大致两条,二选一是真实的工程决策:
| 维度 | 手写(recursive descent / Pratt) | 生成(LR/LALR 表) |
|---|---|---|
| 文法规模 | 适合小而频繁演化的语言 | 适合大而稳定的文法 |
| 错误控制 | 可完全手工定制消息 | 受生成器恢复模型限制 |
| 表达力 | 偏 LL,靠手工向前看 | 完整 LR 家族能力 |
| 可调试性 | 单步普通代码 | 读表 + 冲突报告 |
| 维护成本 | 随语言规模增长 | 随声明复杂度增长 |
没有绝对优劣:按这些维度选择,而不是凭习惯。
一份规范包含什么
通常包括终结符(terminal)与值类型、非终结符(nonterminal)和产生式、优先级/结合性、构造 AST 的语义动作(semantic action)、开始符号、错误规则与生成配置。文法应引用稳定的 IDENT、INT、PLUS、LBRACE 等词法单元(token)种类,不应重新处理词法分析器已做的字符拼写规则。每个 AST 节点也应有源范围,方便诊断和工具链。
语义动作在归约时执行。把 $1、$2、$$ 作为薄桥梁,真正的节点构造放到有名称字段的辅助函数中;动作同样需要类型检查、测试并避免隐藏可变状态。优先级声明应记录语言决定,不应用来遮掩结构冲突。错误伪词法单元适合放在语句结束或块(block)闭合等稳定边界。
生成文件是否提交取决于项目构建模型,但文法源、工具版本、冲突报告和金标 AST/诊断测试必须保留。选择生成器时看语言生态、算法、类型化动作、恢复、IDE 支持与表大小;小语言的手写 RD/Pratt 依然常是最佳工程选择。
把文法文件当生产代码
Expr: Expr PLUS Term 的动作应只构造一个命名清楚、保留算符范围的 AST 节点;不应在这里做类型检查或生成机器码。这样解析失败保持局部,同一 AST 也能服务格式化器、IDE 与后续阶段。若动作长到十行,应移入类型化辅助函数,让文法文件展示语言结构而不是藏逻辑。
每次改文法都检查冲突报告,记录有意冲突和对应反例。生成文件不应手改:要么按项目需要作为可复现工件提交,要么由钉住版本的工具在构建时生成。
复审问题
- 每个终结符是否有唯一词法分析器所属者与稳定类型?
- 每个产生式是否构造节点或明确转发值?
- 优先级是否有 AST 形状测试?
- 错误规则是否在真实边界恢复且不吞下下一条声明?