暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

解析树(Parse Tree)介绍

恩恩霸 2025-10-23
176

解析树(Parse Tree),又称具体语法树(Concrete Syntax Tree, CST),是编译器、数据库、解释器以及各种语言处理工具在“语法分析”阶段产出的第一颗“树”。它把原本扁平的字符流,按照文法规则一级一级地归约成嵌套的树形结构,既保留了源程序的全部“字面信息”(包括空格、括号、分号、注释等),又首次体现了程序“长什么样”。理解解析树的本质、构造、存储、遍历和后续应用,是掌握编译原理、SQL 优化、IDE 智能提示、前端 AST 变换等技术的公共基础。下文从“出现位置 → 构造原理 → 存储形态 → 遍历算法 → 与抽象语法树差异 → 工业级实例 → 常见坑与调优”七个维度,用 1500 字系统拆解。


  1. 出现位置:编译链路的“第二把手术刀”


字符流 → 词法分析(Token 序列) → 语法分析(解析树) → 语义分析(抽象语法树 AST) → 中间码/IR → 优化 → 目标码。
解析树处在“语法分析”出口,是文法推导过程的直接镜像;后续语义分析再把冗余节点折叠、合并、标注类型,得到更简洁的 AST。
数据库 SQL 引擎、前端 Babel、后端注解处理器、脚本语言解释器,只要涉及“自定义语法”,都要先生成解析树。


  1. 构造原理:最右推导的逆过程


以上下文无关文法 G = (N, Σ, P, S) 描述语言,输入 Token 序列 w。
自顶向下(LL、递归下降):从 S 出发,对非终结符按产生式展开,直到叶子节点与 w 完全匹配;展开轨迹即树。
自底向上(LR、SLR、LALR、GLR):从 w 出发,不断归约到 S,每次归约把若干子树合并成父节点;归约逆序就是树生长顺序。
无论方向,解析树节点都一一对应一次“文法应用”,因此必满足:
① 根节点是开始符号 S;
② 每个内部节点是某产生式左部非终结符 A;
③ 该节点的子节点从左到右恰好是产生式右部符号序列 X₁X₂…Xₙ;
④ 叶子节点从左到右构成原始 Token 流(含空白、注释、分号等全部“垃圾”)。


  1. 存储形态:统一基类 + 子类化 vs 结构体数组


工业级实现追求“内存紧凑 + 访问局部性好”,常见三种做法:

  1. 面向对象:统一 SyntaxNode 基类,含 Kind 枚举、Parent 指针、子节点 vector。
    优点:易调试、易附加属性;缺点:指针多、内存碎片。

  2. 结构体数组(Packed AST):所有节点顺序存入 std::vector<Node>

  3. 列式存储(Roslyn 模式):把整棵树拆成 8 个平行数组——Kinds、Parents、Lefts、Rights、Tokens、Positions、Lengths、Texts。
    优点:序列化极速,易于并发只读;缺点:代码可读性下降。
    附:节点位置(Line/Column)通常不直接存,而存“起始偏移 + 长度”,需要时再根据源码重新算行号,以节省 30%+ 内存。


  1. 遍历算法:四种场景四种策略


  1. 深度优先先序(Preorder):最常用,适合打印、重构、符号收集。

  2. 深度优先后序(Postorder):适合常量折叠、类型推导等“先看孩子再看爹”场景。

  3. 广度优先(Level-order):适合语法高亮、增量折叠 UI。

  4. 父链迭代(Parent-pointer walk):用于“向上找最近封闭作用域”这类需求,避免递归爆栈。
    实现技巧:
    · 递归版直观,但 100 万节点容易爆栈;
    · 显式栈版把“节点 + 子索引”打包,栈深度 = 树高,安全;
    · Walker 模式(Visitor Pattern)把“遍历骨架”与“业务逻辑”解耦,新增遍历时无需改树定义。


  1. 与抽象语法树 AST 的六大差异


表格

复制

维度解析树/CST抽象语法树/AST
目标保存文法推导过程保存语义要素
冗余含括号、分号、逗号节点已删除
深度往往更深更扁平
节点类型与文法产生式一一对应与语义概念对应
可逆可 100% 还原源码不一定能还原格式
用途语法调试、格式化、重构类型检查、优化、代码生成

一句话记忆:CST 是“律师视角”,连标点都留;AST 是“程序员视角”,只留能跑的逻辑。


  1. 工业级实例:MySQL、Babel、Roslyn


  1. MySQL SQL 解析树
    sql/sql_yacc.yy 定义 1000+ 产生式,yacc/bison 生成自底向上 LALR 分析器;
    节点结构:Parse_tree_node → PT_select_stmt、PT_table_ref 等子类;
    关键字段:pos(起止偏移),用于报错精准定位;
    内存分配:使用 MEM_ROOT 一次性 Arena,解析完整棵树一起 free,避免碎片。

  2. Babel JavaScript 解析树
    基于 Acorn 解析器,先出 CST,随后 babel-parser 把括号、分号、逗号折叠,生成 ESTree 规范的 AST;
    插件体系允许在 CST→AST 之间插入“自定义语法扩展”,如 TypeScript、JSX。

  3. Roslyn C# 解析树
    微软“编译器即服务”典范,语法 API 公开为 NuGet 包;
    整棵树不可变(Immutable),每次修改返回新树,旧树可共享子节点;
    支持增量解析——只重新解析受文本更改影响的子树,Visual Studio 因此能在 100 ms 内完成百万行项目语法高亮。

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论