Stories

Detail Return Return

AST語法樹增刪改查 - Stories Detail

AST 是 Abstract Syntax Tree 的縮寫,即 “抽象語法樹”.它是以樹狀的形式表現編程語言的語法結構. webpack 打包 JS 代碼的時候,webpack 會在我們的原有代碼基礎上新增一些代碼, 例如我們可以在打包JS 代碼的時候將高級代碼轉為低級代碼,就是通過 AST 語法樹來完成的
AST在線生成地址
babel插件查看使用地址

AST生成過程由源碼->詞法分析->語法分析->抽象語法樹
例如
let a = 1 + 2
詞法分析

  • 從左至右一個字符一個字符地讀入源程序, 從中識別出一個一個 “單詞”“符號”等 讀出來的就是普通的字符,沒有任何編程語言的函數
  • 將分析之後結果保存在一個詞法單元數組中
單詞 單詞 符號 數字 符號 數字
let a = 1 + 2
[
  {"type": "word", value: "let"},
  {"type": "word", value: "a"},
  {"type": "Punctuator", value: "="},
  {"type": "Numberic", value: "1"},
  {"type": "Punctuator", value: "+"},
  {"type": "Numberic", value: "2"},
]

之後進入詞法分析
語法分析
將單詞序列組合成各類的語法短語

關鍵字 標識符 賦值運算符 字面量 二元運算符 字面量
let a = 1 + 2
[{
  "type": "VariableDecLaration", 
  "content": {
    {"type": "kind", "value": "let"},  // kind 表示是什麼類型的聲明
    {"type": "Identifier", "value": "a"},  // Identifier 表示是標識符
    {"type": "init", "value": "="},  // 表示初始值的表達式
    {"type": "Literal", "value": "1"},  // Literal 表示是一個字面量
    {"type": "operator", "value": "+"},  // operator 表示是一個二元運算符
    {"type": "Literal", "value": "2"},
  } 
}]

抽象語法樹

 "program": {
    "type": "Program",
    "start": 0,
    "end": 13,
    "loc": {
      "start": {
        "line": 1,
        "column": 0
      },
      "end": {
        "line": 1,
        "column": 13
      }
    },
    "sourceType": "module",
    "interpreter": null,
    "body": [
      {
        "type": "VariableDeclaration",
        "start": 0,
        "end": 13,
        "loc": {
          "start": {
            "line": 1,
            "column": 0
          },
          "end": {
            "line": 1,
            "column": 13
          }
        },
        "declarations": [  // 這裏是數組,表示可以同時聲明多個變量
          {
            "type": "VariableDeclarator",
            "start": 4,
            "end": 13,
            "loc": {
              "start": {
                "line": 1,
                "column": 4
              },
              "end": {
                "line": 1,
                "column": 13
              }
            },
            "id": {
              "type": "Identifier",
              "start": 4,
              "end": 5,
              "loc": {
                "start": {
                  "line": 1,
                  "column": 4
                },
                "end": {
                  "line": 1,
                  "column": 5
                },
                "identifierName": "a"
              },
              "name": "a"
            },
            "init": {
              "type": "BinaryExpression",
              "start": 8,
              "end": 13,
              "loc": {
                "start": {
                  "line": 1,
                  "column": 8
                },
                "end": {
                  "line": 1,
                  "column": 13
                }
              },
              "left": {
                "type": "NumericLiteral",
                "start": 8,
                "end": 9,
                "loc": {
                  "start": {
                    "line": 1,
                    "column": 8
                  },
                  "end": {
                    "line": 1,
                    "column": 9
                  }
                },
                "extra": {
                  "rawValue": 1,
                  "raw": "1"
                },
                "value": 1
              },
              "operator": "+",
              "right": {
                "type": "NumericLiteral",
                "start": 12,
                "end": 13,
                "loc": {
                  "start": {
                    "line": 1,
                    "column": 12
                  },
                  "end": {
                    "line": 1,
                    "column": 13
                  }
                },
                "extra": {
                  "rawValue": 2,
                  "raw": "2"
                },
                "value": 2
              }
            }
          }
        ],
        "kind": "let"
      }
    ],
    "directives": []
  }

程序示例

package.json依賴

    "@babel/generator": "^7.11.6",  // 轉換AST結構
    "@babel/parser": "^7.11.5",  // 拆解AST樹結構
    "@babel/traverse": "^7.11.5", 
    "@babel/types": "^7.11.5",

@babel/parse生成 AST

import * as parser from '@babel/parser'

const code = `let a = 1 + 2`
const ast = parser.parse(code)
console.log(ast)

修改語法樹

通過 babel 的 traverse 模塊遍歷節點,找到對應節點後,通過 babel 中的 generator 模塊來轉換語法樹為代碼

import * as parser from '@babel/parser'
import traverse from "@babel/traverse"
import generator from '@babel/generator'

const code = `let val = 1 + 2`
const ast = parser.parse(code)
console.log(ast)

// traverse 方法可以遍歷所有的語法樹結點
traverse(ast, {
  enter(path) {  // 這個path會找到所有的node
    if (path.node.type == 'Identifier') {
      path.node.name = 'modify'
      path.stop()
    }
  }
})

const ret = generator(ast)
console.log(ret)

這裏會把val修改為modify

創建語法樹

通過@babel/types 模塊創建語法樹節點然後 push 到 body 中就可以實現語法樹的手動創建,
image.png

import * as parser from '@babel/parser'
import traverse from "@babel/traverse"
import generator from '@babel/generator'
import * as t from '@babel/types'

let code = ``
let ast = parser.parse(code)

let left = t.NumericLiteral(1)
let right = t.NumericLiteral(2)
let init = t.binaryExpression("+", left, right)

let id = t.identifier("add")
let variable = t.variableDeclarator(id, init)
let declaration = t.variableDeclaration('let', [variable])
ast.program.body.push(declaration)


// 轉換為code 
let genCode = generator(ast)
console.log(genCode.code)
刪除語法樹節點

想要刪除語法節點的核心就是先遍歷找到所有的節點,通過 @babel/traverse 來完成, 找到每個節點之後就可以通過具體的方法來完成增刪改查操作

  • NodePath 常用的屬性

    • node: 獲取當前節點
    • parent : 父節點
    • parentPath :父path
    • scope: 作用域
    • context : 上下文
  • NodePath 常用的方法

    • get: 當前節點
    • findParent:向父節點搜尋節點
    • getSibling: 獲取兄弟節點
    • replaceWith: 用 AST 節點替換該節點
    • replaceWithMultiple :用多個 AST 節點替換該節點
    • insertBefore: 在節點前插入節點
    • insertAfter: 在節點後插入節點
    • remove: 刪除節點
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default  
const generator = require('@babel/generator').default

let code = `
  console.log('jake')
  let sum = 1 + 2 
  let minus = 2 - 1
  console.log("tom")
`
let ast = parser.parse(code)
console.log(ast)

// traverse 遍歷語法樹的
traverse(ast, {
  Identifier(path) {
    if (path.node.name == 'sum') {
      path.parentPath.remove()
    }
  }
})

console.log(generator(ast))

刪除了sum之後
image.png

user avatar alibabawenyujishu Avatar xiaoxxuejishu Avatar youyoufei Avatar licin Avatar abc-x Avatar geeklab Avatar best-doraemon Avatar bygpt Avatar kuailedehuanggua Avatar smallhuifei Avatar tianhenmei Avatar luxigaola Avatar
Favorites 15 users favorite the story!
Favorites

Add a new Comments

Some HTML is okay.