原文:Abstract Syntax Tree (AST) – Explained in Plain English
作为开发者,你编写的源代码简洁又优雅,其他开发者也能轻松阅读和理解你的代码。但编译器要理解这些代码,还需要完成更多工作。
在本文中,你将了解编译器是如何理解代码功能的,重点学习什么是抽象语法树(AST),以及它在静态分析中的作用。
编译器如何理解你的代码?
编译器处理源代码的步骤如下图所示:

下面我们来详细解释一下这个过程。
- 第一步:词法分析。源代码首先会通过词法分析(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 是什么样的:

在这个 AST 中,+ 和 – 是运算符节点,z 是变量节点,1 和 2 是字面量节点。
需要注意的是,源代码中的括号 () 在 AST 中被 “舍弃” 了 —— 因为括号的作用(改变运算优先级)已经通过节点的层级关系体现出来了:z 和 1 都是 – 节点的子节点,这就对应了源代码中的 (z – 1)。
想深入了解抽象语法树 (AST) 吗?AST Explorer 可以帮助你可视化 Go、Python、Java 和 JavaScript 等几种流行语言的 AST。
再来看一个简单的 Python 代码示例:
| 1 | my_str = "code more!" | 
这段代码对应的 AST(以 JSON 格式简化展示)如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | {   "type": "Program",   "loc": {     "start": {       "line": 1,       "column": 0     },     "end": {       "line": 1,       "column": 21     }   },   "range": [     0,     21   ],   "body": [     {       "type": "VariableDeclaration",       "loc": {         "start": {           "line": 1,           "column": 0         },         "end": {           "line": 1,           "column": 6         }       },       "range": [         0,         6       ],       "kind": "var",       "declarations": [         {           "type": "VariableDeclarator",           "loc": {             "start": {               "line": 1,               "column": 0             },             "end": {               "line": 1,               "column": 6             }           },           "range": [             0,             6           ],           "id": {             "type": "Identifier",             "loc": {               "start": {                 "line": 1,                 "column": 0               },               "end": {                 "line": 1,                 "column": 6               }             },             "range": [               0,               6             ],             "name": "my_str"           },           "init": {             "type": "Literal",             "loc": {               "start": {                 "line": 1,                 "column": 9               },               "end": {                 "line": 1,                 "column": 21               }             },             "range": [               9,               21             ],             "value": "code more!",             "raw": "\"code more!\""           }         }       ]     }   ] } | 
在这个 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 如何为静态代码分析提供支持,帮助开发者提升代码质量。






