babel源碼分析之一:AST生成

前言

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

一個通用完整的statement遞歸函數邏輯:

  • 去除註釋,空格,換行。
  • 詞法分析: 將源碼中的字符轉化爲單詞。即解析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);
    }
 ...

部分Statement, Experssion內部也有本身的遞歸邏輯:json

Statement內部遞歸

VariableDeclaration

以逗號分隔的遞歸如var a,b,cbabel

FunctionDeclaration

參數內部以逗號分隔的行參遞歸,大括號內部的以分號分割的statement遞歸,如function a(b,c,d){e;f;g;}ide

BlockStatement

大括號內部的以分號分割的statement遞歸,直到到遇到大括號結束符,如{e;f;g}函數

IfStatement

以else關鍵字的遞歸,如if(a){}else if(b){}else{}

SwitchStatement

以 case以及default關鍵字的遞歸,如switch(a){case a:xxx;caseb :xxx; default: xxx;}

ForStatement,ForInStatement,WhileStatement,DoWhileStatement,TryStatement,LabeledStatement

都是大括號內部的以分號分割的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;}

Experssion內部遞歸

ObjectExpression

以逗號分隔分割的遞歸,直到遇到大括號結束符。如{a,b,c,}

ArrayExpression

以逗號分隔分割的遞歸,直到遇到中括號結束符。如[a,b,c,]

FunctionExpression

和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

具體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

後話

實現了AST以後,後續也能夠拓展不少有趣的功能如代碼轉換,代碼風格檢測,代碼自動格式化,代碼壓縮。目前我還不是太明白,之後能夠嘗試實現一下。

(完)

相關文章
相關標籤/搜索