JavaScript的语法解析与抽象语法树

Jul 10, 2015

抽象语法树(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');

在上面的代码中:

  1. 函数analyzeCode用于执行主要的代码分析工作,这里我们暂时预留下来这部分工作待后面去解决。
  2. 我们需要确保用户在命令行中指定了分析文件的具体位置,这可以通过查看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.

  3. 获取文件,并将文件传入到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子属性即为函数名。paramsbody分别为函数的参数列表和函数体。

我们再来看函数调用:

"expression": {"type": "CallExpression","callee": {"type": "Identifier","name": "myAwesomeFunction"},"arguments": []
}

对函数调用而言,即节点类型为CallExpressioncallee指向被调用的函数。有了上面的了解,我们可以继续完成我们的程序如下:

function analyzeCode(code) {var ast = esprima.parse(code);var functionsStats = {}; //1var addStatsEntry = function (funcName) { //2if (!functionsStats[funcName]) {functionsStats[funcName] = { calls: 0, declarations: 0 };}};// 3estraverse.traverse(ast, {enter: function (node) {if (node.type === 'FunctionDeclaration') {addStatsEntry(node.id.name); //4functionsStats[node.id.name].declarations++;} else if (node.type === 'CallExpression' && node.callee.type === 'Identifier') {addStatsEntry(node.callee.name);functionsStats[node.callee.name].calls++; //5}}});
}
  1. 我们创建了一个对象functionStats用来存放函数的调用和声明的统计信息,函数名作为key。
  2. 函数addStatsEntry用于实现存放统计信息。
  3. 遍历AST
  4. 如果发现了函数声明,增加一次函数声明
  5. 如果发现了函数调用,增加一次函数调用

处理结果

最后进行结果的处理,我们只需要遍历查看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
Done
原文地址:http://wwsun.me/posts/javascript-ast-tutorial.html

转JavaScript的语法解析与抽象语法树相关推荐

  1. JavaScript的语法解析与抽象语法树

    抽象语法树(Abstract Syntax Tree)也称为AST语法树,指的是源代码语法所对应的树状结构.也就是说,对于一种具体编程语言下的源代码,通过构建语法树的形式将源代码中的语句映射到树中的每 ...

  2. JavaScript 是如何工作的:解析、抽象语法树(AST)+ 提升编译速度5个技巧

    这是专门探索 JavaScript 及其所构建的组件的系列文章的第 14 篇. 如果你错过了前面的章节,可以在这里找到它们: JavaScript 是如何工作的:引擎,运行时和调用堆栈的概述! Jav ...

  3. js 数组 实现 完全树_JavaScript的工作原理:解析、抽象语法树(AST)+ 提升编译速度5个技巧

    摘要: JS的"编译原理". 原文:JavaScript的工作原理:解析.抽象语法树(AST)+ 提升编译速度5个技巧 作者:前端小智 Fundebug经授权转载,版权归原作者所有 ...

  4. java抽象语法树_抽象语法树AST的全面解析(一)

    Javac编译概述 将.java源文件编译成.class文件,这一步大致可以分为3个过程: 1.把所有的源文件解析成语法树,输入到编译器的符号表: 2.注解处理器的注解处理过程: 3.分析语法树并生成 ...

  5. js最小化浏览器_「译」解析、抽象语法树(ast) +如何最小化解析时间的5个技巧...

    前言 该系列课程会在本周陆续更新完毕,主要讲解的都是工作中可能会遇到的真实开发中比较重要的问题以及相应的解决方法.通过本系列的课程学习,希望能对你日常的工作带来些许变化.当然,欢迎大家关注我,我将持续 ...

  6. python 抽象语法树_抽象语法树(Abstract Syntax Tree)

    一般来说,程序中的一段源代码在执行之前会经历下面三个步骤 1 分词/词法分析 这个过程会将由字符组成的字符串分解成有意义的代码快,这些代码块被称为词法单元.例如 var a = 4:会被分解成 var ...

  7. 语法树与抽象语法树(parse tree abstract syntax tree)

    1 语法树(parse tree): 是在parsing阶段,derivation的图像化表示,parser tree focus on grammar的actual implemment,包括像wh ...

  8. java抽象语法树_抽象语法树(AST)

    抽象语法树(AST) 最近在做一个类JAVA语言的编译器,整个开发过程,用抽象语法树(Abstract SyntaxTree,AST)作为程序的一种中间表示,所以首先就要学会建立相对应源代码的AST和 ...

  9. 详解AST抽象语法树

    浅谈 AST 先来看一下把一个简单的函数转换成AST之后的样子. // 简单函数 function square(n) {return n * n; }// 转换后的AST {type: " ...

最新文章

  1. aesecbpkcs5 php_php AES/ECB/PKCS5Padding加密
  2. JAVA版连连看算法研究
  3. java的知识点17——java.util.Arrays类、多维数组
  4. Deepin v20依赖错误,检测更新失败解决办法
  5. 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(五)
  6. html页面js遍历listview,javascript实现的listview效果
  7. nyoj239 月老的难题 二分图 匈牙利算法
  8. Android入门(八) | 常用的界面布局 及 自定义控件
  9. php数组循环转为对象,php中循环实现(字符串,对象,或者数组)编码相互转换
  10. orange's系统可以装mysql 吗?_bochs 2.4.2 ubuntu 安装运行问题《orange#39;s 一个操作系统的...
  11. 微信小程序下拉刷新不回弹
  12. 智慧水务信息化系统的意义
  13. CREO草绘标注字体设置
  14. Simulink选择器模块(Selector)
  15. Java泛型方法与普通成员方法以及案例说明(五)
  16. 【MySQL】SQL执行顺序
  17. 【VUE】图片预览放大缩小插件(推荐阅读)
  18. 人工智能现在的发展前景如何?
  19. access 升迁 mysql_随说秋色园从Access升迁到MSSQL过程
  20. AI翻译新思路,OBTranslate打破非洲城乡居民之间的沟通差距

热门文章

  1. 01_李宏毅机器学习
  2. NTS-Net论文思想
  3. 2019年“华为杯”研究生数学建模竞赛--E题(全球变暖)思路感想
  4. 喜欢猫吗?用这个开源工具撸一只吧!
  5. 2019年最值得期待的区块链项目:PlusFo到底行不行?
  6. 狗看了都流泪的Mask-RCNN
  7. 谷歌地图应用——基础篇之入门
  8. web数据库管理和运维软件 - webcat
  9. 5-2交换实数下面的程序,输入两个实数,交换后输出。请在空白处填写适当内容完成此程序。#include <stdio.h>void RealSwap(double *x, doubl
  10. SharePoint定制开发个性皮肤