一个简单的例子看懂抽象语法树的魔力

栏目: JavaScript · 发布时间: 5年前

内容简介:在计算机科学中,抽象语法树(Abstract Syntax Tree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。上面这是百度百科的定义,一如既往的让人摸不着头脑。我们总结一下百度百科的定义:

在计算机科学中,抽象语法树(Abstract Syntax Tree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。

上面这是百度百科的定义,一如既往的让人摸不着头脑。

我们总结一下百度百科的定义:

  1. 抽象语法树是一颗树
  2. 树的每个节点都表示了源代码的一种 语法结构

上面这段总结有几个关键词:抽象语法树、树、节点、源代码、语法结构,其中语法结构是比较难理解的,那么什么是语法结构呢?

举几个简单的例子:

//变量声明
var a = 1;
复制代码
//循环
while(true){
    console.log(1);
}
复制代码
//判断
if(true){
    console.log(1);
}
复制代码
//函数声明
function a(){
    console.log(1);
}
复制代码

以上这些例子是js的语法声明(statement),这种声明就可以看成js的语法结构! 也就是说抽象语法树的每个节点都在描述这种结构。

比如说一个节点是变量声明,那么这个节点的子节点都会去描述变量声明的具体内容:变量名是什么,变量是什么类型,变量的初始值是什么等等。

就是这样一个一个的声明,构成了抽象语法树。

代码执行的三个步骤

从js程序到机器可执行的机器码需要经历两个阶段:

  • 语法检查
  • 编译运行

语法检查又分为语法分析和词法分析,所以分成三个步骤就是:

  • 词法分析
  • 语法分析
  • 编译运行

这里先简单介绍下每个阶段都干了什么活:

第一步:词法分析,也叫做扫描scanner。它读取我们的代码,然后把它们按照预定的规则合并成一个个的标识Tokens(type 和 value )。这个阶段,它会移除空白符,注释等。最后,整个代码将被分割进一个Tokens列表(一个一维数组)。

第二步:语法分析,也叫做解析器。它会将词法分析出来的Token数组转化成树形的表达形式。同时,验证语法,语法如果有错的话,抛出语法错误。

第三步:编译阶段,也叫编译器。这个阶段会处理AST,生成机器可执行的机械码。

词法分析

先以一个简单的例子看下token序列长什么样

var a = 1;
复制代码

他的token长这样

一个简单的例子看懂抽象语法树的魔力

其实就是一个一维数组,里面有一些对象用于描述每个单词。

我整理了下常见的type:

  • Keyword (关键词)
  • Identifier (标识符)
  • Punctuator (标点符号)
  • Numberic(数字)
  • String (字符串)
  • Boolean(布尔)
  • Null(空值)

语法分析

词法分析由源代码生成了Token序列,语法分析则是由Token序列生成了抽象语法树。

还是看上一个例子:

var a = 1;
复制代码

他的抽象语法树长这样:

一个简单的例子看懂抽象语法树的魔力

1、先看最外层的三个属性

  • type(表示是一段程序代码)
  • body(代码的具体内容)
  • sourceType(表示语言的种类)

2、再看body里面的具体内容,body是一个数组,这是因为程序可能有多个内容块(statement),每个内容块用一个对象表示。

3、再看每个内容块的内容

  • type(表示这个内容块是干什么的)
  • declarations(乘装变量内容的块,可以看到这个块也是一个数组,因为变量声明可能生命多个,所以一个生命对应一个对象 例如 var a=1,b=2;) kind(关键字)

4、再看declarations里面对象里面的内容

  • type (声明的类型是个变量)
  • id(表示变量名)
  • init(表示为这个变量设置的初值)

上面提到statement,statement有很多类型,比如说变量声明,函数定义,if语句,while循环,等都是一个statement,大家如果想看更多的类型,点击这里。

一个超级简单的例子

好了,说了这么多,终于要写代码了。

这个例子实现的功能:

  • 将源代码中的 == 变成 ===
  • 将源代码中的 var 变成 let
  • 将源代码中的 console注释掉

这个例子用到的工具:

  • Esprima (将源代码转化为ast)
  • Estraverse(遍历语法树)
  • Escodegen(讲语法书反编译为js代码)

初始化一个项目

npm init
复制代码

安装用到的依赖包

npm install esprima estraverse escodegen --save
复制代码

新建index.js入口文件 和 originCode.js 源代码文件

在 originCode.js 中输入要转换的源代码

function fun() {
    var opt = 1;
    console.log(1);
    if (opt == 1) {
        console.log(2);
    }
}
复制代码

在 index.js 中实现我们的功能

let fs  = require('fs');
const esprima = require('esprima');//将JS代码转化为语法树模块
const estraverse = require('estraverse');//JS语法树遍历各节点
const escodegen = require('escodegen');//将JS语法树反编译成js代码模块

/**
* 由源代码得到抽象语法树
*/
function  getAst(jsFile) {
    let jsCode;
    return new Promise((resolve)=>{
        fs.readFile(jsFile, (error, data) => {
            jsCode = data.toString();
            resolve(esprima.parseScript(jsCode));
        });
    });
}

/**
 * 设置全等
 */
function setEqual(node) {
    if (node.operator === '==') {
        node.operator = '===';
    }
}

/**
 * 删除console
 */
function delConsole(node) {
    if (node.type === 'ExpressionStatement' && node.expression.type === 'CallExpression' && node.expression.callee.object.name==='console') {
        node.expression.callee.object.name = '//console';
    }
}

/**
 * 把var变成let
 */
function setLet(node){
    if(node.kind === 'var'){    
        node.kind = 'let';
    }
}

/**
 * 遍历语法树
 */
function travel(ast){
    estraverse.traverse(ast, {
        enter: (node) => {
            setEqual(node);
            setLet(node);
            delConsole(node);
        }
    });
}

/**
* 生成文件
*/
function  writeCode(file,data) {
    fs.writeFile(file,data,(error)=>{
        console.log(error);
    });
}

/**
* 入口函数
*/
function main(){
    let file = './originCode.js';
    let distFile = './distCode.js';
    getAst(file).then(function(jsCode) {
        travel(jsCode);
        
        // 删掉 console , 通过parseScript在生成一变ast去掉注释的内容
        // let distCode = escodegen.generate( esprima.parseScript( escodegen.generate(jsCode)));
        // 注释 console
        let distCode = escodegen.generate(jsCode);
        console.log('distcode',distCode);

        writeCode(distFile,distCode);
    });
}

main();
复制代码

然后运行我们的项目

node index.js
复制代码

distCode.js的内容已经变成我们想要的了

function fun() {
    let opt = 1;
    //console.log(1);
    if (opt === 1) {
        //console.log(2);
    }
}
复制代码

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

第三次工业革命

第三次工业革命

[美] 杰里米•里夫金(Jeremy Rifkin) / 张体伟 / 中信出版社 / 2012-5 / 45.00元

第一次工业革命使19世纪的世界发生了翻天覆地的变化 第二次工业革命为20世纪的人们开创了新世界 第三次工业革命同样也将在21世纪从根本上改变人们的生活和工作 在这本书中,作者为我们描绘了一个宏伟的蓝图:数亿计的人们将在自己家里、办公室里、工厂里生产出自己的绿色能源,并在“能源互联网”上与大家分享,这就好像现在我们在网上发布、分享消息一样。能源民主化将从根本上重塑人际关系,它将影响......一起来看看 《第三次工业革命》 这本书的介绍吧!

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具