阅读 817

抽象语法树-AST初探

释义

an abstract syntax tree ( AST ), or just syntax tree , is a tree representation of the abstract syntactic structure of source code written in a programming language . Each node of the tree denotes a construct occurring in the source code。
—— wiki百科

AST,即抽象语法树,是对源代码语法结构的一种树状抽象表示,每个节点对应源代码中的一段代码语句,是一种平台无关的数据描述。

如何生成

我们平常所写的代码本质上是一种结构化的指令文本,是高级程序设计语言,是出于人们理解阅读而设计的。但处理器只能识别一连串的二进制机器代码,对于文本代码是无法直接识别运行的。而编译器作为桥梁打通了两者,他的作用是将源代码翻译成语义等价的目标代码,机器码和AST是常见的编译结果。

处理过程

在将源码转换成AST过程中,一般会经过三步:词法分析、语法分析、语义分析。

词法分析

将字符序列转换为标记(token)序列的过程,也叫tokenize,主要由词法分析器(lexical analyzer)来扫描处理。分析器会逐个读取字符,按照对应编程语言规则和关键词,将代码文本切分成一个个以token为元素的数组,并区分出对应的token类型。 举个生活🌰

我兴高采烈地走回家 ==> 我、兴高采烈、地、走回、家
复制代码

以javascript:以下js代码会被切分成下面的token序列

const num = 1;
console.log(num);
复制代码
[
    {type:'identifier',value:'const'},
    {type:'whitespace',value:' '},    
    {type:'identifier',value:'num'},
    {type:'whitespace',value:' '},
    {type:'operator',value:'='},
    {type:'whitespace',value:' '},
    {type:'num',value:'1'},
    ......
]
复制代码

根据上面的结果可知,token切分方式一般有以下几类:

  • 关键字

  • 标识符

  • 操作符

  • 字面量

  • 标点符号

语法分析

语法分析 (英语:syntactic analysis,也叫 parsing)是根据某种给定的 形式文法 对由单词序列(如英语单词序列)构成的输入文本进行分析并确定其语法结构的一种过程 ——wiki百科

生活🌰:

我、兴高采烈、地、走回、家 ==> 我(主)、兴高采烈地(状)、走回(谓)、家(宾) 
满足主谓宾定状补结构 ✅
复制代码

编程语言里,各类语句也有固定语法: 条件语句包含 if关键字、判断条件、执行块 声明语句包含 声明关键字、标识符 ......

若代码存在语法问题SyntaxtError 会在这步直接抛出来,并中断整个编译过程

确定语法结构无误后,编译器按照语法规则将 扁平的token列表,组合成一个个声明语句节点、表达式节点,最终形成嵌套结构的语法树

可以看到很多多余token都被删除掉,比如:= ;等,AST的抽象也体现在这里,很多代码写法细节都被隐藏掉,只用节点类型去说明对应代码的含义。

语义分析

语义分析是对语法结构上正确的源程序进行上下文前后相关性的检查,检查程序的前后语义是否一致。 生活🌰:

// 正常语境下
我兴高采烈地走回家 ✅
我兴高采烈地滚回家 ❌
复制代码

对于编程语言来说,语义分析一般有类型检查、作用域分析等等。 多数静态语言 如java、c等都会有这一步检查; javascript作为一种动态解释型语言,没有类型检查,在语法分析阶段一般能够获取完整的AST

AST 详细展示

astexplorer.net/#/gist/38a8…

实际应用

AST在babel的应用

业界对babel处理流程的总结:

Webpack DCE

Webpack一个常见的场景是分环境打包编译,通过DefinePlugin插件,注入环境变量,在编译过程,相关变量token被替换成注入的变量值,并在后续处理过程中,结合AST进行DCE(dead code elimination),多余开发代码被删除简化。

// 原始index.js
if (process.env.NODE_ENV === 'production') {
  // 生产代码
  console.log('Welcome to production');
}
if (process.env.DEBUG) {
  // 开发调试代码
  console.log('Debugging output');
}
console.log('running');

// webpack.config.js
new webpack.DefinePlugin({
  'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
  'process.env.DEBUG': false,
});

// 环境变量注入
if ('production' === 'production') {
  // 生产代码
  console.log('Welcome to production');
}
if (false) { //
  // 开发调试代码
  console.log('Debugging output');
}
console.log('running');

// DCE处理后
console.log('Welcome to production');

console.log('running');
复制代码

对于Tree shaking,本质上也是对冗余代码进行删除简化,但和DCE相比处理的粒度有所不同,DCE更多是对代码逻辑层面的删减,而Tree shaking是对引用模块间的去冗余。 除此之外,Tree shaking是借助了ES6模块语法的静态导入特性进行模块依赖分析,从而实现对多余模块删减

H5 与小程序

近几年随着微信小程序推广普及,各个平台都推出了自身类小程序的开放技术。由于缺乏相关标准,各平台小程序都实现了自己的一套DSL,变成新时代『浏览器兼容问题』。不过仔细了解各平台小程序的语法和技术特征,都是模板+业务逻辑的,和vue的模板语言十分接近。 微信小程序:

<!-- 数据绑定 -->
<view> {{ message }} </view>
<!-- 条件渲染 -->
<view wx:if="{{condition}}"> nice day! </view>
<!-- 列表渲染 -->
<view wx:for="{{[0, 1, 2, 3, 4]}}"> {{item}} </view>
复制代码
Page({
  data: {
    condition: true,
    message: 'heelo word',
  }
})
复制代码

H5 (Vue2.0):

<!-- 数据绑定 -->
<div> {{ message }} </div>
<!-- 条件渲染 -->
<div v-if="condition"> nice day! </div>
<!-- 列表渲染 -->
<ul>
  <li v-for="item in items" :key="item.message">
    {{ item.message }}
  </li>
</ul>
复制代码
var example1 = new Vue({
  el: '#example-1',
  data: {
    items: [0, 1, 2, 3, 4],
    condition: true,
    message: 'heelo word',
  }
})
复制代码

基于这样的背景,逐渐诞生了各类跨平台的小程序框架,其中一类是编译时框架,如Taro2.0,通过将源码解析成AST,进一步转换成各平台所支持的代码

参考链接:

用JS解释JS!详解AST及其应用

AST in Modern JavaScript

浅谈对AST的理解——动手写写Babel插件

在 2020 年,谈小程序框架该如何选择

文章分类
前端