babel是javaScript编译器,主要用于将ECMAScript2015+版本的代码转化为具备兼容性的较低版本,从而让代码适用于各类环境。
它的早期代码从acorn项目中fork出来,后续提供了acorn不具有的一整套的代码解析,转换,生成的功能。javascript
如今的babel有着抽象的工程实现很难直接啃源码。笔者打算经过一系列早期功能点的实现来慢慢揭开其内部机制。babel代码转换第一步是将源码转化成AST(Abstract Syntax Tree)。
本文将借助babel/acorn初期的源码,详细讲解其生成AST的工程细节。java
阅读本文前,但愿您对AST的概念如Statement,Expression有一些了解。ESTree 规范文档传送门node
先举个代码转为ast的例子:git
/* 测试whileStatement */ while(b !== 0){ if (a > b){ a = a - b; }else{ b = b - a; } } console.log(a)
转化后的ast结构github
上图的整个树的生成都是由一次次词法,语法解析中递归出来的。express
... while (tokType !== _eof) { const statement = parseStatement(); if (first) { first = false; if (isUseStrict(statement)) { setStrict(true); } } node.body.push(statement); } ...
部分Statement, Experssion内部也有本身的递归逻辑:json
以逗号分隔的递归如var a,b,c
。babel
参数内部以逗号分隔的行参递归,大括号内部的以分号分割的statement递归,如function a(b,c,d){e;f;g;}
。ide
大括号内部的以分号分割的statement递归,直到到遇到大括号结束符,如{e;f;g}
。函数
以else关键字的递归,如if(a){}else if(b){}else{}
。
以 case以及default关键字的递归,如switch(a){case a:xxx;caseb :xxx; default: xxx;}
。
都是大括号内部的以分号分割的statement递归,大括号结束符,如
for(;;){e;f;g;} for (var a in b){e;f;g} while(a){e;f;g} do{e;f;g;}while(a) try{e;f;g}catch(a){e;f;g}finally{e;f;g;}
以逗号分隔分割的递归,直到遇到大括号结束符。如{a,b,c,}
以逗号分隔分割的递归,直到遇到中括号结束符。如[a,b,c,]
和FunctionDeclaration同样,参数内部以逗号分隔的行参递归,大括号内部的以分号分割的statement递归,如var a=function (b,c,d){e;f;g;}
。
单个通用的递归函数的实现的功能以下
注释分两种:
/ /,或者 / * /的多行注释以及//的单行注释。
空格分为' ' 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有很是多,可是按类型分的话,能够分为如下6种:
operator运算符类型。如+ - * % | & = 等符号。对于不一样符号在一块儿解析的时候,会有不一样的解析优先级。
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) } }
除了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": [] } }
这个模块我的认为是最核心的模块,对不一样表达式进行解析。
最基本的表达式如: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
实现了AST以后,后续也能够拓展不少有趣的功能如代码转换,代码风格检测,代码自动格式化,代码压缩。目前我还不是太明白,之后能够尝试实现一下。
(完)