原文:Abstract Syntax Tree (AST) – Explained in Plain English
作为开发者,你编写的源代码简洁又优雅,其他开发者也能轻松阅读和理解你的代码。但编译器要理解这些代码,还需要完成更多工作。

在本文中,你将了解编译器是如何理解代码功能的,重点学习什么是抽象语法树(AST),以及它在静态分析中的作用。

编译器如何理解你的代码?

编译器处理源代码的步骤如下图所示:

screenshot-20251031-000929

下面我们来详细解释一下这个过程。

  •  第一步:词法分析。源代码首先会通过词法分析(Lexical Analysis)过程被拆分成更小的片段,这些片段被称为 “标记”(Tokens)。词法分析也常被称为 “标记化”(Tokenization)。
  • 第二步:语法分析。随后,编译器的解析器(Parser)会对这些标记进行处理,将它们解析成一种树状结构,这就是语法树(Syntax Tree)。

而抽象语法树(AST) 会忽略掉源代码中的某些细节,只保留足够的信息,帮助编译器理解代码的结构。

因此,AST 是一种树状数据结构,能最精准地表示源代码的语法结构

如果现在你还不太理解,也不用担心!接下来几分钟,我们会用一个贴近生活的类比,让你明白编译器的工作方式,其实和你理解一个句子的过程非常相似。

而抽象语法树(AST) 会忽略掉源代码中的某些细节,只保留足够的信息,帮助编译器理解代码的结构。

因此,AST 是一种树状数据结构,能最精准地表示源代码的语法结构。

如果现在你还不太理解,也不用担心!接下来几分钟,我们会用一个贴近生活的类比,让你明白编译器的工作方式,其实和你理解一个句子的过程非常相似。

词法分析(Lexical Analysis)

在这一部分,我们来深入了解词法分析。

假设你正在学习一门新的语言 —— 不过不是编程语言哦 😄。现在有个任务,让你理解这门语言中某个句子的含义,你会怎么做呢?

首先,你会尝试识别句子中的名词、动词,或者更宽泛地说,识别出那些有实际意义的 “词”。词法分析的过程,就和这一步非常相似。

面对源代码时,编译器首先会尝试识别出代码所包含的各种类型的 “标记”。

在编程语言中,“标记” 可以是任何合法的语法单元,比如取值固定的 “字面量”(Literal,如数字 5、字符串 “hello”)、“变量”(Variable)、“运算符”(Operator,如 +、=),或者 “函数调用”(Function Call)等。

由于词法分析是将源代码拆分成标记的过程,所以它也被称为 “标记化”。

语法分析(Syntactic Analysis)

到目前为止,我们已经知道:标记化(词法分析)的结果是一堆 “标记” 或 “语法单元”—— 这就像你在理解句子时,先识别出其中的名词、动词等成分一样。

我们再回到刚才的类比。当你识别出句子中的名词和动词后,接下来会如何推断句子的含义呢?

显然,你会尝试分析名词、动词之间的关系 —— 看看它们如何组合在一起,是否符合这门语言的语法规则。

这个过程,就对应着编译器的语法分析(Syntactic Analysis),也常被称为 “解析”(Parsing)。

为了完成语法分析,编译器会通过 “解析器”(Parser)处理之前得到的标记,并将这些标记解析成一种树状结构,这就是抽象语法树(AST)

更多关于 AST 的表示方式

不同语法单元之间的关系,通常是 “与语言相关的”。举个例子,德语句子的语法结构和印地语句子的语法结构可能截然不同。

同理,AST 也没有统一的通用表示方式,其实际结构会因编程语言的不同而有所差异。

不过总体来说,AST 会将源代码中标记之间的关系建模成一棵 “树”:树由 “节点”(Nodes)构成,每个节点可以包含子节点;而且每个节点都会存储与标记相关的信息,包括标记的类型和对应的关联数据。

例如,如果某个节点代表 “函数调用”(Function Call),那么该节点存储的关联数据就会包括这个函数的 “参数”(Arguments)和 “返回值”(Return Values)。

示例:为公式 2 + (z – 1) 绘制 AST

我们以一个简单的数学公式 2 + (z – 1) 为例,看看它的 AST 是什么样的:

screenshot-20251031-001943

在这个 AST 中,+ 和 – 是运算符节点,z 是变量节点,1 和 2 是字面量节点。

需要注意的是,源代码中的括号 () 在 AST 中被 “舍弃” 了 —— 因为括号的作用(改变运算优先级)已经通过节点的层级关系体现出来了:z 和 1 都是 – 节点的子节点,这就对应了源代码中的 (z – 1)。

想深入了解抽象语法树 (AST) 吗?AST Explorer 可以帮助你可视化 Go、Python、Java 和 JavaScript 等几种流行语言的 AST。

再来看一个简单的 Python 代码示例:

这段代码对应的 AST(以 JSON 格式简化展示)如下:

在这个 AST 中,核心节点的类型是 VariableDeclaration(变量声明),这是因为我们在代码中声明了变量 my_str。其中:

  • loc 字段记录了该变量声明在源代码中的位置(起始行、起始列、结束行、结束列);
  • range 字段记录了该代码片段在源代码中的字符索引范围;
  • declarations 字段则会包含变量名(my_str)和变量值(字符串 “code more!”)的详细信息。

AST 在开发中的作用

那么,AST 在实际开发过程中会在哪些场景出现呢?

在大多数编程语言中,能生成 AST 的解析器,通常还会提供 “遍历 AST” 的方法。通过这些方法,你可以访问 AST 中的各个节点,理解每个节点的功能,甚至在此基础上进行进一步的分析。

借助 AST,你可以完成以下工作:

  • 定义一套分析规则(例如 “禁止使用未声明的变量”“函数名必须使用驼峰命名”);
  • 遍历 AST 的每个节点;
  • 检查代码是否违反了预先定义的规则。

而这,正是 AST 在静态代码分析(Static Code Analysis)中的核心作用。

静态代码分析的过程是:先将源代码解析成一种 “中间表示”(Intermediary Representation),然后在这种中间表示上进行分析 —— 整个过程不需要实际运行代码。而这种 “中间表示”,通常就是 AST。

通过静态代码分析,我们可以快速发现代码中潜在的安全问题、Bug(漏洞)和性能问题,进而立即修复这些问题。如果你想了解更多关于静态分析的内容,可以参考 What Is Static Code Analysis?

总结

在本文中,你已经学习了以下内容:

1、词法分析和语法分析的工作原理:

  • 词法分析:识别源代码中的 “标记”(Tokens);
  • 语法分析:解析这些标记之间的关系,判断它们如何组合成合法的代码结构。

2、 AST 是源代码的树状表示,能精准反映代码的语法结构;

3、AST 如何为静态代码分析提供支持,帮助开发者提升代码质量。

 

 

 

 

 

分类&标签