抽象语法树(Abstract Syntax Tree)也称为AST语法树,指的是源代码语法所对应的树状结构。也就是说,对于一种具体编程语言下的源代码,通过构建语法树的形式将源代码中的语句映射到树中的每一个节点上。
JavaScript语法解析
什么是语法树
可以通过一个简单的例子来看语法树具体长什么样子。有如下代码:
var AST = "is Tree";
我们可以发现,程序代码本身可以被映射成为一棵语法树,而通过操纵语法树,我们能够精准的获得程序代码中的某个节点。例如声明语句,赋值语句,而这是用正则表达式所不能准确体现的地方。JavaScript的语法解析器Espsrima提供了一个在线解析的工具,你可以借助于这个工具,将JavaScript代码解析为一个JSON文件表示的树状结构。
有什么用
抽象语法树的作用非常的多,比如编译器、IDE、压缩优化代码等。在JavaScript中,虽然我们并不会常常与AST直接打交道,但却也会经常的涉及到它。例如使用UglifyJS来压缩代码,实际这背后就是在对JavaScript的抽象语法树进行操作。
在一些实际开发过程中,我们也会用到抽象语法树,下面通过一个小栗子来看看怎么进行JavaScript的语法解析以及对节点的遍历与操纵。
举个例子
小需求
我们将构建一个简单的静态分析器,它可以从命令行进行运行。它能够识别下面几部分内容:
已声明但没有被调用的函数
调用了未声明的函数
被调用多次的函数
现在我们已经知道了可以将代码映射为AST进行语法解析,从而找到这些节点。但是,我们仍然需要一个语法解析器才能顺利的进行工作,在JavaScript的语法解析领域,一个流行的开源项目是Esprima,我们可以利用这个工具来完成任务。此外,我们需要借助Node来构建能够在命令行运行的JS代码。b
完整代码地址:
https://github.com/wwsun/awesome-javascript/tree/master/src/day05
准备工作
为了能够完成后面的工作,你需要确保安装了Node环境。首先创建项目的基本目录结构,以及初始化NPM。
mkdir esprima-tutorial
cd esprima-tutorial
npm install esprima --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');
在上面的代码中:
函数analyzeCode用于执行主要的代码分析工作,这里我们暂时预留下来这部分工作待后面去解决。
我们需要确保用户在命令行中指定了分析文件的具体位置,这可以通过查看process.argv的长度来得到。为什么?你可以参考Node的官方文档:
The first element will be ‘node’, the second element will be the name of the JavaScript file. The next elements will be any additional command line arguments.
获取文件,并将文件传入到analyzeCode函数中进行处理
解析代码和遍历AST
借助Esprima解析代码非常简单,只要使用一个方法即可:
var ast = esprima.parse(code);
esprima.parse()方法接收两种类型的参数:字符串或Node的Buffer对象,它也可以收附加的选项作为参数。解析后返回结果即为抽象语法树(AST),AST遵守Mozilla SpiderMonkey的解析器API。例如代码:
6 * 7
解析后的结果为:
{
"type": "Program",
"body": [
{
"type": "ExpressionStatement",
"expression": {
"type": "BinaryExpression",
"operator": "*",
"left": {
"type": "Literal",
"value": 6,
"raw": "6"
},
"right": {
"type": "Literal",
"value": 7,
"raw": "7"
}
}
}
]
}
我们可以发现每个节点都有一个type,根节点的type为Program。type也是所有节点都共有的,其他的属性依赖于节点的type。例如上面实例的程序中,我们可以发现根节点下面的子节点的类型为EspressionStatement,依此类推。
为了能够分析代码,我们需要对得到的AST进行遍历,我们可以借助Estraverse进行节点的遍历。执行如下命令进行安装该NPM包:
npm install estraverse --save
基本用法如下:
function analyzeCode(code) {
var ast = esprima.parse(code);
estraverse.traverse(ast, {
enter: function (node) {
console.log(node.type);
}
});
}
上面的代码会输出遇到的语法树上每个节点的类型。
获取分析数据
为了完成需求,我们需要遍历语法树,并统计每个函数调用和声明的次数。因此,我们需要知道两种节点类型。首先是函数声明:
{
"type": "FunctionDeclaration",
"id": {
"type": "Identifier",
"name": "myAwesomeFunction"
},
"params": [
...
],
"body": {
"type": "BlockStatement",
"body": [
...
]
}
}
对函数声明而言,其节点类型为FunctionDeclaration,函数的标识符(即函数名)存放在id节点中,其中name子属性即为函数名。params和body分别为函数的参数列表和函数体。
我们再来看函数调用:
"expression": {
"type": "CallExpression",
"callee": {
"type": "Identifier",
"name": "myAwesomeFunction"
},
"arguments": []
}
对函数调用而言,即节点类型为CallExpression,callee指向被调用的函数。有了上面的了解,我们可以继续完成我们的程序如下:
function analyzeCode(code) {
var ast = esprima.parse(code);
var functionsStats = {}; //1
var addStatsEntry = function (funcName) { //2
if (!functionsStats[funcName]) {
functionsStats[funcName] = { calls: 0, declarations: 0 };
}
};
// 3
estraverse.traverse(ast, {
enter: function (node) {
if (node.type === 'FunctionDeclaration') {
addStatsEntry(node.id.name); //4
functionsStats[node.id.name].declarations++;
} else if (node.type === 'CallExpression' && node.callee.type === 'Identifier') {
addStatsEntry(node.callee.name);
functionsStats[node.callee.name].calls++; //5
}
}
});
}
我们创建了一个对象functionStats用来存放函数的调用和声明的统计信息,函数名作为key。
函数addStatsEntry用于实现存放统计信息。
遍历AST
如果发现了函数声明,增加一次函数声明
如果发现了函数调用,增加一次函数调用
处理结果
最后进行结果的处理,我们只需要遍历查看functionStats中的信息就可以得到结果。创建结果处理函数如下:
function processResults(results) {
for (var name in results) {
if (results.hasOwnProperty(name)) {
var stats = results[name];
if (stats.declarations === 0) {
console.log('Function', name, 'undeclared');
} else if (stats.declarations > 1) {
console.log('Function', name, 'decalred multiple times');
} else if (stats.calls === 0) {
console.log('Function', name, 'declared but not called');
}
}
}
}
然后,在analyzeCode函数的末尾调用该函数即可,如下:
processResults(functionsStats);
测试
创建测试文件demo.js如下:
function declaredTwice() {
}
function main() {
undeclared();
}
function unused() {
}
function declaredTwice() {
}
main();
执行如下命令:
$ node index.js demo.js
你将得到如下的处理结果:
Reading test.js
Function declaredTwice decalred multiple times
Function undeclared undeclared
Function unused declared but not called
相关推荐
现在: Javascript:一个(基于的)解析器,分析重建类型结构并首先尝试进行类型推断。 lib/treehugger/js/*.js 该项目依赖进行库加载。 AST表示 ast.js使用一些简单的数据结构来表示AST,并使用文本表示形式来...
Nodejs SQL解析器 使用访问的tableList,columnList将简单SQL语句解析为抽象语法树(AST),然后将其转换回SQL。 :star: 特征支持用分号分隔的多个sql语句支持选择,删除,更新和插入类型支持放置,截断和重命名命令...
与AST (抽象语法树)不同, CST包含JavaScript源文件中的所有信息:空格,标点符号,注释。 此信息对于代码样式检查器和其他代码查询非常有用。 当您需要在保留初始文件格式的同时对现有JavaScript文件应用修改的...
支持主要正则功能已经分组捕获等实现了一个语法解析引擎(支持左递归定义,语法定义简单)提供一个整合了词法分析和语法分析的API,方便使用,可以直接生成最终满足要求的抽象语法树(直接生成最初定义规则对应的抽象语法...
生成命令的抽象语法树(AST)。 根据Bash引用规则的子集标记给定命令 检测格式错误的命令 , 为什么 主要是这样写的: 作为编写基于严格语法规则的解析器的练习 作为一个从头开始构建Bash外壳的大型项目的一部分 ...
js-sql-parser 在js中解析/字符串化sql(选择语法)。 sql语法遵循新闻自v1.3.0 起修复bug tableFactor别名从v1.2.2开始,增加了对“`”引号别名的支持 BUG修复字符串化的关键字having自V1.2.1。 自v1.2.0起,错字...
ASTy 是 JavaScript 的抽象语法树 (AST) 数据结构库,即它提供了一种分层数据结构,用于保存任意形式语言的语法抽象。 它通常与像这样的解析器生成器结合使用(然后特别是与它的实用程序类 )来携带解析步骤的结果并...
prettybnf提供用于Backus-Naur Form语法的解析器和用于漂亮打印BNF抽象语法树的字符串化器。 它使用传统的BNF语法而不是yacc语法。 入门 从获取源代码或最新版本。 您也可以将其作为找到。 您可以在Node.js应用程序...
表达评估 JavaScript表达式解析和评估。 由提供支持。 安装 安装: npm install --save expression-eval 进口: // ES6 import { parse , eval } from '... 解析的结果是AST(抽象语法树),例如: { " ty
GitHub AST查看器 Google Chrome扩展程序,用于查看GitHub上代码的抽象语法树(AST)。 目前仅支持JavaScript代码,但将来会提供更多语言支持!安装从Google Chrome网上应用店安装扩展程序:用法该扩展将AST按钮添加...
抽象语法树 (AST) 敏感的格式,兼容 Mozilla Parser API 经过重度测试,超过 500 个单元测试以及 100% 的代码覆盖 可选跟踪语法节点定位 (index-based and line-column) 超级快,速度是 UglifyJS parse-js ...
1.使解析器生成抽象语法树 当调用Expression.parse(input)时,当前的数学表达式解析器将返回结果和匹配的字符串。 第一个挑战是添加一个Expression.compile(input)函数,该函数返回 。 AST应该具有一个result()函数...
注意! 到目前为止,作者的时间已用完。 如果有人想帮助“修复”我的私人代码的一小部分,那么可以将其移植到...发出ECMAScript:registered:2021兼容的抽象语法树 具有增量解析支持的错误恢复模式 错误诊断 支持J
从 JavaScript 代码生成抽象语法树 (AST) 的解析器。 一个从 AST 输出 JavaScript 代码的代码生成器,还提供获取源映射的选项。 压缩器(优化器)。 它使用转换器 API 将 AST 优化为更小的 AST。 一个 mangler,将...
可选生成抽象语法树 (AST) 使用用户编写的回调函数翻译 AST 广泛的追踪设施 统计分析树节点覆盖的全貌 广泛的属性生成以概述语法的特征 注意:目前此存储库仅包含运行 APG 生成的解析器所需的 APG 运行时库。 发电...
解析器:指的是把描述客户端调用要求的表达式,经过解析,形成一个抽象语法树的程序。 解释器:指的是解释抽象语法树,并执行每个节点对应的功能的程序。 要使用解释器模式,一个重要的前提就是要定义一套语法规则,...
Peast (PHP ECMAScript抽象语法树)是一个PHP 5.4+库,根据解析JavaScript代码,并遵循生成抽象语法树。 安装 在您的composer.json文件中包含以下要求: { "require": { "mck89/peast": "dev-master" } } ...
AST方程式 用于电子表格公式JavaScript解析器,以生成抽象语法树。
解析器,可从JavaScript代码生成抽象语法树(AST)。 一个代码生成器,它从AST输出JavaScript代码,还提供了获取源映射的选项。 压缩机(优化器)。 它使用转换器API将AST优化为更小的AST。 一个mangler,将局部...