第二章 - 抽象语法树(AST)概述
关于抽象语法树及其使用方法的介绍
如果想编写能生成代码或分析代码的程序,首先需要理解什么是抽象语法树。AST这个术语听起来可能很吓人,但其实它和HTML的DOM树一样简单。下面我们就用DOM树作为例子,说明树形结构的基本概念。
HTML DOM示例
假设我们有一个基础网页,包含标题和两个带子标题的内容区块。其HTML代码可能如下:
css
<body>
<main>
<h1>重要标题</h1>
<div>
<h2>子标题1</h2>
<p>附加信息</p>
</div>
<div>
<h2>子标题2</h2>
<p>其他信息</p>
</div>
</main>
</body>
对应的树形结构表示如下:
css
body
|-> main
|-> h1
|-> div
|-> h2
|-> p
|-> div
|-> h2
|-> p
这表示body
节点有一个子节点main
。main
包含三个子节点:h1
和两个div
。每个div
又包含h2
和p
子节点。这种树结构是源代码的元数据表示,即使省略具体文本内容,也能完整还原代码结构。
HTML的AST有一些规则限制,比如"p
节点不能是另一个p
节点的子节点"。这类规则定义了节点间的父子关系限制。在其他编程语言中,这类规则会更加重要。
TypeScript/JavaScript等语言也有类似的树形结构,通过AST可以重新生成原始代码。
JavaScript示例
来看一个TypeScript相关的例子,分析下面这个简单的if
语句:
erlang
if (6 === 7) {
console.log("wow")
}
对应的AST结构如下:
rust
IfStatement
|-> BinaryExpression
|-> NumericLiteral
|-> EqualsEqualsEqualsToken
|-> NumericLiteral
|-> Block
|-> ExpressionStatement
|-> CallExpression
|-> PropertyAccessExpression
|-> Identifier
|-> Identifier
|-> StringLiteral
这个结构看起来复杂,让我们逐步解析:
最顶层的IfStatement
包含两个子节点:
BinaryExpression
表示条件判断部分Block
表示花括号{}
内的代码块
BinaryExpression
包含三个部分:
- 左右两边的数字字面量
6
和7
(NumericLiteral
) - 中间的严格相等运算符
===
(EqualsEqualsEqualsToken
)
Block
节点包含一个ExpressionStatement
,即console.log("wow")
。这个表达式语句又包含:
-
CallExpression
表示函数调用-
PropertyAccessExpression
表示属性访问console.log
- 两个
Identifier
分别是console
和log
- 两个
-
StringLiteral
是字符串参数"wow"
-
通过这个例子,我们完整解析了if
语句的AST结构。接下来介绍TypeScript中常见的AST节点类型。
TypeScript AST节点类型
下面介绍TypeScript代码中最常用的节点类型。这不是完整列表,但掌握这些核心类型就能理解大部分AST结构。
注意:本节内容偏技术性,后续章节不会这么枯燥。但这些基础知识很重要。
字面量(Literals)
字面量是最直观的节点类型,表示源代码中的具体值。例如:
javascript
const age = 102 // NumericLiteral
const name = "Jason" // StringLiteral
const regex = /hello/ // RegularExpressionLiteral
其他字面量示例:
csharp
const a = 1n // BigIntLiteral
const b = true // BooleanLiteral
const c = null // NullLiteral
const d = {} // ObjectLiteralExpression
const e = [] // ArrayLiteralExpression
表达式(Expressions)
表达式是能产生值或引起副作用的代码片段。常见类型包括:
scss
hello() // CallExpression
process.env // PropertyAccessExpression
1 + 2 // BinaryExpression
() => {} // ArrowFunctionExpression
await hello() // AwaitExpression
this // ThisExpression
语句(Statements)
语句表示执行动作的代码单元。与表达式的区别在于:大多数关键字都属于语句,如if
、for
循环等。所有表达式都可以作为语句,但反之不成立。
示例:
scss
const a = 1 // VariableStatement
if(5>9){} // IfStatement
for(let i in [1,2,3]){} // ForInStatement
return // ReturnStatement
声明(Declarations)
声明用于引入新类型或值:
javascript
import a from "./a" // ImportDeclaration
class Human {} // ClassDeclaration
function d() {} // FunctionDeclaration
export {} // ExportDeclaration
注意:
const a = 1
整体是VariableStatement
,但其中的a=1
部分是VariableDeclaration
。
标识符(Identifiers)
标识符是其他节点的名称:
csharp
function hello() {} // hello是标识符
class Animal {} // Animal是标识符
const myVar = 7 // myVar是标识符
令牌(Tokens)
令牌表示特殊符号:
arduino
= // EqualsToken
=== // EqualsEqualsEqualsToken
... // DotDotDotToken
++ // PlusPlusToken
子句(Clauses)
子句用于提升代码可读性:
scala
try {} catch {} // catch是子句
switch中的case/default // 也是子句
class A extends B {} // extends B是HeritageClause
关键字(Keywords)
关键字是语言保留的特殊单词:
vbnet
async
await
const
class
interface
类型引用(References)
TypeScript特有,用于类型标注:
java
type Age = {}
function myFunc(age: Age) {} // :Age是TypeReference
总结
本章概述了TypeScript抽象语法树的核心概念。我们学习了AST的各种节点类型,为后续的代码分析和转换打下了基础。