Babel 内部原理系列-AST 的构建过程

Babylon

Babylon用于词法与语法分析的 JS 解析器,后并入 babel 中,由babel-parser维护。

Abstract Syntax Tree

中文意思是抽象语法树,简称AST,是源代码语法结构的一种抽象表示(来自维基)。比如下面的简代码:

1
2
3
4
function add(a, b) {
return a + b;
}
add(1, 2);

经过编译(词法与语法分析)之后,会生成下面的语法树:

展开查看

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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
{
"type": "File",
"start": 0,
"end": 37,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"program": {
"type": "Program",
"start": 0,
"end": 37,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"sourceType": "module",
"body": [
{
"type": "FunctionDeclaration",
"start": 0,
"end": 37,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"id": {
"type": "Identifier",
"start": 9,
"end": 12,
"loc": {
"start": {
"line": 1,
"column": 9
},
"end": {
"line": 1,
"column": 12
}
},
"name": "add"
},
"generator": false,
"expression": false,
"async": false,
"params": [
{
"type": "Identifier",
"start": 13,
"end": 14,
"loc": {
"start": {
"line": 1,
"column": 13
},
"end": {
"line": 1,
"column": 14
}
},
"name": "a"
},
{
"type": "Identifier",
"start": 16,
"end": 17,
"loc": {
"start": {
"line": 1,
"column": 16
},
"end": {
"line": 1,
"column": 17
}
},
"name": "b"
}
],
"body": {
"type": "BlockStatement",
"start": 19,
"end": 37,
"loc": {
"start": {
"line": 1,
"column": 19
},
"end": {
"line": 3,
"column": 1
}
},
"body": [
{
"type": "ReturnStatement",
"start": 22,
"end": 35,
"loc": {
"start": {
"line": 2,
"column": 1
},
"end": {
"line": 2,
"column": 14
}
},
"argument": {
"type": "BinaryExpression",
"start": 29,
"end": 34,
"loc": {
"start": {
"line": 2,
"column": 8
},
"end": {
"line": 2,
"column": 13
}
},
"left": {
"type": "Identifier",
"start": 29,
"end": 30,
"loc": {
"start": {
"line": 2,
"column": 8
},
"end": {
"line": 2,
"column": 9
}
},
"name": "a"
},
"operator": "+",
"right": {
"type": "Identifier",
"start": 33,
"end": 34,
"loc": {
"start": {
"line": 2,
"column": 12
},
"end": {
"line": 2,
"column": 13
}
},
"name": "b"
}
}
}
]
}
}
]
},
"comments": [],
"tokens": [...<分词信息>]
}

词法分析

基于编译原理的思想,源代码编译的第一步就是词法分析,形象点可以称为扫词:所谓的词法分析就是将源代码解析出最小单位,也就是Token,而在babel-parser中,负责词法分析工作的是Tokenizer,具体一点是nextToken()这个函数。比如上面的代码,词法分析阶段会经过以下步骤:

  1. 开始解析时,会默认调用nextToken(),此时生成一个Token,包含state信息(比如读词位置)。
  2. 扫词:开始时state.pos0,在调用getTokenFromCode(),根据 unicode 匹配当前字符,处理各种情况。比如上面例子里的functionf的 unicode 编码102,因此会执行readWord(),会根据当前的位置信息:state.pos,while 循环下一个字符,直到组词完成,每次循环pos+1
  3. 调用finishToken(),闭合 Token。
  4. 之后都是通过next()生成 Token,遇到空格换行字符则跳过,同时pos + 1

语法分析

有了 Token 之后,就可以进入语法分析阶段了,如果语法分析过程中遇到问题,就会抛出那个我们很熟悉的错误,比如输入var =,我们知道变量声明后面必须跟变量名,而=明显是不合法的:

1
Uncaught SyntaxError: Unexpected token ;

而在babel-parser中,语法分析的入口函数是parseBlockOrModuleBlockBody,语法解析会经过以下步骤:

  • 块级解析:从函数命名可以知道这是用于解析块级或模块的,那么如何划分各个块级呢。对于文件而言,边界标识是eof(end of file),而普通块级的边界则是},解析到这两个 Token 时,eat就会跳出循环停止解析,在遇到这两个标识前,babe-parser根据各个表达式进入parseStatement()阶段。
  • parseStatement 解析:在源码开始解析时,默认调用了一次nextToken()生成了关键字,然后根据关键字去处理各种parser***Statement的情况,举个例子,函数声明:var a = 1;var是变量声明的关键字,因此会进入parseVarStatement()解析。
  • parseVarStatement 解析:调用next()生成下一个 token,并进入parseVar解析,首先会先检查标识符(Identifier)是否合法,不合法则抛出异常,如果标识符之后跟着=,则进行赋值操作parseMaybeAssign(),再以VariableDeclaration的类型,调用finishNode()闭合 Token。

以下是在分析源码时画了一个草图:

[本文谢绝转载,谢谢]

粤ICP备2022084378号