astm讲解(AST初探深浅代码还能这样玩)
AST 听起来好像是个很新的东西,那么具体有什么用,好不好用就在这篇文章中找到答案吧~
我们简单将这个词拆分抽象、语法、树,如果我们能够顺利将这个词拆分,那么我们也就掌握了其核心所在
- 抽象:抽象的反义词是具象,也就说明抽象的事物关注点不在于细节,而在于整体
- 语法:语法一组词法的表达式,具备某种指定的规则,具有某种特定的意义,比如 1 1
- 树:树是一种一对多的结构,通过根节点往下递生,可以存在多个子树,当然这不是我们这篇讨论的主题,但却是重点
我们接下来通过几个例子更加清楚了解一下什么是树
一、什么是树?1)算数表达式5 * 4 / 2 3 * 6 这是一个简单的算法运算,但是如果我们要通过树形的方式表达它的话,结果可能是以下这样:
我们通过分析这张树形图,我们可以发现有哪几个结构 ?
- 一部分是数字:5,4,2,3,6
- 一部分是操作符:*, /, , *
我们从中抽取出了 符号,并将其作为该树的根节点,这个时候又可以分为左右两个子树,我们从中提取出一棵子树来看
观察发现子树又变成了一棵树,那么可以得出一个结论:任何一棵子树都可以独立成为一棵完整的树,多个子树可以组合成一棵完整的树。至此,我们就完成了一棵树的定义,接下来我们再看一个其他例子
2)XML 文件XML文件也是我们日常中比较常用到的文件结构
<person> <name> 张三 </name> <label> 法外狂徒 </label> </person>
我们将文件结构转成属性结构后,就可以很直观的看出数据层级与内容
二、树的转换树的有点是很直观,可以直接看出数据层级与内容,但是我们平时操作的时候只能是操作客观上的树形结构,而不是以上主观的树形结构。因此当我们得到上述树形结构后,我们就需要对该树进行扁平化操作,那问题来了,如何扁平化呢?
我们一样拿上述算数运算为例
红色的框框代表一棵树,而绿色和黄色框框则表示该树的两棵子树,当然 5 * 4 当然也可以框起来作为绿色框的子树。
这个时候,聪明的小伙伴们看到这些树有没有什么发现,比如每棵树表示什么?
我们可以发现每棵树似乎都表示着一个算数运算
1)规则定义转换需要建立在一定的规则基础上
我们需要先定义下规则,如果遇到一个运算,我们就以 BinaryExpression 来表示,而 运算 中的结构自然就包含着 字符 和 运算符 ,比如 5 * 4 这是一个运算,我们将整体标识为一个 BinaryExpression。
而这个运算中存在三个元素,分别是:5, 4, *。那么其中 5 和 4 我们就可以称之为 字符, *可以称之为 运算符。由此我们可以再定一个规则,字符 的类型我们可以用 Identifier 来标识,运算符 的类型我们就以 operator 来表示。
到这步我们就已经简单地定义好了一个 规则,接下来我们要做的事情就是利用我们的规则将上述树形结构扁平化
2)小试牛刀
我们先拿上述例子来做操作,首先这是一个表达式,我们利用 BinaryExpression 进行标识
BinaryExpression type: BinaryExpression
从运算中我们 以运算符 可以拆分为左右两部分,也就是 5 和 4,我们继续进行标识
left: Identifier type: Identifier value: 5
right: Identifier type: Identifier valuer: 4
定义好两部分后我们该如何将两部分链接起来呢?那就得用到我们的运算符了 *,我们先利用规则定义好运算符的表示
operator: *
然后将两部分链接起来
3)成品展示
BinaryExpression type: BinaryExpression left: Identifier type: Identifier value: 5 operator: * right: Identifier type: Identifier valuer: 4
很好,到这里我们就完成了第一块里程碑了!
4)趁热打铁上面我们才完成了一小部分的规则转换定义,接下来我们继续将树形结构进行转换:
到这里我们已经从树形结构图转到了我们定义的层级结构了,但我们可以发现,以上的层级结构图依然是不够完整的
目前为止我们才定义了上述表达式中左边的部分,还缺少右边的定义,这个时候就需要大家来帮个忙, 帮我补充一下右边的部分,结构体已经在下述文本中贴出,大家可以复制到自己的文本编辑器中进行填空补充,将__ 内容替换补充即可
right: __ type: __ left: __ type: __ value: __ operator: __ right: __ type: __ value: __
接下来就到了公布答案的环节了!
right: BinaryExpression type: BinaryExpression left: Identifier type: Identifier value: 3 operator: * right: Identifire type: Identifier value: 6
大家可以进行比对下答案是否正确,然后我们将两部分内容进行组装
到这里,我们就已经得到了一个完整的层级结构了,那么这部分内容跟我们今天将的 AST 有什么关系呢?
我们先来看下真正的 AST(抽象语法树)长啥样
我们转换一个简单的函数:
function add(n, m){ return n m }
左边是我们平时编写的代码,而右侧便是通过代码转换得到的 AST 树
我们通过观察这棵 AST 树有什么发现?没错!这棵 AST 树的结构基本和我们刚刚共同完成的层级结构图一致,这意味着我们刚刚自己手撸了一棵 AST 树出来
三、揭露 AST 面纱1)AST 定义1. 它是什么?AST(抽象语法树)并没有我们所想的那么神秘,它是源代码语法结构的一种抽象表示,它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。
2. 它有什么特征?首先它是抽象的,它无关语法结构,不会记录源语言真实语法中的每个细节,比如分隔符,空白符,注释等,它都会进行移除。
3. 它有什么用?通过以上的实践,我们也认识到了转换AST 是一项繁琐的过程,但为什么要去转换呢?现在各种语言语法种类繁多,虽然最终落到计算机的眼中都是 0 和 1,但是编译器需要识别语言,这个时候就需要使用一种通用的数据结构来描述,而 AST 就是那个东西,因为 AST 是真实存在且存在一定逻辑规则的。
4. 它是如何进行转换的?它转换的过程中也是运用到了我们刚刚所说的几种方式:
- 词法分析器
- 语法分析器
- 解释器
比如我们写个简单的代码:
const name = '张三'
- 词法分析
第一步就是 词法分析 ,它的任务就是一个一个字母地读取代码,当它遇到 空格、操作符、特殊符号的时候,就表示自己第一活已经扫描结束了,我们上述的代码这经过 词法分析 后就会被解析为 [const, name, =, '张三'] 这几个值
- 语法分析
经过上层的分析,我们已经拿到了各个 token, 也就是 token流 ,也就是接下来我们就可以对token流 进行语法分析,比如我们第一个遇到的 token 是 const ,语法分析器通过分析,判断它是一个 声明参数 ,就会标记为 VariableDeclaration,以此类推,后面的几个 token 都会进行分析,直到生成了一棵 AST 抽象语法树
当生成树的时候,解析器 会删除一些没必要的标识tokens(比如不完整的括号),因此AST不是100%与源码匹配的,但是已经能让我们知道如何处理了
2)AST 应用AST 查看辅助工具:点我
解析并转换 AST 的这个步骤比较繁琐,当然我们不必重复造轮子,已经有人替我们造好了轮子,比如解析服Java文件,我们可以应用 Javaparser 进行 AST 转换,解析 Js / Ts 文件,可以应用 Babelparser 进行 AST 转换。当然,尽管轮子已经为我们准备好了,我们还需要如何运用,那就是得了解规则,下面附上一些常用的节点类型含义对照表,也就是 AST 转换的规则:
为了快速了解,我们这篇以 JavaScript 文件为例,那么解析与操作 JavaScript 文件,已经有了比较好用的轮子 -- jscodeshift,我们下面就利用 jscodeshift 来操作 AST
1、查找这里是一段十分简易的代码:
import React from 'react'; import { Button } from 'antd';
我们对比上面的 节点类型含义对照表 ,可以看出这是两个 ImportDeclaration 语句
然后我们将这段代码放到 AST 可视化工具中查看转换成 AST 后的样子:
这个时候我们有个小小的需求,那就是我想要获取下面代码块中的导包源,也就是 from 后面的内容
import React from "react"; import { Button } from "antd"; import { moment } from "moment";
我们来看这段话的含义,代码中我们通过引入 jscodeshift 来帮助我们解析和操作 AST 文件,然后在 API 中声明了我们要查找元素的类型
这个时候我们可以打开控制台运行 node find.js 来运行该脚本内容,可以看到控制台成功的输出了我们想要的结果!
react antd moment
接下来我们玩法进阶,我们在下面代码块中除了看到有 import 语法,还定义了 name 属性,那我们这个时候需求又来了, 我想获取该 name 的值!这个时候要怎么办呢?
第一步我们需要查看 AST 结构,我们可以将文件体复制到我们的 AST 查看辅助工具上进行 AST 结构概览:
可以看到我们想要的内容在 ArrayExpression 中的 elements中,那么接下来我们在代码中该如何操作呢?大家可以先进行尝试~
答案如下:
我们先要找到 ArrayExpression 类型的元素,然后访问该元素下的 elements 属性,就会得到我们想要的值了!
2、修改
张三 李四 王五
我们上面已经实现了通过 AST 结构来查找我们想要的元素,下面我们就可以开始进行操作节点元素了!
首先先看如何修改,这时来了个需求,我们的 Button 组件名称变了,换成了 Button01 ,那我们就得做出相应的修改
接下来我们继续看以下文件,通过查看可以发现有些不同,这个时候多了 find API,而且这个API可以增加参数 { source: { value: "antd" } } 。
这个 API 的目的是只查找 source = antd 的 ImportDeclaration 元素,然后进行替换,Button命名的所在位置在 imported.name,因此我们相应修改该值即可
我们通过运行 node modify.js 便可以看到我们修改后的文件内容,想要使之生效,我们还需要将修改后的内容写会该文件中,我们可以在文件最下方补上下面一段代码:
fs.writeFileSync('./code/demo.js', root.toSource(), 'utf-8')
然后运行代码,这个时候我们就可以发现 demo.js文件内容已经发生了修改。
3、新增
import React from "react"; import { Button01 } from "antd"; import { moment } from "moment"; var name = ["张三", "李四", "王五"];
有了查,改,接下来就轮到了增了,增的话会比上面复杂些,因为我们需要将我们要新增的内容构建成 AST 结构,然后再往已有的 AST 结构中插入
老样子,我们老朋友需求又来了,之前页面中只用到了 antd 的 Button 组件,那我们页面这个时候还需要用到 antd 的 Select 组件
我们第一步就是要将我们要插入的内容构建成 AST 元素,我们先分析已有的 Button AST 结构长啥样,然后依葫芦画瓢构建即可。
我们分析得到该结构的组成部分由 ImportSpecifier 和 Identifier 组成,ImportSpecifier中包着 Identifier
那么我们就可以得出我们要插入的内容结构为:
接下来就交给 jscodeshift 帮我们生成
$.importSpecifier($.identifier("Select"))
得到 AST 结构后我们还需要查看我们要插入的位置,回到之前的 AST 结构中
我们发现导入的资源组件内容都放在了 specifiers 属性中,那我们就可以动手操作了,我们在项目中找到 create.js 文件
通过运行代码,可以发现结果已经变成了我们修改后的内容。
4、删除
import React from "react"; import { Button, Select } from "antd"; import { moment } from "moment"; var name = ["张三", "李四", "王五"];
讲完查,改,增,最后就剩下我们拿手的删了
需求它又来了,页面这个时候不需要 antd 组件了,也就是将 import { Button } from "antd"; 这句话移除
那就老规则,先找到 antd 这个元素所在的 AST,然后将它置为空即可
这个时候通过运行,就可以发现打印出来的内容已经没有了关于antd 的引入信息了
import React from "react"; import { moment } from "moment"; var name = ["张三", "李四", "王五"];
到这里我们就讲完了关于 AST 的增删改查操作
好了,以上便是本篇的所有内容,AST 是个很有用的工具,如果觉得对你有帮助的小伙伴不妨点个关注做个伴,便是对我最大的支持。
需要LeetCode算法刷题小册的可以给我私信发【算法】两个字
不要空谈,不要贪懒,和我一起做个吹着牛X做架构的程序员吧~ 咱们下文再见!
链接:https://mp.weixin.qq.com/s/FEHHkm0XVM2AAQw440Oljw
,
免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com