若只看一句简介,Tree-sitter 往往会被收进 parser generator 那一栏,和 grammar 文件、编译器工具、编辑器背后那类难而远的基础设施摆在一起。Max Brunsfeld 在 2017 年 Strange Loop 的演讲值得回看,正在于他从开场就换了位置。Tree-sitter 被放进 Atom 和其他编程工具的语境里,接下来的整场论证也都围绕同一个判断展开:解析一旦能跟上实时编辑,并同时供给多种工具功能,它的意义就会完全显出来。[1]

把这支视频放到 2026 年再读,官方文档仍然在支持这一种理解。Tree-sitter 首页把项目定义成 parser generator 加 incremental parsing library,然后强调四个更偏编辑器的性质:它应该能覆盖多种语言,快到可以在每次击键时运行,在语法错误存在时仍然给出有用结果,并且足够 dependency-free,能被嵌入各类应用。[2] Implementation 文档又把这条线写得更清楚:libtree-sitter 是嵌入式 runtime,CLI 承担 parser 生成这一层构建工作,生成完成之后就退出主舞台。[7] 这层分工之所以关键,在于它把项目真正优化的目标说透了。grammar generation 当然重要,更深的一层在于怎样给编程工具提供一个足够快、足够本地、足够稳的结构接口,让它可以一直留在编辑热路径上。

顺着视频与文档一起看,Tree-sitter 最强的想法落得很具体。它给编辑器提供了一种更清楚的工作方式:语法树会在修改之后继续更新,其他功能可以围绕它做查询,文件暂时失效时这棵树仍然留在场上。[1][2][3][4][5][7] 放在这个层面里,Tree-sitter 更像一种编辑时接口。

配图说明:题图使用 Max Brunsfeld 的真实 GitHub 头像肖像。这张图合适,因为本文围绕的是作者本人对原始设计边界的解释,重心落在思路来源与工具边界本身。[6]

大约从 1:18 开始,项目开始显出编辑原语的形状

视频里第一步真正决定性的动作,出现在 Brunsfeld 把 Tree-sitter 定义成 incremental parsing 的时候。[1] 他的意思并不复杂,却带着很硬的架构意味:文件完成一次初始解析之后,后续编辑进入的是一条持续更新的路径;已有的树会被调整、被复用。今天的 runtime API 仍然保留着这条主线。“Advanced Parsing” 文档写的是同一个两步模型:先把 TSInputEdit 应用到现有语法树上,再把旧树传回解析器,让新树在内部共享原先的结构。[3]

这会直接改变解析在编程工具里的意义。放进编译器语境里,整文件重解析往往还可以接受,因为工作单元本来就是一次有意发起的 build。放进编辑器语境里,用户一直在输入、删除、粘贴,也一直在短暂制造语法损坏,这条边界就会显得昂贵而且醒目。Tree-sitter 的贡献,在于把结构复用放进正常接口里,让它从一开始就贴着工具的工作方式展开。[1][3]

Implementation 文档又把这个判断往前推了一步。CLI 从 grammar.js 生成 parser.c,但日常真正落进产品表面的,是驻留在应用内部的 C runtime。[7] Grammar 当然重要,真正的产品更像一套可以被应用复用的 runtime 契约,让工具在源代码持续变化时仍然维持响应。

视频中段之所以关键,是因为语法高亮与代码折叠才是真正的证明

Brunsfeld 给出的最好证据,落在任何编辑器用户都能立刻感觉到的普通功能上。视频中段反复回到 syntax highlighting,这正是许多编辑器最容易露出边界的地方:延迟、错误着色、文件一大就掉速。[1] 他把 Tree-sitter 与重度依赖正则的高亮,以及带着明显回传延迟的远程语言服务路径放在一起看,再演示基于语法树的方案如何更干净、更本地地完成更新。[1] 到了今天,官方高亮文档仍然保留着相同哲学。Tree-sitter 提供专门的 highlighting library,明确写到 GitHub.com 已经用它为若干语言做高亮,并解释高亮、局部变量分析与语言注入都由 tree queries 加每种语言的元数据共同驱动。[4]

这层变化很难用一句“高亮更好看”带过去。它实际在说,语法树之所以有价值,是因为很多编辑器功能都需要同一份结构真相。大约在 12:17 左右,Brunsfeld 展示一份大文件能在约 54 毫秒内完成解析,并据此说明文件一打开,颜色几乎就可以全部立即出现;紧接着他又转向 code folding,拿它做第二个示范。[1] 这组顺序很值得记住。折叠和高亮共用的是同一套结构模型。只要语法树足够便宜、足够稳定,功能开发的重心就会从发明各种临时启发式,转向决定哪些树结构该驱动哪些交互。[1][4]

也正因为这样,Tree-sitter 更适合被叫作工具基础设施。高亮与折叠之所以是证明,就在于它们属于最普通、最高频的编辑器动作。系统一旦能让这些动作同时变得即时而且结构准确,编辑器本身的架构就已经改变了。Parser 会从后台专家的位置,进入一份被多处共享的前线 runtime 状态。[1][2][4]

大约从 14:34 开始,query 层把语法树变成了其他功能真正可用的接口

视频到这里会显得更清楚。Brunsfeld 说查询语法树有点像查询 DOM。[1] 这句话点出的,是许多解析系统一直缺少的那一层。语法树一旦能让别的工具功能围绕它做匹配,又不用把 parser 逻辑全部重新带进来,它就真正开始普遍可用。Tree-sitter 的 query system 正是这层接口。Query Syntax 文档把它写成针对节点、子结构与字段的 S-expression 模式;Syntax Highlighting 文档再把这些 query 接到 highlights、locals 与 injections 的实际文件组织上。[4][5]

到了这里,Tree-sitter 已经从一个快的树生成器,长成了一个功能平台。折叠系统、符号导航、文本对象选择器、重构辅助,只要都能围绕同一棵树用显式模式做匹配,编辑器里的结构功能就会越来越多地落进同一份公共基底。[1][4][5] 本文最核心的判断也落在这里:树本身只完成了一半工作,另一半是 query 层,它让普通工具代码可以提出结构问题,同时把 parser 留在自己的位置上。

这也解释了 Tree-sitter 生态为什么能持续长出来。高亮文档把 tree-sitter.json、query 文件和 grammar 仓库编排成一条线,首页又强调它对多种语言的普适性。[2][4] 两边合起来,露出的是一份相当克制的模块化协议:runtime 保持小而易嵌入,grammar 各自独立,query 则承担面向功能层的接口。比起让每个编辑器功能都直接和 parser 内部细节对话,这是一种更干净的扩展方式。

视频后段关于错误恢复的部分,解释了 Tree-sitter 穿过真实编辑的能力

这支视频到最后仍然重要,还有一个原因,就是它对坏代码的处理。Brunsfeld 说得很直白:就算文件里有错误,树也应该照样返回,只是在树里标出错误出现的位置。[1] 再往后,他解释 Tree-sitter 会借助 GLR 风格的分支,在语法出错时尝试不同恢复路径,继续把结构往前保住。[1] 首页到今天还把“在 syntax errors 存在时保持有用”列为项目目标,这句话本身就是增量解析在编辑器里成立的前提。[2]

坏代码在文本编辑器里,本来就是两次击键之间最常见的状态。编辑时接口一旦遇到 for if 这类临时错误就立刻失去结构,后面的工具功能就很难继续展开。Tree-sitter 的 error nodes、恢复分支与 tree-edit API,最后都指向同一个方向:尽量保住结构,让工具的其他部分继续运作。[1][2][3]

也正因为这样,这个项目即使隔了很多年再看,仍然显得异常清楚。真正的论点落得很实:把解析留在本地,编辑之后复用旧树,让功能围绕这棵树做查询,穿过语法损坏继续前进。四个条件一旦同时成立,高亮、折叠以及其他编辑器能力,就会越来越像同一份结构 runtime 的不同视图。[1][2][3][4][5][7]

来源

  1. Strange Loop Conference,《"Tree-sitter - a new parsing system for programming tools" by Max Brunsfeld》,YouTube 视频,发布于 2017 年 10 月 16 日。
  2. Tree-sitter 文档首页——项目概览、增量解析目标、容错性与无依赖 runtime。
  3. Tree-sitter 文档,《Advanced Parsing》——TSInputEditts_tree_edit、增量重解析、结构共享与 included ranges。
  4. Tree-sitter 文档,《Syntax Highlighting》——高亮库、GitHub.com 使用说明、tree-sitter.json 与 query 驱动的高亮 / locals / injections 文件。
  5. Tree-sitter 文档,《Query Syntax》——S-expression 模式、字段约束与 tree query 模型。
  6. Max Brunsfeld 的 GitHub 个人页——包含本文配图所用肖像来源。
  7. Tree-sitter 文档,《Implementation》——libtree-sitter、CLI 生成 parser,以及嵌入式 runtime / 工具分工。