详解AST抽象语法树

更新时间:2023-07-04 14:43:12 阅读: 评论:0

详解AST抽象语法树
浅谈 AST
先来看⼀下把⼀个简单的函数转换成AST之后的样⼦。
// 简单函数
function square(n) {
return n * n;
}
// 转换后的AST
{
type: "FunctionDeclaration",
id: {
type: "Identifier",
鞋带的系法图解name: "square"
},
params: [
{
type: "Identifier",
name: "n"
}
],
...
}
从纯⽂本转换成树形结构的数据,每个条⽬和树中的节点⼀⼀对应。
纯⽂本转AST的实现
当下的编译器都做了纯⽂本转AST的事情。
⼀款编译器的编译流程是很复杂的,但我们只需要关注词法分析和语法分析,这两步是从代码⽣成AST的关键所在。
第⼀步:词法分析,也叫扫描scanner
它读取我们的代码,然后把它们按照预定的规则合并成⼀个个的标识 tokens。同时,它会移除空⽩符、注释等。最后,整个代码将被分割进⼀个 tokens 列表(或者说⼀维数组)。
const a = 5;
// 转换成
[{value: 'const', type: 'keyword'}, {value: 'a', type: 'identifier'}, ...]
当词法分析源代码的时候,它会⼀个⼀个字母地读取代码,所以很形象地称之为扫描 - scans。当它遇
到空格、操作符,或者特殊符号的时候,它会认为⼀个话已经完成了。
第⼆步:语法分析,也称解析器
它会将词法分析出来的数组转换成树形的形式,同时,验证语法。语法如果有错的话,抛出语法错误。
[{value: 'const', type: 'keyword'}, {value: 'a', type: 'identifier'}, ...]
// 语法分析后的树形形式
{
type: "VariableDeclarator",
id: {
type: "Identifier",
name: "a"
},
...
}
当⽣成树的时候,解析器会删除⼀些没必要的标识 tokens(⽐如:不完整的括号),因此 AST 不是 100% 与源码匹配的。
解析器100%覆盖所有代码结构⽣成树叫做CST(具体语法树)。
⽤例:代码转换之babel
babel 是⼀个 JavaScript 编译器。宏观来说,它分3个阶段运⾏代码:解析(parsing) — 将代码字符串转换成 AST抽象语法树,转译(transforming) — 对抽象语法树进⾏变换操作,⽣成(generation) — 根据变换后的抽象语法树⽣成新的代码字符串。
我们给 babel ⼀段 js 代码,它修改代码然后⽣成新的代码返回。它是怎么修改代码的呢?没错,它创建了 AST,遍历树,修改 tokens,最后从 AST中⽣成新的代码。
详解 AST
前⾔
抽象语法树(AST),是⼀个⾮常基础⽽重要的知识点,但国内的⽂档却⼏乎⼀⽚空⽩。
本⽂将带⼤家从底层了解AST,并且通过发布⼀个⼩型前端⼯具,来带⼤家了解AST的强⼤功能
Javascript就像⼀台精妙运作的机器,我们可以⽤它来完成⼀切天马⾏空的构思。
我们对javascript⽣态了如指掌,却常忽视javascript本⾝。这台机器,究竟是哪些零部件在⽀持着它运⾏?
AST在⽇常业务中也许很难涉及到,但当你不⽌于想做⼀个⼯程师,⽽想做⼯程师的⼯程师,写出vue、react之类的⼤型框架,或类似webpack、vue-cli前端⾃动化的⼯具,或者有批量修改源码的⼯程需求,那你必须懂得AST。AST的能⼒⼗分强⼤,且能帮你真正吃透javascript的语⾔精髓。
事实上,在javascript世界中,你可以认为抽象语法树(AST)是最底层。 再往下,就是关于转换和编译的“⿊魔法”领域了。
⼈⽣第⼀次拆解Javascript
⼩时候,当我们拿到⼀个螺丝⼑和⼀台机器,⼈⽣中最令⼈怀念的梦幻时刻便开始了:
我们把机器,拆成⼀个⼀个⼩零件,⼀个个齿轮与螺钉,⽤巧妙的机械原理衔接在⼀起…
当我们把它重新照不同的⽅式组装起来,这时,机器重新⼜跑动了起来——世界在你眼中如获新⽣。
通过抽象语法树解析,我们可以像童年时拆解玩具⼀样,透视Javascript这台机器的运转,并且重新按着你的意愿来组装。
现在,我们拆解⼀个简单的add函数
function add(a, b) {
return a + b
}
⾸先,我们拿到的这个语法块,是⼀个FunctionDeclaration(函数定义)对象。
⽤⼒拆开,它成了三块:
⼀个id,就是它的名字,即add
两个params,就是它的参数,即[a, b]
⼀块body,也就是⼤括号内的⼀堆东西
add没办法继续拆下去了,它是⼀个最基础Identifier(标志)对象,⽤来作为函数的唯⼀标志,就像⼈的姓名⼀样。{
name: 'add'
type: 'identifier'
...
}
params继续拆下去,其实是两个Identifier组成的数组。之后也没办法拆下去了。
[
{
name: 'a'
type: 'identifier'
...
},
{
name: 'b'
type: 'identifier'
...
}
]
接下来,我们继续拆开body
我们发现,body其实是⼀个BlockStatement(块状域)对象,⽤来表⽰是{return a + b}
打开Blockstatement,⾥⾯藏着⼀个ReturnStatement(Return域)对象,⽤来表⽰return a + b
继续打开ReturnStatement,⾥⾯是⼀个BinaryExpression(⼆项式)对象,⽤来表⽰a + b
继续打开BinaryExpression,它成了三部分,left,operator,right
operator 即+
left ⾥⾯装的,是Identifier对象 a
right ⾥⾯装的,是Identifer对象 b
就这样,我们把⼀个简单的add函数拆解完毕,⽤图表⽰就是
看!抽象语法树(Abstract Syntax Tree),的确是⼀种标准的树结构。
那么,上⾯我们提到的Identifier、Blockstatement、ReturnStatement、BinaryExpression, 这⼀个个⼩部件的说明书去哪查?送给你的AST螺丝⼑:recast
输⼊命令:
npm i recast -S
你即可获得⼀把操纵语法树的螺丝⼑
接下来,你可以在任意js⽂件下操纵这把螺丝⼑,我们新建⼀个par.js⽰意:
揠苗助长告诉我们什么道理
par.js
// 给你⼀把"螺丝⼑"——recast
const recast = require("recast");
// 你的"机器"——⼀段代码
尖椒毛豆// 我们使⽤了很奇怪格式的代码,想测试是否能维持代码结构
const code =
`
function add(a, b) {
return a +
// 有什么奇怪的东西混进来了
b
}
`
// ⽤螺丝⼑解析机器
const ast = recast.par(code);
// ast可以处理很巨⼤的代码⽂件
/
/ 但我们现在只需要代码块的第⼀个body,即add函数
const add  = ast.program.body[0]
分离的英文console.log(add)
表格怎么画斜线在所难免输⼊node par.js你可以查看到add函数的结构,与之前所述⼀致,通过AST对象⽂档可查到它的具体属性:FunctionDeclaration{
type: 'FunctionDeclaration',
马克塞尔比id: ...
params: ...
body: ...
有爱有家}
你也可以继续使⽤console.log透视它的更内层,如:
console.log(add.params[0])
console.log(add.body.body[0].argument.left)
⼀个机器,你只会拆开重装,不算本事。

本文发布于:2023-07-04 14:43:12,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/89/1067557.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:代码   语法   机器   抽象   对象   函数   结构
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图