解析树(Parse Tree),又称具体语法树(Concrete Syntax Tree, CST),是编译器、数据库、解释器以及各种语言处理工具在“语法分析”阶段产出的第一颗“树”。它把原本扁平的字符流,按照文法规则一级一级地归约成嵌套的树形结构,既保留了源程序的全部“字面信息”(包括空格、括号、分号、注释等),又首次体现了程序“长什么样”。理解解析树的本质、构造、存储、遍历和后续应用,是掌握编译原理、SQL 优化、IDE 智能提示、前端 AST 变换等技术的公共基础。下文从“出现位置 → 构造原理 → 存储形态 → 遍历算法 → 与抽象语法树差异 → 工业级实例 → 常见坑与调优”七个维度,用 1500 字系统拆解。
出现位置:编译链路的“第二把手术刀”
字符流 → 词法分析(Token 序列) → 语法分析(解析树) → 语义分析(抽象语法树 AST) → 中间码/IR → 优化 → 目标码。
解析树处在“语法分析”出口,是文法推导过程的直接镜像;后续语义分析再把冗余节点折叠、合并、标注类型,得到更简洁的 AST。
数据库 SQL 引擎、前端 Babel、后端注解处理器、脚本语言解释器,只要涉及“自定义语法”,都要先生成解析树。
构造原理:最右推导的逆过程
以上下文无关文法 G = (N, Σ, P, S) 描述语言,输入 Token 序列 w。
自顶向下(LL、递归下降):从 S 出发,对非终结符按产生式展开,直到叶子节点与 w 完全匹配;展开轨迹即树。
自底向上(LR、SLR、LALR、GLR):从 w 出发,不断归约到 S,每次归约把若干子树合并成父节点;归约逆序就是树生长顺序。
无论方向,解析树节点都一一对应一次“文法应用”,因此必满足:
① 根节点是开始符号 S;
② 每个内部节点是某产生式左部非终结符 A;
③ 该节点的子节点从左到右恰好是产生式右部符号序列 X₁X₂…Xₙ;
④ 叶子节点从左到右构成原始 Token 流(含空白、注释、分号等全部“垃圾”)。
存储形态:统一基类 + 子类化 vs 结构体数组
工业级实现追求“内存紧凑 + 访问局部性好”,常见三种做法:
面向对象:统一 SyntaxNode 基类,含 Kind 枚举、Parent 指针、子节点 vector。
优点:易调试、易附加属性;缺点:指针多、内存碎片。结构体数组(Packed AST):所有节点顺序存入 std::vector<Node>
列式存储(Roslyn 模式):把整棵树拆成 8 个平行数组——Kinds、Parents、Lefts、Rights、Tokens、Positions、Lengths、Texts。
优点:序列化极速,易于并发只读;缺点:代码可读性下降。
附:节点位置(Line/Column)通常不直接存,而存“起始偏移 + 长度”,需要时再根据源码重新算行号,以节省 30%+ 内存。
遍历算法:四种场景四种策略
深度优先先序(Preorder):最常用,适合打印、重构、符号收集。
深度优先后序(Postorder):适合常量折叠、类型推导等“先看孩子再看爹”场景。
广度优先(Level-order):适合语法高亮、增量折叠 UI。
父链迭代(Parent-pointer walk):用于“向上找最近封闭作用域”这类需求,避免递归爆栈。
实现技巧:
· 递归版直观,但 100 万节点容易爆栈;
· 显式栈版把“节点 + 子索引”打包,栈深度 = 树高,安全;
· Walker 模式(Visitor Pattern)把“遍历骨架”与“业务逻辑”解耦,新增遍历时无需改树定义。
与抽象语法树 AST 的六大差异
| 维度 | 解析树/CST | 抽象语法树/AST |
|---|---|---|
| 目标 | 保存文法推导过程 | 保存语义要素 |
| 冗余 | 含括号、分号、逗号节点 | 已删除 |
| 深度 | 往往更深 | 更扁平 |
| 节点类型 | 与文法产生式一一对应 | 与语义概念对应 |
| 可逆 | 可 100% 还原源码 | 不一定能还原格式 |
| 用途 | 语法调试、格式化、重构 | 类型检查、优化、代码生成 |
一句话记忆:CST 是“律师视角”,连标点都留;AST 是“程序员视角”,只留能跑的逻辑。
工业级实例:MySQL、Babel、Roslyn
MySQL SQL 解析树
sql/sql_yacc.yy 定义 1000+ 产生式,yacc/bison 生成自底向上 LALR 分析器;
节点结构:Parse_tree_node → PT_select_stmt、PT_table_ref 等子类;
关键字段:pos(起止偏移),用于报错精准定位;
内存分配:使用 MEM_ROOT 一次性 Arena,解析完整棵树一起 free,避免碎片。Babel JavaScript 解析树
基于 Acorn 解析器,先出 CST,随后 babel-parser 把括号、分号、逗号折叠,生成 ESTree 规范的 AST;
插件体系允许在 CST→AST 之间插入“自定义语法扩展”,如 TypeScript、JSX。Roslyn C# 解析树
微软“编译器即服务”典范,语法 API 公开为 NuGet 包;
整棵树不可变(Immutable),每次修改返回新树,旧树可共享子节点;
支持增量解析——只重新解析受文本更改影响的子树,Visual Studio 因此能在 100 ms 内完成百万行项目语法高亮。




