AST抽象語法樹

做爲一個前端同窗,無論你是否知道AST是個什麼東西,但一點也不影響你在工做中使用它。咱們平時項目中用到的lessbabeleslint、代碼壓縮以及JavaScript代碼可以運行在瀏覽器中等,都是創建在AST的基礎上。在瞭解了AST相關知識後,你也能夠本身折騰點東西出來,給單調無聊的工做找點樂趣。前端

什麼是AST

AST(Abstract Syntax Tree),中文叫作抽象語法樹,是源代碼語法結構的一種抽象表示。它以樹狀的形式表現編程語言的語法結構,樹上的每一個節點都表示源代碼中的一種結構。之因此說語法"抽象"的,是由於這裏的語法並不會表示出真實語法中出現的每一個細節。好比,嵌套括號被隱含在樹的結構中,並無以節點的形式呈現;而相似於if-condition-then這樣的條件跳轉語句,能夠使用帶有三個分支的節點來表示。(以上概念來自維基百科)。node

JavaScript AST轉換工具

對於JavaScript而言,能夠經過JS ParserJS代碼轉換成AST。目前比較常見的JS Parser以下:jquery

  • esprima(流行庫)
  • Babylon(babel中使用)
  • acorn(webpack中使用)
  • espree(在acorn基礎上衍生而來,eslint中使用)
  • astexplorer(在線生成工具,可選不一樣的JS Parser實時查看)

本文中的例子均是使用esprima來實現。webpack

如何將代碼轉換成AST

在將代碼轉換成AST的過程當中,有兩個重要的階段:詞法分析(Lexical Analysis)語法分析(Syntax Analysis)git

詞法分析

也稱爲分詞,是將字符串形式的代碼轉換爲標記(token)序列的過程。這裏的token是一個字符串,是構成源代碼的最小單位,相似於英語中單詞。詞法分析也能夠理解成將英文字母組合成單詞的過程。詞法分析過程當中不會關心單詞之間的關係。好比:詞法分析過程當中可以將括號標記成token,但並不會校驗括號是否匹配。github

JavaScript中的token主要包含如下幾種:web

關鍵字:var、let、const等編程

標識符:沒有被引號括起來的連續字符,多是一個變量,也多是 if、else 這些關鍵字,又或者是 true、false 這些內置常量瀏覽器

運算符: +、-、 *、/ 等babel

數字:像十六進制,十進制,八進制以及科學表達式等

字符串:變量的值等

空格:連續的空格,換行,縮進等

註釋:行註釋或塊註釋都是一個不可拆分的最小語法單元

標點:大括號、小括號、分號、冒號等

如下是const a = 'hello world'通過esprima詞法分析後生成的tokens

[
    {
        "type": "Keyword",
        "value": "const"
    },
    {
        "type": "Identifier",
        "value": "a"
    },
    {
        "type": "Punctuator",
        "value": "="
    },
    {
        "type": "String",
        "value": "'hello world'"
    }
]
語法分析

也稱爲解析器,是將詞法分析產生的token按照某種給定的形式文法轉換成AST的過程。也就是把單詞組合成句子的過程。在轉換過程當中會驗證語法,語法若是有錯的話,會拋出語法錯誤。

上述const a = 'hello world'通過語法分析後生成的AST以下:

{
  "type": "Program",
  "body": [
    {
      "type": "VariableDeclaration",
      "declarations": [
        {
          "type": "VariableDeclarator",
          "id": {
            "type": "Identifier",
            "name": "a"
          },
          "init": {
            "type": "Literal",
            "value": "hello world",
            "raw": "'hello world'"
          }
        }
      ],
      "kind": "const"
    }
  ],
  "sourceType": "script"
}

在拿到了AST後,咱們就能夠分析AST,在此基礎上作一些本身的事情。好比最簡單的將代碼中的某一變量都替換成另外一個名字。

實踐

下面咱們來實現將上述代碼中定義的變量a替換成變量b。要實現這個需求,咱們須要將源代碼轉換成AST,而後在此基礎上進行一些操做,更改樹的內容,以後再把AST轉換成目標代碼。也就是要經歷 解析 -> 轉換 -> 生成 的過程。

首先咱們須要分析源代碼生成的AST和目標代碼生成的AST具體有何不一樣。
如下是const b = 'hello world'生成的AST:

{
  "type": "Program",
  "body": [
    {
      "type": "VariableDeclaration",
      "declarations": [
        {
          "type": "VariableDeclarator",
          "id": {
            "type": "Identifier",
            "name": "b" // 這裏不一樣
          },
          "init": {
            "type": "Literal",
            "value": "hello world",
            "raw": "'hello world'"
          }
        }
      ],
      "kind": "const"
    }
  ],
  "sourceType": "script"
}

經過對比分析,發現惟一的不一樣就是typeIdentifieridname屬性值不同。接下來就能夠經過修改AST來實現咱們的需求了。

咱們須要安裝estraverse(遍歷AST)和escodegen(根據AST生成JS)這兩個包。

const esprima = require('esprima');
const estraverse = require('estraverse');
const escodegen = require('escodegen');

const program = "const a = 'hello world'";
const ASTree = esprima.parseScript(program);

estraverse.traverse(ASTree, {
    enter(node) {
        changeAToB(node);
    }
});

const ASTreeAfterChange = escodegen.generate(tree);
console.log(ASTreeAfterChange); // const b = 'hello world'

function changeAToB(node) {
    if (node.type === 'Identifier') {
        node.name = 'b';
    }
}

看,是否是很容易就能夠實現。掌握了AST的知識後,咱們能作不少事情,各類babel的插件也是這麼產生的,只不過用的庫不同。

如何實現一個babel插件能夠參考官方Babel 插件手冊

參考文章

  1. 【你應該瞭解的】抽象語法樹AST
  2. 平庸前端碼農之蛻變 — AST
  3. Babel 插件手冊
相關文章
相關標籤/搜索