AST 初探深浅,代码还能这样玩?

news2024/12/22 15:01:45

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, *。那么其中 54 我们就可以称之为 字符* 可以称之为 运算符。由此我们可以再定一个规则,字符 的类型我们可以用 Identifier 来标识,运算符 的类型我们就以 Operator 来表示。

到这步我们就已经简单地定义好了一个 规则,接下来我们要做的事情就是利用我们的规则将上述树形结构扁平化

2)小试牛刀

我们先拿上述例子来做操作,首先这是一个表达式,我们利用 BinaryExpression 进行标识

BinaryExpression
    type: BinaryExpression
复制代码

从运算中我们 以运算符 可以拆分为左右两部分,也就是 54,我们继续进行标识

left: Identifier
    type: Identifier
    value: 5
复制代码
right: Identifier
    type: Identifier
    valuer: 4
复制代码

定义好两部分后我们该如何将两部分链接起来呢? 那就得用到我们的运算符了 *,我们先利用规则定义好运算符的表示

operator: *
复制代码

然后将两部分链接起来

BinaryExpression
    type: BinaryExpression
    left: Identifier
        type: Identifier
        value: 5
    operator: *
    right: Identifier
        type: Identifier
        valuer: 4
复制代码

3)成品展示

很好,到这里我们就完成了第一块里程碑了!

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 转换的规则:

类型名称中文译名描述
Program程序主体整段代码的主体
VariableDeclaration变量声明声明变量,比如 let const var
FunctionDeclaration函数声明声明函数,比如 function
ExpressionStatement表达式语句通常为调用一个函数,比如 console.log(1)
BlockStatement块语句包裹在 {} 内的语句,比如 if (true) { console.log(1) }
BreakStatement中断语句通常指 break
ContinueStatement持续语句通常指 continue
ReturnStatement返回语句通常指 return
SwitchStatementSwitch 语句通常指 switch
IfStatementIf 控制流语句通常指 if (true) {} else {}
Identifier标识符标识,比如声明变量语句中 const a = 1 中的 a
ArrayExpression数组表达式通常指一个数组,比如 [1, 2, 3]
StringLiteral字符型字面量通常指字符串类型的字面量,比如 const a = '1' 中的 '1'
NumericLiteral数字型字面量通常指数字类型的字面量,比如 const a = 1 中的 1
ImportDeclaration引入声明声明引入,比如 import

为了快速了解,我们这篇以 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 = antdImportDeclaration 元素,然后进行替换,Button 命名的所在位置在 imported.name,因此我们相应修改该值即可

我们通过运行 node modify.js 便可以看到我们修改后的文件内容,想要使之生效,我们还需要将修改后的内容写会该文件中,我们可以在文件最下方补上下面一段代码:

fs.writeFileSync('./code/demo.js', root.toSource(), 'utf-8')
复制代码

然后运行代码,这个时候我们就可以发现 demo.js文件内容已经发生了修改。

import React from "react";
import { Button01 } from "antd";
import { moment } from "moment";

var name = ["张三", "李四", "王五"];
复制代码

3、新增

有了查,改,接下来就轮到了了,增的话会比上面复杂些,因为我们需要将我们要新增的内容构建成 AST 结构,然后再往已有的 AST 结构中插入

老样子,我们老朋友需求又来了,之前页面中只用到了 antdButton 组件,那我们页面这个时候还需要用到 antdSelect 组件

我们第一步就是要将我们要插入的内容构建成 AST 元素,我们先分析已有的 Button AST 结构长啥样,然后依葫芦画瓢构建即可。

我们分析得到该结构的组成部分由 ImportSpecifierIdentifier 组成,ImportSpecifier 中包着 Identifier

那么我们就可以得出我们要插入的内容结构为:

接下来就交给 jscodeshift 帮我们生成

$.importSpecifier($.identifier("Select"))
复制代码

得到 AST 结构后我们还需要查看我们要插入的位置,回到之前的 AST 结构中

我们发现导入的资源组件内容都放在了 specifiers 属性中,那我们就可以动手操作了,我们在项目中找到 create.js 文件

通过运行代码,可以发现结果已经变成了我们修改后的内容。

import React from "react";
import { Button, Select } from "antd";
import { moment } from "moment";

var name = ["张三", "李四", "王五"];
复制代码

4、删除

讲完查,改,增,最后就剩下我们拿手的

需求它又来了,页面这个时候不需要 antd 组件了,也就是将 import { Button } from "antd"; 这句话移除

那就老规则,先找到 antd 这个元素所在的 AST,然后将它置为空即可

这个时候通过运行,就可以发现打印出来的内容已经没有了关于antd 的引入信息了

import React from "react";
import { moment } from "moment";

var name = ["张三", "李四", "王五"];
复制代码

到这里我们就讲完了关于 AST 的增删改查操作


好了,以上便是本篇的所有内容,AST 是个很有用的工具,如果觉得对你有帮助的小伙伴不妨点个关注做个伴,便是对小菜最大的支持。不要空谈,不要贪懒

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/22445.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

微信小程序|使用小程序制作一个2048小游戏

文章目录一、文章前言二、创建小程序三、功能开发一、文章前言 此文主要通过小程序实现2048游戏&#xff0c;游戏操作简单&#xff0c;容易上手。 规则&#xff1a;正常打开游戏的界面&#xff0c;会只有两个2&#xff0c;每次移动后都会出现一个2&#xff0c;数字大了之后会出…

RabbitMQ初步到精通-第八章-Java-AMQP-Client源码分析

目录 第八章-Java-AMQP-Client源码分析 1、背景 1.1 客户端介绍 1.2 看源码好处 1.3 如何看源码 2、生产者 3、消费者监听 4、创建连接 5、消费者消费 6. 总结&#xff1a; 第八章-Java-AMQP-Client源码分析 1、背景 1.1 客户端介绍 通过前面几章的学习&#xff0c;大家对…

工作以来一直在CRUD,Spring源码该怎么阅读?这份价值百万的源码解析让你如有神助!

初学SpringBoot框架时&#xff0c;第一次启动服务&#xff0c;直呼什么鬼&#xff1f;只需要简单的几步配置&#xff0c;几个核心的注解&#xff0c;就可以快速实现工程的搭建和运行&#xff1b; 虽然从Spring框架迁移到SpringBoot框架&#xff0c;在初期会有很多的不适应&…

谈谈对跨域(跨源)的一些理解

一、相关概念 1、什么是跨域&#xff1f; 跨域又称为跨源&#xff0c;是指在违反了浏览器的同源政策&#xff0c;也就是协议、域名和端口号三者不完全一致的情况下产生的。只要客户端与浏览器的三者有一项不同&#xff0c;就属于不同源&#xff0c;就会产生跨域。 很多初级开…

G 蛋白偶联受体与小分子化合物的相互作用

化学遗传学 (Chemogenetics) 是指一种蛋白被改造与先前未被识别的小分子化合物相互作用的过程。多种蛋白的改造已被报道&#xff0c;包括激酶、非激酶的酶类、G 蛋白偶联受体 (GPCRs) 和配体门控离子通道。化学遗传学技术 DREADDs (Designer receptors exclusively activated b…

MapReduce分区、排序、Combiner

Shuffle MapReduce的Map阶段与Reduce阶段之间有一个Shuffle的过程&#xff0c;包括分区、排序等内容。数据从Map阶段出来后&#xff0c;会进入一个环形缓冲区&#xff08;默认100M&#xff09;&#xff0c;环形缓冲区中会同时记录数据和索引&#xff0c;当使用了80%的时候&…

PostgreSQL主从数据库数据同步

运行环境 操作系统&#xff1a;Debian 11.5 数 据 库&#xff1a;PostgreSQL 14.6 主数据库&#xff1a;192.168.8.68 从数据库&#xff1a;192.168.8.69 使用apt-get安装postgresql&#xff0c;安装方法可以参考 https://blog.csdn.net/itbs/article/details/127909359?…

智能家居环境小护士(原理图、pcb、源码、设计报告)

目录 ARM-STM32校园创新大赛 1 题 目&#xff1a; 智能家居环境小护士 1 摘要 1 引言 2 系统方案 3 整套系统的工作原理是&#xff1a;单片机是整套系统的控制核心&#xff0c;温湿度传感器负责测试环境中的温湿度&#xff1b;烟雾传感器负责检测空气中的有毒气体&#xff0c;…

数据同步工具DataX介绍和原理

目录1. DataX介绍2. 框架设计3. 架构1. DataX介绍 DataX是一个各种数据源之间的离线数据同步工具 DataX的设计理念是一种星型数据链路。DataX作为中间传输载体负责连接各种数据源&#xff0c;通过reader从一个数据源读取数据&#xff0c;再通过writer将数据写入另一个数据源。…

Hadoop运行模式

hgfhfg Hadoop运行模式包括&#xff1a;本地模式、伪分布式模式以及完全分布式模式。 Hadoop官方网站&#xff1a;Apache Hadoop 一、本地运行模式 官方Grep案例 1. 创建在hadoop-2.7.2文件下面创建一个input文件夹 mkdir input 2. 将Hadoop的xml配置文件复制到input cp et…

FTP替代产品方案的优异性体现在哪些方面?

多年来&#xff0c;FTP一直是最常见的交换文件的方式&#xff0c;FTP-FTPS-SFTP似乎是FTP的不断迭代更新&#xff0c;但是究竟是技术更新导致FTP过时&#xff1f;还是它真的已经满足不了企业的需求了&#xff1f; 之前&#xff0c;大家选择FTP往往是因为它简单易得的特性&…

在 MySQL 中模拟外部联接 (LEFT、RIGHT、INNER JOIN、OUTER JOIN)

上周的文章详细介绍了 SELECT 查询中的外部联接。它是一种 JOIN 类型&#xff0c;可以从相关表中返回匹配和不匹配的行。遗憾的是&#xff0c;并非所有数据库&#xff08;DB&#xff09;供应商都支持它&#xff0c;包括 MySQL。但这没关系&#xff0c;因为可以通过组合其他三种…

【Java】构造方法及类的初始化

一. 利用构造方法给对象初始化 1. 构造方法的概念 构造方法(也称为构造器)是一个特殊的成员方法&#xff0c;其名字必须与类名相同&#xff0c;在创建对象时&#xff0c;由编译器自动调用&#xff0c;并且在整个对象的生命周期内只调用一次。 构造方法的作用就是给对象中的成…

心知天气api接口怎么用?

心知天气是什么&#xff1f;心知天气提供API吗&#xff1f; 心知天气是国内领先的气象服务商&#xff0c;由中国气象局官方授权的商业气象服务公司&#xff0c;基于气象数值预报和人工智能技术&#xff0c;提供高精度气象数据、天气监控机器人、气象数据可视化产品&#xff0c…

基于PHP+MySQL美食分享网站的设计与实现(含论文)

本系统是一个基于PHP和MySQL的美食分享网站,在本网站中用户可以通过注册登录来查看其他人分享的美食,查看周边好吃的店铺,分享和管理自己的美食,并且可以对他人分享的美食进行评论等一系类操作,通过这些操作可以让大家更加愉快的就美食进行交流 通过上图我们可以看到美食网站的…

Web APIs——DOM

JS 的组成 Web API Web API 是浏览器提供的一套操作浏览器功能和页面元素的 API ( BOM 和 DOM )。 现阶段我们主要针对于浏览器讲解常用的 API , 主要针对浏览器做交互效果。 比如我们想要浏览器弹出一个警示框&#xff0c; 直接使用 alert(‘弹出’) MDN 详细 API : https://d…

如何在Github精准地搜索项目

文章目录1、Github的项目有什么组成&#xff1f;2、如何搜索&#xff1f;in:name 条件in:readme 条件in:description 条件language:条件pushed: 条件stars: 条件awesome 关键字3、查看阅读项目https://blog.csdn.net/qq_45069279/article/details/107809617 https://blog.csdn.…

[附源码]SSM计算机毕业设计高校教师教学助手系统的设计与实现JAVA

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

将Nacos注册到springboot使用以及Feign实现服务调用

哈喽~大家好&#xff0c;这篇来看看将Nacos注册到springboot使用以及Feign实现服务调用。 &#x1f947;个人主页&#xff1a;个人主页​​​​​ &#x1f948; 系列专栏&#xff1a;【微服务】 &#x1f949;推荐专栏&#xff1a; JavaEE框架 目录 …