deno实现一个js语法解析器

本文最后更新于:a few seconds ago

前言

javascript是解释型语言,在v8中,源代码需要经过词法和语法分析,转化为AST, 再进行语义分析,最后转化成字节码执行。

语法分析成AST可以在这里体验:https://esprima.org/demo/parse.html

本次deno 实现的源码在这里获取:deno-ast,开源学习随意复制。

Parser(解析器)

Parser分为Tokenizer(分词器),和parser(解析器)。

Tokenizer

分词器的最终目的是帮助我们将代码进行词法分析,分割成一个一个的小token,方便我们的parser去解析成AST.

我们先来测试一下我们tokenizer的效果。

把源码clone下来之后,运行:

1
deno test --filter "tokenizerTest"

tokenizer

这些就是我们生产的token,有了这些token,我们的parser才能转化为AST。
tokenizer最核心的就是正则表达式,本质上就是用正则表达式去提取token。具体的正则定义可以查看Tokenizer.ts文件。

Parser

接下来继续看parser,里面主要的内容就是下面两个

  • BNF(巴科斯范式)
  • Finite-state machine(有限状态机)

巴科斯范式

巴科斯范式我个人的理解是一种使用递归思想来描述计算机语言的定义规范。
想进一步学习可以查看bnf等相关内容。
不过,个人觉得最值得学习的还是编译器的优化,这比较重要。

我们来看一个代码的结构声明。代码位于parser

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* ReturnStatement
* : 'return' OptExpression ';'
*/
ReturnStatement() {
this._eat('return'); // 吃掉当前的token
const argument =
this.currentToken.type !== ';' ? this.Expression() : null; // 这里的token已经是下一个的了
this._eat(';');

return {
type: 'ReturnStatement',
argument,
};
}

上面就是对return的一个声明,表示这个ReturnStatement需要以下结构:

: ‘return’ OptExpression ‘;’
一个return关键字,加上可选择的表达(Expression在代码下面有进行结构声明),然后结尾一个分号表示结束。

而一些Statement状态的转移主要是依赖我们的Statement函数:

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
/**
* Statement
* : ExpressionStatement
* | BlockStatement
* | EmptyStatement
* | VariableStatement
* | IfStatement
* | IterationStatement
* | FunctionDeclaration
* | ClassDeclaration
* | ReturnStatement
* ;
*/
Statement(): Record<string, unknown> | undefined {
switch (this.currentToken.type) {
case ";":
return this.EmptyStatement();
case "if":
return this.IfStatement();
case "{":
return this.BlockStatement();
case "let":
return this.VariableStatement();
case "fun":
return this.FunctionDeclaration();
case "class":
return this.ClassDeclaration();
case "return":
return this.ReturnStatement();
case "while":
case "do":
case "for":
return this.IterationStatement();
default:
return this.ExpressionStatement();
}
}

他帮助我们把tokeninzer生成的token转移到各个具体的声明去,构造我们的抽象语法树。

Finite-state machine(有限状态机)

在我们的parser里,可以理解成一系列状态的转移,每个方法都是一个状态。
每个函数的调用可以看作是一个状态的转移。
比如parser.ts里状态的转移是:

Program -> StatementList -> Statement …

最终的状态都会在Literal结束。

输出AST

我们运行以下测试,就能得到我们要的结果:

1
deno test --filter "parserTest"

这样我们就将代码转换为抽象语法树了。

1
2
3
4
5
6
7
8
9
10
class add {
fun constructor(x, y) {
this.x = x;
this.y = y;
}

fun calc() {
return this.x + this.y;
}
}
AST
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
{
"type": "Program",
"body": [
{
"type": "ClassDeclaration",
"id": {
"type": "Identifier",
"name": "Point"
},
"superClass": null,
"body": {
"type": "BlockStatement",
"body": [
{
"type": "FunctionDeclaration",
"name": {
"type": "Identifier",
"name": "constructor"
},
"params": [
{
"type": "Identifier",
"name": "x"
},
{
"type": "Identifier",
"name": "y"
}
],
"body": {
"type": "BlockStatement",
"body": [
{
"type": "ExpressionStatement",
"expression": {
"type": "AssignmentExpression",
"operator": "=",
"left": {
"type": "MemberExpression",
"computed": false,
"object": {
"type": "ThisExpression"
},
"property": {
"type": "Identifier",
"name": "x"
}
},
"right": {
"type": "Identifier",
"name": "x"
}
}
},
{
"type": "ExpressionStatement",
"expression": {
"type": "AssignmentExpression",
"operator": "=",
"left": {
"type": "MemberExpression",
"computed": false,
"object": {
"type": "ThisExpression"
},
"property": {
"type": "Identifier",
"name": "y"
}
},
"right": {
"type": "Identifier",
"name": "y"
}
}
}
]
}
},
{
"type": "FunctionDeclaration",
"name": {
"type": "Identifier",
"name": "calc"
},
"params": [],
"body": {
"type": "BlockStatement",
"body": [
{
"type": "ReturnStatement",
"argument": {
"type": "BinaryExpression",
"operator": "+",
"left": {
"type": "MemberExpression",
"computed": false,
"object": {
"type": "ThisExpression"
},
"property": {
"type": "Identifier",
"name": "x"
}
},
"right": {
"type": "MemberExpression",
"computed": false,
"object": {
"type": "ThisExpression"
},
"property": {
"type": "Identifier",
"name": "y"
}
}
}
}
]
}
}
]
}
}
]
}

我们可以看到,最外层包裹着Program,而class 的类型为ClassDeclaration
而他的body里又包裹着两个FunctionDeclaration,也就是我们的函数声明fun

结束语

分享一个最近很喜欢的表情包哈哈

哈哈

感谢


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!