`

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

 
阅读更多

抽象语法树(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()方法接收两种类型的参数:字符串或NodeBuffer对象,它也可以收附加的选项作为参数。解析后返回结果即为抽象语法树(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,根节点的typeProgramtype也是所有节点都共有的,其他的属性依赖于节点的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 = {}; //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

 

分享到:
评论

相关推荐

    自定义抽象语法树JSON模板

    自定义抽象语法树JSON模板是为特定语言或解析任务构建的,用于将源代码转换成更容易处理的形式。这个过程通常发生在编译器、解释器或者静态代码分析工具的工作流程中。 首先,让我们深入了解什么是抽象语法树。AST...

    使用JavaScript编写的语法分析器

    JavaScript语法分析器是一种用于解析JavaScript源代码的工具,它能够将源代码分解成一系列符合语法规则的结构,这些结构通常被称为语法树或抽象语法树(AST)。在本项目中,这个语法分析器是基于LL(1)文法构建的,...

    C# 动态解析 javascript 脚本引擎源码

    开发者可以通过阅读这些源码来了解C#如何实现JavaScript引擎的核心功能,包括词法分析、语法解析、抽象语法树(AST)的构建以及代码的执行。 6. **Tests**:测试目录,通常包含单元测试或集成测试,用于验证Noesis....

    JavaScript 解释器,包含词法分析、语法解析和执行。基于 LR 实现 eval.zip

    语法解析紧接着词法分析,它使用词法单元构建抽象语法树(AST,Abstract Syntax Tree)。AST是源代码的结构化表示,每个节点都代表代码的一个结构部分,如表达式、语句或函数定义。在这个项目中,提到使用了LR(Look...

    treehugger:JavaScript AST(抽象语法树)转换工具

    现在: Javascript:一个(基于的)解析器,分析重建类型结构并首先尝试进行类型推断。 lib/treehugger/js/*.js 该项目依赖进行库加载。 AST表示 ast.js使用一些简单的数据结构来表示AST,并使用文本表示形式来...

    JavaScript引擎工作原理

    在解析阶段,JavaScript引擎会将源代码转换为抽象语法树(Abstract Syntax Tree,AST)。AST是一个树状结构,用于表示编程语言的语法结构。在这个阶段,JavaScript引擎会检查代码的语法错误,如果代码存在语法错误,...

    node-sql-parser:使用访问的tableList将简单SQL语句解析为抽象语法树(AST),并将其转换回SQL

    Nodejs SQL解析器 使用访问的tableList,columnList将简单SQL语句解析为抽象语法树(AST),然后将其转换回SQL。 :star: 特征支持用分号分隔的多个sql语句支持选择,删除,更新和插入类型支持放置,截断和重命名命令...

    Acorn一个基于JavaScript的小型快速的JavaScript解析器

    Acorn的设计目标是提供一种简洁的方式来将JavaScript源代码转换为抽象语法树(Abstract Syntax Tree,AST)。AST是一种编程语言的结构表示,对于理解和操作代码非常有用。通过Acorn解析后的AST,开发者可以方便地...

    旖美信息_抽象语法树在前端的应用.rar

    抽象语法树(Abstract Syntax Tree,AST)是编程语言编译理论中的一个重要概念,它在前端开发中的应用日益广泛。在本资料"旖美信息_抽象语法树在前端的应用"中,我们将深入探讨AST如何帮助前端开发者提高代码质量和...

    通过MOZILLA的javascript引擎(SpiderMonkey)执行js代码

    1. **解析阶段**:SpiderMonkey接收到JavaScript源代码后,首先将其转化为抽象语法树(AST,Abstract Syntax Tree)。这个过程类似于编译器的词法分析和语法分析,将代码拆分成可操作的结构。 2. **编译阶段**:...

    在Rust中编写JavaScript解析器的教程.zip

    JavaScript解析器是编译器或解释器的重要组成部分,它负责将源代码转换为抽象语法树(AST),为后续的编译或执行阶段提供结构化的表示。 **Rust语言介绍** Rust是一种系统级编程语言,以其高性能、内存安全和并发性...

    tree-sitter-[removed]树保姆的Javascript语法

    Tree-sitter是一个现代的解析器生成器,它能够为各种编程语言(包括JavaScript)生成高效、精确的抽象语法树(AST)。在本篇文章中,我们将深入探讨Tree-sitter-javascript项目,以及它如何帮助开发者理解和操作...

    纯Java 实现的一个脚本语言 语法类似javascript.zip

    3. **词法分析和解析**:实现这样的脚本引擎需要理解编译原理,包括词法分析(将源代码分解成标记)和语法解析(将标记转化为抽象语法树)。 4. **虚拟机或解释器设计**:这个引擎可能有一个自己的虚拟机或解释器来...

    mujs-1.0.6_javascript树_javascript_bottomc5y_

    在标题"mujs-1.0.6_javascript树_javascript_bottomc5y_"中,“javascript树”可能是指MUJS引擎在处理JavaScript代码时所采用的数据结构——抽象语法树(Abstract Syntax Tree,AST),这是一种用于编译器和解释器的...

    JavaScript解析器用于ES6的压缩器优化器和美化工具包

    Terser的工作原理是首先将源代码解析成抽象语法树(AST),然后对AST进行各种优化操作,最后再将优化后的AST转换回JavaScript代码。 在JavaScript开发中,Terser这样的工具常与构建工具(如Webpack或Rollup)结合...

    hast:超文本抽象语法树格式

    超文本抽象语法树(HAST)是HTML解析和处理领域中的一个重要概念,它与编程语言中的抽象语法树(AST)类似,但专为HTML文档设计。HAST是基于unist(通用语法树)标准的一种表示形式,它允许开发者以结构化的方式理解...

    Node.js-Acorn-一个微小快速的JavaScript解析器完全用JavaScript编写

    它能够将JavaScript源代码转换成抽象语法树(AST),这是编译器和解释器理解代码的基础。Acorn的体积小巧,适合于那些对解析速度和内存占用有高要求的项目,特别是当需要在浏览器环境中处理大量或复杂的JavaScript...

    Node.js-Hevia-用JavaScript编写的Swift解析器

    4. **编译器与解析器原理**:了解编译器和解析器的工作原理,如词法分析、语法分析、抽象语法树(AST)等,这是Hevia的核心技术。 5. **JSON处理**:学习如何在JavaScript中操作JSON,包括解析和序列化JSON数据。 ...

    语法分析树

    抽象语法树(Abstract Syntax Tree, 简称AST)是在语法分析树的基础上经过优化得到的一种更为简化的表示形式。AST只保留了与编译过程相关的信息,而去掉了那些不必要的信息,比如括号、逗号等。AST使得编译器在后续...

Global site tag (gtag.js) - Google Analytics