摘要:語法解析首先來看一下什么是抽象語法樹。抽象語法樹也稱為語法樹,指的是源代碼語法所對(duì)應(yīng)的樹狀結(jié)構(gòu)。也就是說,對(duì)于一種具體編程語言下的源代碼,通過構(gòu)建語法樹的形式將源代碼中的語句映射到樹中的每一個(gè)節(jié)點(diǎn)上。
前言
最近在分析微信小程序,需要統(tǒng)計(jì)以前代碼中所使用過的wx.api。思路為對(duì)js文件進(jìn)行語法分析,然后找出調(diào)用者為wx的成員表達(dá)式。
JavaScript語法解析首先來看一下什么是抽象語法樹。抽象語法樹(Abstract Syntax Tree)也稱為AST語法樹,指的是源代碼語法所對(duì)應(yīng)的樹狀結(jié)構(gòu)。也就是說,對(duì)于一種具體編程語言下的源代碼,通過構(gòu)建語法樹的形式將源代碼中的語句映射到樹中的每一個(gè)節(jié)點(diǎn)上。
可以通過一個(gè)demo來看一下什么是AST。
js代碼為
var a = 1; function main() { console.log("this is a function"); } main();
這段代碼的AST長(zhǎng)這樣:
可以發(fā)現(xiàn),代碼被映射成了一顆語法樹,有三個(gè)節(jié)點(diǎn),不同語句映射成不同的節(jié)點(diǎn)。那么我們便可以通過操作語法樹來準(zhǔn)確的獲得代碼中的某個(gè)節(jié)點(diǎn),對(duì)代碼進(jìn)行分析等。
EspsrimaEspsrima是一個(gè)較為成熟的JavaScript語法解析開源項(xiàng)目。使用Espsrima對(duì)js代碼進(jìn)行語法解析的步驟如下:
1. 準(zhǔn)備環(huán)境我們使用node來構(gòu)建能夠在命令行中運(yùn)行的js代碼。所以首先確保安裝了node環(huán)境。
然后創(chuàng)建項(xiàng)目目錄。
新建一個(gè)文件夾,比如我新建一個(gè)‘espsrima’的文件夾。
進(jìn)入該文件夾
cd espsrima。
安裝庫(kù)接下來要用到的庫(kù)
npm install esprima --save npm install estraverse --save
在根目錄下新建index.js文件,初始代碼如下
var fs = require("fs"), esprima = require("esprima"); function analyzeCode(code) { // 1 } // 2 if (process.argv.length < 3) { console.log("Usage: index.js file.js"); process.exit(1); } // 3 var filename = process.argv[2]; console.log("Reading " + filename); var code = fs.readFileSync(filename); analyzeCode(code); console.log("Done");2. 解析js代碼并分析數(shù)據(jù)
esprima 將代碼解析為語法樹,Estraverse則用來對(duì)節(jié)點(diǎn)進(jìn)行遍歷。我們?nèi)砸赃@段代碼為例
var a = 1; function main() {} wx.laod();
我們看下這段代碼節(jié)點(diǎn)遍歷的結(jié)果,結(jié)果太長(zhǎng)截圖其中一部分:
從圖中看出,type代表節(jié)點(diǎn)類型,如函數(shù)聲明FunctionDeclaration和函數(shù)調(diào)用 CallExpression。我們的目的是找出所有的wx.xxx的函數(shù),所以我們主要關(guān)注函數(shù)調(diào)用類型。我們來看基本的函數(shù)調(diào)用代碼:
"expression": { "type": "CallExpression", "callee": { "type": "Identifier", "name": "myAwesomeFunction" }, "arguments": [] }
對(duì)函數(shù)調(diào)用而言,即節(jié)點(diǎn)類型為 CallExpression,callee指向被調(diào)用函數(shù)。
我們?cè)賮砜聪滦稳?"wx.xxx"的代碼
CallExpression: { type: "CallExpression", callee: StaticMemberExpression { type: "MemberExpression", computed: false, object: Identifier { type: "Identifier", name: "wx" }, property: Identifier { type: "Identifier", name: "laod" } }, arguments: [] }
可以看到,節(jié)點(diǎn)類型仍然為 CallExpression,callee的類型為 MemberExpression, callee的object name為wx??梢来颂岢鰓x.xxx。
3. 輔助函數(shù)首先需要一個(gè)對(duì)象functionsStats,用來存儲(chǔ)提取的api。其次,需要一個(gè)"對(duì)象去重函數(shù)addStatsEntry".最后需要一個(gè)函數(shù)對(duì)functionsStats進(jìn)行處理,將結(jié)果寫到本地。 經(jīng)過以上的分析,最終的代碼如下:
var fs = require("fs"), esprima = require("esprima"), estraverse = require("estraverse"); function addStatsEntry(funcName) { if (!functionsStats[funcName]) { functionsStats[funcName] = { calls: 0 }; } } // 寫文件 function writeResult(str) { fs.writeFileSync("result.txt", str, "utf8", (err) => { if (err) throw err; console.log("It"s saved!"); }); } // 結(jié)果處理函數(shù) function processResults(results) { var str = ""; for (var name in results) { if (results.hasOwnProperty(name)) { var stats = results[name]; var apiName = "wx."+ name; console.log("name:", apiName); str += apiName+ " "; } } writeResult(str); } function analyzeCode(code) { var ast = esprima.parse(code); var functionsStats = {}; //1 estraverse.traverse(ast, { enter: function (node) { if (node.type === "CallExpression" && node.callee.type === "MemberExpression") { if(node.callee.object.name === "wx") { addStatsEntry(node.callee.property.name); functionsStats[node.callee.property.name].calls++; } } } }); processResults(functionsStats); } if (process.argv.length < 3) { console.log("Usage: index.js file.js"); process.exit(1); } var filename = process.argv[2]; console.log("Reading " + filename); var code = fs.readFileSync(filename, { encoding: "utf8" }); analyzeCode(code);
可以看到類似以下的處理結(jié)果
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/92998.html
摘要:引擎負(fù)責(zé)整個(gè)程序的編譯和執(zhí)行過程編譯器負(fù)責(zé)語法分析和代碼生成作用域收集和維護(hù)一系列查詢由所有聲明的標(biāo)識(shí)符組成例子聲明一個(gè)變量并賦值編譯器對(duì)該程序段分解成詞法單元編譯器對(duì)以上的詞法單元解析成一個(gè)樹結(jié)構(gòu)抽象語法樹的語法解析器提供了一個(gè)在線解析的 引擎:負(fù)責(zé)整個(gè)js程序的編譯和執(zhí)行過程編譯器:負(fù)責(zé)語法分析和代碼生成作用域:收集和維護(hù)一系列查詢(由所有聲明的標(biāo)識(shí)符組成) 【例子:聲明一個(gè)變量并...
摘要:的目標(biāo)是對(duì)高級(jí)程序中間表示的適當(dāng)?shù)图?jí)抽象,即代碼旨在由編譯器生成而不是由人來寫。表示把源代碼變成解釋器可以運(yùn)行的代碼所花的時(shí)間表示基線編譯器和優(yōu)化編 WebAssembly 那些事兒 什么是 WebAssembly? WebAssembly 是除 JavaScript 以外,另一種可以在網(wǎng)頁(yè)中運(yùn)行的編程語言,并且相比之下在某些功能和性能問題上更具優(yōu)勢(shì),過去我們想在瀏覽器中運(yùn)行代碼來對(duì)網(wǎng)...
摘要:無論你使用的是解釋型語言還是編譯型語言,都有一個(gè)共同的部分將源代碼作為純文本解析為抽象語法樹的數(shù)據(jù)結(jié)構(gòu)。和抽象語法樹相對(duì)的是具體語法樹,通常稱作分析樹。這是引入字節(jié)碼緩存的原因。 這是專門探索 JavaScript 及其所構(gòu)建的組件的系列文章的第 14 篇。 想閱讀更多優(yōu)質(zhì)文章請(qǐng)猛戳GitHub博客,一年百來篇優(yōu)質(zhì)文章等著你! 如果你錯(cuò)過了前面的章節(jié),可以在這里找到它們: JavaS...
摘要:本文主要介紹解析生成的抽象語法樹節(jié)點(diǎn),的實(shí)現(xiàn)也是基于的。原文地址解析器是把源碼轉(zhuǎn)化為抽象語法樹的解析器。參考文獻(xiàn)前端進(jìn)階之抽象語法樹抽象語法樹 前言 Babel為當(dāng)前最流行的代碼JavaScript編譯器了,其使用的JavaScript解析器為babel-parser,最初是從Acorn 項(xiàng)目fork出來的。Acorn 非???,易于使用,并且針對(duì)非標(biāo)準(zhǔn)特性(以及那些未來的標(biāo)準(zhǔn)特性) 設(shè)...
摘要:例如會(huì)被分解成解析語法分析這個(gè)過程是將詞法單元流數(shù)組轉(zhuǎn)換成一個(gè)由元素逐級(jí)嵌套所組成的代表了程序語法結(jié)構(gòu)的樹,這個(gè)樹就叫抽象語法樹。常用的有使用生成并使用抽象語法樹。 一般來說,程序中的一段源代碼在執(zhí)行之前會(huì)經(jīng)歷下面三個(gè)步驟1 分詞/詞法分析這個(gè)過程會(huì)將由字符組成的字符串分解成有意義的代碼快,這些代碼塊被稱為詞法單元。例如 var a = 4;會(huì)被分解成 var、a、=、4、; 2 解析...
閱讀 3320·2021-11-16 11:45
閱讀 2671·2021-09-22 15:23
閱讀 576·2021-07-30 14:58
閱讀 471·2019-08-30 15:54
閱讀 2249·2019-08-29 16:19
閱讀 3028·2019-08-29 12:45
閱讀 950·2019-08-23 17:57
閱讀 1804·2019-08-23 17:54