浅谈抽象语法树(AST)

737 阅读6分钟

1. AST(抽象语法树) 到底是什么?

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

image.png

1.1 抽象语法树的具体结构

序号类型原名称中文名称描述
1Program程序主体整段代码的主体
2VariableDeclaration变量声明声明一个变量,例如 var let const
3FunctionDeclaration函数声明声明一个函数,例如 function
4ExpressionStatement表达式语句通常是调用一个函数,例如 console.log()
5BlockStatement块语句包裹在 {} 块内的代码,例如 if (condition){var a = 1;}
6BreakStatement中断语句通常指 break
7ContinueStatement持续语句通常指 continue
8ReturnStatement返回语句通常指 return
9SwitchStatementSwitch 语句通常指 Switch Case 语句中的 Switch
10IfStatementIf 控制流语句控制流语句,通常指 if(condition){}else{}
11Identifier标识符标识,例如声明变量时 var identi = 5 中的 identi
12CallExpression调用表达式通常指调用一个函数,例如 console.log()
13BinaryExpression二进制表达式通常指运算,例如 1+2
14MemberExpression成员表达式通常指调用对象的成员,例如 console 对象的 log 成员
15ArrayExpression数组表达式通常指一个数组,例如 [1, 3, 5]
16NewExpressionNew 表达式通常指使用 New 关键词
17AssignmentExpression赋值表达式通常指将函数的返回值赋值给变量
18UpdateExpression更新表达式通常指更新成员值,例如 i++
19Literal字面量字面量
20BooleanLiteral布尔型字面量布尔值,例如 true false
21NumericLiteral数字型字面量数字,例如 100
22StringLiteral字符型字面量字符串,例如 vansenb
23SwitchCaseCase 语句通常指 Switch 语句中的 Case

1.2 抽象语法树常见节点类型

2.如何获取AST

以前我们在做语文习题时,经常会做到的一种题型就是判断句子是否正确和改正,比如:"我是顺丰前端。"

解题方法通常是:

  • 第一步:找出语句中的主语、谓语、宾语、定、状、补。
  • 第二步:找出语句中的形容词、动词、标点符号等进行分析

image.png

2.1 分词后结构

在这一步骤中可以很明显的发现第一个错误:在句末使用的是一个句号,实际应该使用感叹号(!) 接着再对主语、谓语、宾语中的词语进行依次分析,将数据结构整理成这样(?):

image.png

2.2 加入语法后结构

在上面这个简单的例子中,其实和AST的生成就颇为相似,AST是源代码的抽象语法结构的树状表现形式,简单点就是一个深度嵌套对象,这个对象能够描述我们书写代码的所有信息

2.1 AST 如何生成

js 执行的第一步是读取 js 文件中的字符流,然后通过词法分析生成 token,之后再通过语法分析( Parser )生成 AST,最后生成机器码执行。

整个解析过程主要分为以下两个步骤:

  • 词法分析:将整个代码字符串分割成最小语法单元数组
  • 语法分析:在分词基础上建立分析语法单元之间的关系
词法分析

词法分析,也称之为扫描(scanner),简单来说就是调用 next() 方法,一个一个字母的来读取字符,然后与定义好的 JavaScript 关键字符做比较,生成对应的Token。Token 是一个不可分割的最小单元:

例如 var 这三个字符,它只能作为一个整体,语义上不能再被分解,因此它是一个 Token。

词法分析器里,每个关键字是一个 Token ,每个标识符是一个 Token,每个操作符是一个 Token,每个标点符号也都是一个 Token。除此之外,还会过滤掉源程序中的注释和空白字符(换行符、空格、制表符等。

最终,整个代码将被分割进一个tokens列表(或者说一维数组)。

语法分析

语法分析会将词法分析出来的 Token 转化成有语法含义的抽象语法树结构。同时,验证语法,语法如果有错的话,抛出语法错误。

获取AST常用工具: esprima,acron,espree等。

3. AST的常见应用

抽象语法树在代码语法的检查、代码风格的检查、代码的格式化、代码的高亮、代码错误提示、代码自动补全等等场景均有广泛的应用。 image.png

3.1 抽象语法树学术上的应用

(1) babel

Babel 其实就是一个最常用的Javascript编译器,它能够转译 ECMAScript 2015+ 的代码,使它在旧的浏览器或者环境中也能够运行,工作过程分为三个部分:

  • Parse(解析) 将源代码转换成抽象语法树,树上有很多的estree节点
  • Transform(转换) 对抽象语法树进行转换
  • Generate(代码生成) 将上一步经过转换过的抽象语法树生成新的代码。

babel 实现原理及插件:

  • @babel/parser 可以把源码转换成AST

  • @babel/traverse 用于对 AST 的遍历,维护了整棵树的状态,并且负责替换、移除和添加节点

  • @babel/generate 可以把AST生成源码,同时生成sourcemap

  • @babel/types 用于 AST 节点的 Lodash 式工具库, 它包含了构造、验证以及变换 AST 节点的方法,对编写处理 AST 逻辑非常有用

  • @babel/core Babel 的编译器,核心 API 都在这里面,比如常见的 transformparse,并实现了插件功能。

(2) eslint

相信大家在工作中都肯定使用过 ESLint,今天我来简述一下它的核心工作原理。

其实 Babel 里面的AST遍历也是有生命周期的,有两个钩子:在遍历开始之前或遍历结束之后,它们可以用于设置或清理/分析工作,其中visitor中包含对AST不同节点的处理方法。将需要配置的规则放在vistor中实现,以下是no-console的一个例子:

image.png

image.png image.png

(3) AST的应用-插件按需加入

相信大家在工作中肯定都用过 Lodash 这个工具库,它是一个一致性、模块化、高性能的 JavaScript 实用工具库。

但是在使用它的时候有一个痛点,那就是它不支持按需加载,只要我们引用了这个工具库中的某个方法,就相当于引用整个工具库,要实现按需引入,就需要。

image.png

image.png

image.png

image.png 思路:针对import节点进行替换。

整体方案:

  • 第一步:在插件中拿到我们在插件调用时传递的参数libraryName
  • 第二步:获取import节点,找出引入模块是libraryName的语句
  • 第三步:进行批量替换旧节点

image.png