共计 3757 个字符,预计需要花费 10 分钟才能阅读完成。
前言
babel 是 javaScript 编译器, 主要用于将 ECMAScript2015+ 版本的代码转化为具有兼容性的较低版本,从而让代码适用于各种环境。
它的早期代码从 acorn 项目中 fork 出来,后续提供了 acorn 不具备的一整套的代码解析,转换,生成的功能。
babel 代码转换第一步是将源码转化成 AST(Abstract Syntax Tree)。
本文将借助 acorn 初期的源码以及 ESTree 规范标准, 详细讲解其生成 AST 的工程细节。
功能分析
先举个代码转为 ast 的例子:
/*
测试 whileStatement
*/
while(b !== 0){if (a > b){a = a - b;}else{b = b - a;}
}
console.log(a)
转化后的 ast 结构
实现思路
上图的整个树的生成都是由一次次词法,语法解析中递归出来的。一个完整的递归函数逻辑如下:
- 去除注释, 空格, 换行。
- 词法分析: 将源码中的字符转化为单词。即解析 token,如 while(b !== 0){将被识别为的 [while,(,b,!==,0,),{] 这 7 个单词。
- 语法分析:通过词法解析出来的单词 token 来获取 statement 节点的类型并解析,然后对其中可能含有的 expression 进行相应的语法解析。解析出其开始的 start,结束的 end,值 value 以及值的类型 label。
- 索引移到下一位,开启新一轮的递归。以此循环直到将文件字符串读取完毕。
...
while (tokType !== _eof) {const statement = parseStatement();
if (first) {
first = false;
if (isUseStrict(statement)) {setStrict(true);
}
}
node.body.push(statement);
}
...
功能实现
单个递归函数的实现的功能如下
去除注释, 空格, 换行
注释分两种:
/ /, 或者 / * / 的多行注释以及 // 的单行注释。
空格分为 ’ ‘ t n r f
function skipSpace() {while (tokenPos < inputLen) {const ch = input.charAt(tokenPos);
if (ch === '/') {if (input.charAt(tokenPos + 1) === '/') {
tokenPos += 2;
while (tokenPos < inputLen && !newline.test(input.charAt(tokenPos))) {tokenPos++;}
} else if (input.charAt(tokenPos + 1) === '*') {const i = input.indexOf('*/', tokenPos + 2);
if (i < 0) {raise(tokenPos - 2, 'Unterminated comment');
}
tokenPos = i + 2;
} else {++tokenPos;}
} else if (ch === '\n' || ch === '\t' || ch === "" || ch ==="\r"|| ch ==="\f") {++tokenPos;} else {break;}
}
}
解析 token
具体 token 有非常多,但是按类型分的话,可以分为以下 6 种:
- string 字符串类型。以 ’ ” 开头,且以 ’ “ 结尾。
- regexp 正则类型。以 / 开头,且以 / 结尾
- word 单词类型。关键字如 break case catch continue debugger。保留关键字如 implements interface let package private 以及普通的变量名称。
- number 数字类型。类型有:二进制,八进制,十进制和十六进制。其中 0x 或者 0X 开头的是十六进制。
- punctuation 标点符号类型。如[{ ( , ; ? 等符号。
-
operator 运算符类型。如 + – * % | & = 等符号。对于不同符号在一起解析的时候,会有不同的解析优先级。
- 优先级最高为 10: – * % /
- 优先级为 9:+ –
- 优先级为 8: >> >>> << <<<
- 优先级为 7: > >= < <=
- 优先级为 6: === == !== !===
- 优先级为 5: &
- 优先级为 4: ^
- 优先级为 3: |
- 优先级为 2: &&
- 优先级为 1: ||
function readToken() {
lastStart = tokStart;
lastEnd = tokEnd;
tokStart = tokenPos;
const ch = input.charAt(tokenPos);
if (tokenPos >= inputLen) {return finishToken(_eof);
}
if (ch === '\'' || ch === '"') {readString(ch);
} else if (indentifierReg.test(ch)) {readWord();
} else if (digest.test(ch)) {readNumber();
} else if (puncChars.test(ch)) {
tokenPos++;
finishToken(puncTypes[ch]);
} else if (operatorChar.test(ch)) {readOperator(ch)
}
}
解析节点 Statements
除了 BlockStatement, 其他 statement 是以; 或者换行符结束。
每种 statement 都由自己不同的解析方式以及名称。一个 statement 可能含有 0 个或者多个 expression。
如 while 类型的 statement 解析函数如下
function parseWhile() {const node = startNode();
next();
node.test = parseParenExpression();
node.body = parseBlock();
return finishNode(node, 'WhileStatement');
}
解析后的简单的 json 类型为
{
"type": "WhileStatement",
"test": {
"type": "AssignmentExpression",
"operator": "=",
"left": {
"type": "Identifier",
"name": "a"
},
"right": {
"type": "Identifier",
"name": "b"
}
},
"body": {
"type": "BlockStatement",
"body": []}
}
解析 expression
这个模块个人认为是最核心的模块,对不同表达式进行解析。
最基本的表达式如:Identifier,Literal,FunctionExpression,ObjectExpression,ArrayExpression,NewExpression。
建立在基本表达式之上的如:a.b 的 MemberExpression,a()的 CallExpression。
++a,a– 之类的 UpdateExpression。
!a,!!a 之类的 UnaryExpression。
a||b,a&&b 的 LogicalExpression,a- b 之类的 BinaryExpression。
a= b 之类的 AssignmentExpression。
a?b:c 之类的 ConditionalExpression。
上面这些复杂类型的解析执行顺序如下:
举个复杂的例子:
var a=!b++?c+1:d.e(f,g);
解析之后的 json 格式如下
{
"type": "VariableDeclaration",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "a"
},
"init": {
"type": "ConditionalExpression",
"test": {
"type": "UnaryExpression",
"operator": "!",
"prefix": true,
"argument": {
"type": "UpdateExpression",
"operator": "++",
"prefix": false,
"argument": {
"type": "Identifier",
"name": "b"
}
}
},
"consequent": {
"type": "BinaryExpression",
"left": {
"type": "Identifier",
"name": "c"
},
"operator": "+",
"right": {
"type": "Literal",
"value": 1,
"raw": "1"
}
},
"alternate": {
"type": "CallExpression",
"callee": {
"type": "MemberExpression",
"object": {
"type": "Identifier",
"name": "d"
},
"property": {
"type": "Identifier",
"name": "e"
},
"computed": false
},
"arguments": [
{
"type": "Identifier",
"name": "f"
},
{
"type": "Identifier",
"name": "g"
}
]
}
}
}
],
"kind": "var"
}
代码实现
本人的简易版 babel 实现 simple-babel
(完)