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以後,後續也能夠拓展不少有趣的功能如代碼轉換,代碼風格檢測,代碼自動格式化,代碼壓縮。目前我還不是太明白,之後能夠嘗試實現一下。
(完)