You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Token是否应作为AST节点组成部分?各编程语言实现方案参考

提出该问题的相关背景

我在阅读《Writing An Interpreter In Go》一书时注意到,书中的AST节点内直接包含Token结构体,只要实现tokenLiteral()和String()方法即可满足Node类型的要求:

type IntegerLiteral struct {
    Token token.Token
    Value int64
}

type Node interface {
    TokenLiteral() string
    String() string
}

我了解到实际生产环境中的编译器需要提供报错对应的行列位置信息,而词法分析器无法检测语法类错误,因此位置信息必须传递到语法分析阶段。例如Go语言编译器的AST节点定义如下:

type Pos int

// All node types implement the Node interface.
type Node interface {
    Pos() token.Pos // 节点对应第一个字符的位置
    End() token.Pos // 节点紧邻的下一个字符的位置
}

问题详细说明

据我所知,编译前端的工作流程为:字符流 -> 词法单元流 -> 抽象语法树(AST),每个阶段都会完成对应层级的抽象。在我个人的理解中,Token不应当作为AST节点的组成部分,因此想请教两个问题:

  • Token是否应当作为AST节点的组成部分?
  • 能否举例说明不同编程语言分别采用了哪种实现方案?

回答

不存在非黑即白的“应当/不应当”标准,这本质是工程实现上的权衡选择,和编译原理的阶段抽象原则没有冲突——分层是为了降低逻辑复杂度,不是禁止跨阶段保留必要数据。

两种方案的适用场景和优劣非常明确:

  • 直接在AST节点内嵌完整Token结构:
    优势是实现成本极低,不需要额外设计位置映射、字面量存储逻辑,节点需要的原始词法内容、位置信息可以直接从内嵌Token中获取,省掉大量样板代码,非常适合入门教程、小型解释器、或者需要精确还原源码格式的工具(比如代码格式化器、语法高亮器)。《Writing An Interpreter In Go》作为入门教材选这个方案,就是为了把读者的注意力集中在解释器核心逻辑上,不被周边的工程细节分散精力。
    劣势是内存冗余度高:Token结构通常包含词法类型、原始字面量、位置、附属标记等大量字段,而后续语义分析、IR生成阶段只会用到其中很小一部分,在处理百万行级别的大型代码库时,这种冗余会带来非常明显的内存开销。
  • 只存轻量位置标识、不保留完整Token:
    优势是AST结构极其紧凑,内存效率极高。比如Go用的Pos本质就是一个整数偏移,Rustc用的Span、Clang用的SourceLocation都是类似设计,只存节点在源码中的起止位置,需要获取原始字面量、行列号的时候,再通过全局维护的源码映射表统一查询,非常适合工业级编译器处理超大型项目。
    劣势是实现复杂度更高,需要额外维护全局的位置-源码映射逻辑,对于小型项目来说属于过度设计。

不同编程语言的实现选择可以参考这些例子:

  • 保留完整Token/全量词法信息的实现:
    • 各类教学类解释器(包括《Writing An Interpreter In Go》、SICP中的元循环求值器、多数简易Lisp实现)
    • 代码格式化工具Prettier的内部AST,因为格式化需要精确匹配原始源码的所有词法细节,存完整Token反而能减少大量查询逻辑
  • 只存轻量位置标识、不内嵌完整Token的工业级实现:
    • Go官方编译器:用Pos整数作为统一位置标识,所有AST节点只暴露Pos()和End()方法返回位置,所有位置相关查询统一通过全局FileSet处理
    • Rust生产级编译器rustc:AST节点只存Span结构封装源码偏移范围,不保留Token对象
    • C/C++编译器Clang:AST节点存SourceLocation轻量标识,通过全局SourceManager处理位置查询、宏展开溯源等逻辑
    • OpenJDK的javac编译器:AST节点仅存储源码偏移位置,不保留完整词法单元对象
  • 折中方案:
    Python标准库的ast模块没有保留完整Token结构,但也没有只用整数偏移,而是直接把后续阶段常用的字段拆出来存在节点上:比如位置信息拆成lineno/col_offset/end_lineno/end_col_offset四个字段,常量节点直接存解析后的Python值,标识符节点直接存标识符字符串,平衡了实现复杂度和运行效率。

内容的提问来源于stack exchange,提问作者eugercek

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 17:48:20