1.AST
1.1 AST是什么?
AST,抽象语法树。
1.2 最常用的抽象语法树是什么?
babel eslint
1.3 为什么要用AST?
抽象语法树的核心目的是翻译。
1.4 举个例子🌰
中文: 我是小杜
英文: i am xiaodu
这其实就是一个翻译的过程。细分有以下步骤:
1. 句子 -> 单词 (这其实就是一个词法分析 lexical analysis)
2. 单词 -> 逐词翻译 (语法分析 syntacyic analysis)
3. 润色 (简单来说就是数据结构翻译成另一个数据结构 ast -> newAst)transform
4. 合并生成产物
根据以上,再结合前端知识不难想到:
<div>
<p>
<span123></span>
</p>
</div>
====解析之后:
{
tag:div
children: {
tag: p,
children: {
tag: span,
children: 123
}
}
}
=====以上其实就是一个解析的过程
1. input -> tokenizer -> token 词法分析
2. token -> parser -> ast 语法分析
3. ast -> transform -> newAst 代码转换
4. newAst -> generate -> output 生成代码过程
2. 实现一个简单的编译器Compiler
定义以下的输入输出:
const input = '(add 2 (subtract 4 5))'
const output = '(add 2 (subtract 4 5))'
2.1 词法分析
就是一个将输入的字符串拆成toke的过程。
2.1.1 分析过程
function tokenizer(input){
const tokens = [];
let current = 0;
while(current < input.length){
let char = input[current];
if(char === '(' || char === ')'){
tokens.push({
type: 'paren',
value: char
})
current++;
continue;
}
// 空格跳过
let WHITESPACE = /\s/;
if(WHITESPACE.test(char)){
current++;
continue;
}
// 处理数字
let NUMBERS = /[0-9]/;
if(NUMBERS.test(char)){
let value = '';
while(NUMBERS.test(char)){
value += char;
char = input[++current];
}
tokens.push({
type: 'number',
value
})
continue;
}
// 处理字符串
let LETTERS = /[a-z]/i;
if(LETTERS.test(char)){
let value = '';
while(LETTERS.test(char)){
value += char;
char = input[++current];
}
tokens.push({
type: 'name',
value
})
continue;
}
throw new TypeError('Unknown character: ' + char);
}
return tokens;
}
==== 调用测试:
const input = '(add 2 (subtract 4 5))'
const tokens = tokenizer(input);
console.log(tokens)
2.1.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: '5' },
{ type: 'paren', value: ')' },
{ type: 'paren', value: ')' }
]
2.2 语法分析
通过递归的方式把语法分析之后生成的tokens,转化为一个有层级结构的数据。
2.2.1 分析过程
function parser(tokens){
let current = 0;
function walk(){
let token = tokens[current];
if(token.type === 'number'){
current++;
return {
type: 'NumberLiteral',
value: token.value
}
}
if(token.type === 'name'){
current++;
return {
type: 'Identifier',
name: token.value
}
}
if(token.type === 'paren' && token.value === '('){
token = tokens[++current];
let node = {
type: 'CallExpression',
name: token.value,
params: []
}
token = tokens[++current];
while(token.type !== 'paren' || (token.type === 'paren' && token.value !== ')')){
node.params.push(walk()); // 递归
token = tokens[current];
}
current++;
return node;
}
throw new TypeError(token.type);
}
let ast = {
type: 'Program',
body: []
}
while(current < tokens.length){
ast.body.push(walk());
}
return ast;
}
====调用测试:
需要结合第一步的tokenizer
const input = '(add 2 (subtract 4 5))'
const tokens = tokenizer(input);
const ast = parser(tokens);
console.log(tokens)
console.log(JSON.stringify(ast))
2.2.2 分析结果
{
"type": "Program",
"body": [
{
"type": "CallExpression",
"name": "add",
"params": [
{
"type": "NumberLiteral",
"value": "2"
},
{
"type": "CallExpression",
"name": "subtract",
"params": [
{
"type": "NumberLiteral",
"value": "4"
},
{
"type": "NumberLiteral",
"value": "5"
}
]
}
]
}
]
}
2.3 代码转换
其实类似于构建函数的钩子
2.3.1 转换过程
// webpack plugin 执行钩子 visitor 观察者模式
function traverser(ast, visitor){
// parent 类似context上下文
function traverserArray(array, parent){
array.forEach(child => {
traverserNode(child, parent);
})
}
function traverserNode(node, parent){
let methods = visitor[node.type];
// 执行钩子函数
if(methods && methods.enter){
methods.enter(node, parent);
}
switch(node.type){
case 'Program':
traverserArray(node.body, node);
break;
case 'CallExpression':
traverserArray(node.params, node);
break;
case 'NumberLiteral':
break;
default:
throw new TypeError(node.type);
}
if(methods && methods.exit){
methods.exit(node, parent);
}
}
traverserNode(ast, null);
}
function transformer(ast){
let newAst = {
type: 'Program',
body: []
}
ast._context = newAst.body;
traverser(ast, {
NumberLiteral: {
enter(node, parent){
parent._context.push({
type: 'NumberLiteral',
value: node.value
})
}
},
CallExpression: {
enter(node, parent){
let expression = {
type: 'CallExpression',
callee: {
type: 'Identifier',
name: node.name,
},
arguments:[]
}
node._context = expression.arguments;
if(parent.type !== 'CallExpression'){
expression = {
type: 'ExpressionStatement',
expression: expression
};
}
parent._context.push(expression);
}
}
})
return newAst;
}
====调用测试:
const input = '(add 2 (subtract 4 5))'
const tokens = tokenizer(input);
const ast = parser(tokens);
const newAst = transformer(ast);
console.log(tokens)
console.log(JSON.stringify(ast))
console.log(JSON.stringify(newAst))
2.3.2 转换结果
{
"type": "Program",
"body": [
{
"type": "ExpressionStatement",
"expression": {
"type": "CallExpression",
"callee": {
"type": "Identifier",
"name": "add"
},
"arguments": [
{
"type": "NumberLiteral",
"value": "2"
},
{
"type": "CallExpression",
"callee": {
"type": "Identifier",
"name": "subtract"
},
"arguments": [
{
"type": "NumberLiteral",
"value": "4"
},
{
"type": "NumberLiteral",
"value": "5"
}
]
}
]
}
}
]
}
2.4 代码生成
将新的ast生成新的代码结构。
2.4.1 生成过程
function codeGenerator(node){
switch(node.type){
case 'Program':
return node.body.map(codeGenerator).join('\n');
case 'ExpressionStatement':
return codeGenerator(node.expression) + ';';
case 'CallExpression':
return (
codeGenerator(node.callee) + '(' + node.arguments.map(codeGenerator).join(', ') + ')'
)
case 'Identifier':
return node.name;
case 'NumberLiteral':
return node.value;
}
}
====调用测试:
const input = '(add 2 (subtract 4 5))'
const tokens = tokenizer(input);
const ast = parser(tokens);
const newAst = transformer(ast);
// console.log(tokens)
// console.log(JSON.stringify(ast))
// console.log(JSON.stringify(newAst))
console.log(codeGenerator(newAst))
2.4.2 生成结果
add(2, subtract(4, 5));