什么是抽象语法树?

在计算机科学中,抽象语法和抽象语法树其实是源代码的抽象语法结构的树状表现形式

在线编辑器

我们常用的浏览器就是通过将js代码转化为抽象语法树来进行下一步的分析等其他操作。所以将js转化为抽象语法树更利于程序的分析。

如图:

如上图中的变量声明语句,转化为AST之后就是右图的样子。

先来分析一下左图:

var 是一个关键字

AST是一个定义者

= 是Equal 等号的叫法有很多形式,在后面我们还会看到

“is tree” 是一个字符串

;就是 Semicoion

再来对应一下右图:

首先一段代码转化成的抽象语法树是一个对象,该对象会有一个顶级的type属性'Program',第二个属性是body是一个数组。

body数组中存放的每一项都是一个对象,里面包含了所有的对于该语句的描述信息

type:描述该语句的类型 --变量声明语句
kind:变量声明的关键字 -- var
declaration: 声明的内容数组,里面的每一项也是一个对象
type: 描述该语句的类型
id: 描述变量名称的对象
type:定义
name: 是变量的名字
init: 初始化变量值得对象
type: 类型
value: 值 "is tree" 不带引号
row: "\"is tree"\" 带引号

抽象语法树有哪些用途?

代码语法的检查,代码风格的检查,代码的格式化,代码的高亮,代码错误提示,代码自动补全等等

如:JSLint、JSHint 对代码错误或风格的检查,发现一些潜在的错误

IDE的错误提示,格式化,高亮,自动补全等等

代码的混淆压缩

如:UglifyJS2等

优化变更代码,改变代码结构达到想要的结构

代码打包工具webpack,rollup等等

CommonJS、AMD、CMD、UMD等代码规范之间的转化

CoffeeScript、TypeScript、JSX等转化为原生Javascript

通过什么工具或库来实现源码转化为抽象语法树?

那就是javascript Parser 解析器,他会把js源码转化为抽象的语法树。

浏览器会把js源码通过解析器转化为抽象语法树,再进一步转化为字节码或直接生成机器码

一般来说每一个js引擎都会有自己的抽象语法树格式,chrome的v8引擎,firefox的SpiderMonkey 引擎等等,MDN提供了详细SpiderMonkey AST format的详细说明,算是业界的标准。(SpiderMonkey是Mozilla项目的一部分,是一个用C语言实现的JavaScript脚本引擎,为了在SpiderMonkey中运行JavaScript代码,应用程序必须有三个要素:JSRuntime,JSContext和全局对象。)

常用的javascript Parser

esprima

traceur

acorn

shift

我们主要拿esprima来举一个例子

安装

 npm install esprima estraverse escodegen -S

esprima 涉及三个库名称和功能如下:

esprima 把源码转化为抽象语法树


let esprima = require('esprima'); // 引入esprima
let jsOrigin = 'function eat(){};'; // 定义一个js源码 let AST = esprima.parse(jsOrigin); // 通过esprima.parse将js源码转化为一个抽象语法树 console.log(AST); // 打印生成的抽象语法树 /*Script {
type: 'Program',// 顶级的type属性
body: [ FunctionDeclaration {
type: 'FunctionDeclaration', // js源码的类型--是一个函数声明
id: [Identifier],
params: [],
body: [BlockStatement],
generator: false, // 是不是generator函数
expression: false, // 是不是一个表达式
async: false // 是不是一个异步函数
},
EmptyStatement { type: 'EmptyStatement' }
],
sourceType: 'script'
}*/

estraverse 遍历并更新抽象语法树

在介绍用法之前我们先来npm上看一下这个库,这个库的下载量居然500多万,而且没有README说明文档,是不是很牛掰!

在举例子之前我们要遍历抽象语法树,首先我们要先了解一下他的遍历顺利


let estraverse = require('estraverse'); estraverse.traverse(AST, {
enter(node){
console.log('enter', node.type)
if(node.type === 'Identifier') {
node.name += '_enter'
}
},
leave(node){
console.log('leave', node.type)
if(node.type === 'Identifier') {
node.name += '_leave'
}
}
}) // enter Program
// enter FunctionDeclaration
// enter Identifier
// leave Identifier
// enter BlockStatement
// leave BlockStatement
// leave FunctionDeclaration
// enter EmptyStatement
// leave EmptyStatement
// leave Program

通过上面节点类型的打印结果我们不难看出,我们的抽象语法树的每个节点被访问了2次,一次是进入的时候,一次是离开的时候,我们可以通过下面的图来更加清楚的理解抽象语法树的遍历顺序



看完遍历顺序之后,我们看到代码中的判断条件 如果是变量名的话,第一次进入访问时对这个变量的名称做了一次修改,当离开的时候也做了一次修改。那接下来我们要验证 抽象语法树种的这个节点的变量名称 是否修改成功了呢?我们有两种方案,方案一:直接打印抽象语法树,这个非常简单再这里就你介绍了。方案二: 我们将现有的抽象语法树转化成源码看一下变量名是否变成功 这样就一目了然了。那怎么将我们的抽象语法树还原成源码呢?这就要引入我们的第三个库了 escodegen

escodegen 将抽象语法树还原成js源码


let escodegen = require('escodegen'); let originReback = escodegen.generate(AST);
console.log(originReback);
// function eat_enter_leave() {};

通过上面还原回来的源码我们看到变量名称确实被更改了。

接下来我们来探索一下如何用抽象语法树来将箭头函数转化为普通的函数

我们都知道es6语法转es5的语法我们用的是babel,让我们接下来就看一下 babel是如何将箭头函数转化为普通函数的。

第一步需要使用babel的两个插件,babel-core 核心模块 babel-types 类型模块

npm i babel-core babel-types -S

第一步:我们先来对比普通函数和箭头函数的抽象语法树,通过对比找出其中的不同之处,然后在节点可以复用的前提下,尽可能少的改变一下不同的地方,从而成功的将箭头函数转化为普通函数。

我们以这个箭头函数为例:


let sum = (a,b) => a+b;
------>
var sum = function sum(a, b) {
return a + b;
};

如上图所示,普通函数和箭头函数的AST的不同在于init,所以我们现在要做的是将箭头函数的arrowFunctionExpression 转换为FunctionExpression

利用babel-types生成新的部分的AST语法树,替换原有的。如果创建某个节点的语法树,那就在下面的网址上,需要哪个节点就搜哪个节点

babel-types

	// babel 核心库,用来实现核心的转换引擎
const babel = require('babel-core');
// 实现类型转化 生成AST节点
const types = require('babel-types');
let code = 'let sum = (a,b) => a+b;';
let es5Code = function (a,b) {
return a+b;
}; // babel 转化采用的是访问者模式Visitor 对于某个对象或者一组对象,不同的访问者,产生的结果不同,执行操作也不同 // 这个访问者可以对特定的类型的节点进行处理
let visitor = {
ArrowFunctionExpression(path) {
// 如果这个节点是箭头函数的节点的话,我们在这里进行处理替换工作
// 1.复用params参数
let params = path.node.params;
let blockStatement = types.blockStatement([types.returnStatement(path.node.body)])
let func = types.functionExpression(null, params, blockStatement, false,false);
path.replaceWith(func) }
}; let arrayPlugin = {visitor}; // babel内部先把代码转化成AST,然后进行遍历 let result = babel.transform(code, {
plugins: [
arrayPlugin
]
}); console.log(result.code);
// let sum = function (a, b) {
// return a + b;
// };

我们写一个babel的预计算插件


let code = `const result = 1000 * 60 * 60 * 24`;
//let code = `const result = 1000 * 60`;
let babel = require('babel-core');
let types = require('babel-types');
//预计算
let visitor = {
BinaryExpression(path){
let node = path.node;
if(!isNaN(node.left.value)&&!isNaN(node.right.value)){
let result = eval(node.left.value+node.operator+node.right.value);
result = types.numericLiteral(result);
path.replaceWith(result);
//如果此表达式的父亲也是一个表达式的话,需要递归计算
if(path.parentPath.node.type == 'BinaryExpression'){
visitor.BinaryExpression.call(null,path.parentPath);
}
}
}
}
let r = babel.transform(code,{
plugins:[
{visitor}
]
});
console.log(r.code);

以上就是我对抽象语法树的理解,有什么不正确的地方,恳求斧正。

抽象语法树 Abstract syntax tree的更多相关文章

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

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

  2. 从零写一个编译器(九):语义分析之构造抽象语法树(AST)

    项目的完整代码在 C2j-Compiler 前言 在上一篇完成了符号表的构建,下一步就是输出抽象语法树(Abstract Syntax Tree,AST) 抽象语法树(abstract syntax ...

  3. AST抽象语法树 Javascript版

    在javascript世界中,你可以认为抽象语法树(AST)是最底层. 再往下,就是关于转换和编译的"黑魔法"领域了. 现在,我们拆解一个简单的add函数 function add ...

  4. 【Static Program Analysis - Chapter 2】 代码的表征之抽象语法树

    抽象语法树:AbstractSyntaxTrees 定义(wiki): 在计算机科学中,抽象语法树(abstract syntax tree或者缩写为AST),或者语法树(syntax tree),是 ...

  5. AST抽象语法树

    抽象语法树简介 (一)简介 抽象语法树(abstract syntax code,AST)是源代码的抽象语法结构的树状表示,树上的每个节点都表示源代码中的一种结构,这所以说是抽象的,是因为抽象语法树并 ...

  6. 五分钟了解抽象语法树(AST)babel是如何转换的?

    抽象语法树 什么是抽象语法树? It is a hierarchical program representation that presents source code structure acco ...

  7. 抽象语法树(AST)

    AST描述 在计算机科学中,抽象语法树(AST)或语法树是用编程语言编写的源代码的抽象语法结构的树表示.树的每个节点表示在源代码中出现的构造.语法是“抽象的”,因为它不代表真实语法中出现的每个细节,而 ...

  8. OpenJDK源码研究笔记(十一):浅析Javac编译过程中的抽象语法树(IfElse,While,Switch等语句的抽象和封装)

    浅析OpenJDK源码编译器Javac的语法树包com.sun.source.tree. 抽象语法树,是编译原理中的经典问题,有点难,本文只是随便写写. 0.赋值语句 public interface ...

  9. 编译器开发系列--Ocelot语言1.抽象语法树

    从今天开始研究开发自己的编程语言Ocelot,从<自制编译器>出发,然后再自己不断完善功能并优化. 编译器前端简单,就不深入研究了,直接用现成的一款工具叫JavaCC,它可以生成抽象语法树 ...

随机推荐

  1. memcached的安装、常用命令以及在实际开发中的案例

    Memcached注意缺乏安全认证以及安全管制需要将Memcached服务器放置在防火墙(iptables)之后 Linux平台 (CentOS)安装Memcached 安装依赖yum -y inst ...

  2. ECMAScript基本对象——RegExp 正则表达式对象

    含义:定义字符串的组成规则 使用: 1.定义单个字符:[ ] [a] 表示有一个字符是  小写的a [ab] 表示有一个字符是  小写的a或者b [a-z] 表示有一个字符是  小写的a到z [a-z ...

  3. 剑指offer-面试题24-反转链表-链表

    /* 题目: 定义一个函数,输入链表的头结点,反转链表输出反转后链表的头节点. */ /* 思路: 记录当前节点的next和pre. 断开当前节点指向next的指针,指向pre. */ #includ ...

  4. IDEA 解决Number objects are compared using '==', not 'equals()' 警告

    当代码被工具标黄色高亮时,代表需要优化或重构了 == 是值相等.对于Integer这样的数据类型而言,意义是两个Integer对象的内存地址相等.也就是说如果你有两个不同的Integer的对象, 如果 ...

  5. vue里使用element饿了么的el-menu+vue-router实现路由跳转的两种方法

    最近准备写一个echarts的可视化展示案例,首先用vue-cli3创建了一个项目(好像vue-cli4也出来,感觉变化不大,就没升级了) 然后,开始配置路由↓下面是我的router.js文件 imp ...

  6. java面试记录二:spring加载流程、springmvc请求流程、spring事务失效、synchronized和volatile、JMM和JVM模型、二分查找的实现、垃圾收集器、控制台顺序打印ABC的三种线程实现

    注:部分答案引用网络文章 简答题 1.Spring项目启动后的加载流程 (1)使用spring框架的web项目,在tomcat下,是根据web.xml来启动的.web.xml中负责配置启动spring ...

  7. 洛谷P1219 八皇后 我。。。。。。

    代码1    (学弟版) #include<bits/stdc++.h>using namespace std;int l[15];bool s[15];                  ...

  8. Mybatis核心知识点

    一.初识Mybatis框架 mybatis是一个持久层的框架,是apache下的顶级项目. mybatis托管到goolecode下,再后来托管到github下(https://github.com/ ...

  9. Dataset: online data

    From Kaggle: Appliances Energy Prediction Energy consumption of the Netherlands International Energy ...

  10. 火狐浏览器将网页保存为pdf

    目录 火狐打印功能 火狐插件 save as pdf 深夜更博仙女镇 @ 有时候查一些技术博客之类的,当时收藏了,过一阵子再想查看的时候发现404了,所以稳妥的办法还是将把网页保存为pdf. 火狐打印 ...