抽象语法树,简称:AST
,全称:Abstract Asytax Tree
地址:https://astexplorer.net/
在上述的编译器中编写一个方法,对应的可以看见它生成的一个树状结构的数据,我们称之为抽象语法树,当然这只是JavaScript的AST。
在前端开发过程,应该听说过或者使用babel
,eslint
等,本文在这里就不过多赘述。本文的在这边介绍一款简易的AST;
github地址:https://github.com/jamiebuilds/the-super-tiny-compiler
当然有一定知识储备的兄弟,还是建议去看源码。
以(add 2 (subtract 4 2))
为例:
- [ 词法分析 ]
[
{ type: 'paren', value: '(' },
{ type: 'name', value: 'add' },
{ type: 'number', value: '2' },
{ type: 'paren', value: '(' },
{ type: 'name', value: 'subtract' },
{ type: 'number', value: '4' },
{ type: 'number', value: '2' },
{ type: 'paren', value: ')' },
{ type: 'paren', value: ')' },
]
- [ 语法分析 ]
{
type: 'Program',
body: [{
type: 'CallExpression',
name: 'add',
params: [{
type: 'NumberLiteral',
value: '2',
}, {
type: 'CallExpression',
name: 'subtract',
params: [{
type: 'NumberLiteral',
value: '4',
}, {
type: 'NumberLiteral',
value: '2',
}]
}]
}]
}
语法分析之后就已经生成一个简易的AST了。但是还是没有结束,如果我想在分析完成之后,把4转化成xxx怎么办?所以还有下一步
- [ 转换处理 ]
var visitor = {
NumberLiteral(node, parent) {
if(node.value==='4'){
//处理xxx
}
},
CallExpression(node, parent) {},
};
它的进出方式走完一个节点,进入下一个节点,一进一出的方式
-> Program (enter)
-> CallExpression (enter)
-> Number Literal (enter)
<- Number Literal (exit)
-> Call Expression (enter)
-> Number Literal (enter)
<- Number Literal (exit)
-> Number Literal (enter)
<- Number Literal (exit)
<- CallExpression (exit)
<- CallExpression (exit)
<- Program (exit)
* Original AST | Transformed AST
* ----------------------------------------------------------------------------
* { | {
* type: 'Program', | type: 'Program',
* body: [{ | body: [{
* type: 'CallExpression', | type: 'ExpressionStatement',
* name: 'add', | expression: {
* params: [{ | type: 'CallExpression',
* type: 'NumberLiteral', | callee: {
* value: '2' | type: 'Identifier',
* }, { | name: 'add'
* type: 'CallExpression', | },
* name: 'subtract', | arguments: [{
* params: [{ | type: 'NumberLiteral',
* type: 'NumberLiteral', | value: '2'
* value: '4' | }, {
* }, { | type: 'CallExpression',
* type: 'NumberLiteral', | callee: {
* value: '2' | type: 'Identifier',
* }] | name: 'subtract'
* }] | },
* }] | arguments: [{
* } | type: 'NumberLiteral',
* | value: '4'
* ---------------------------------- | }, {
* | type: 'NumberLiteral',
* | value: '2'
* | }]
* (sorry the other one is longer.) | }
* | }
* | }]
* | }
对比处理前和处理后,发现AST的内容进行丰富
- [ 代码生成 ]
输出的结果是预期可运行的结构
其实babel的大体结构也是如此,遍历解析(词法分析、语法分析),遍历转换,最后代码生成
graph TD
Parse --> Transform --> Generator
总结一下,其实我们写的代码,严格意义上只是字符串,各种框架脚本之所以能够运行,是通过既定的规则解析,把它转化为AST,最后根据宿主环境生成可运行的代码。前端的跨端也是如此。