跟随课程了解ast的实现过程并且实战,做一个记录,方便以后深入学习babel。
实现一个输入函数到输出函数的解析
// test.js
const parser = require('./index-xjm.js');
// 输入
const input = "(add 20 (subtract 4 3))";
// 输出
const output = "add(20, subtract(4, 2))";
console.log(parser(input));
1、需要一个 index-xjm.js 文件去做转化工作
// index-xjm.js
function generateToken(input){
let current = 0;
let tokens = [];
while (current < input.length) {
let char = input[current];
if (char =='(' || char==')' ){
tokens.push({
type: 'paren',
value:char
})
current++;
continue;
}
// 如果是空格继续解析
if (/\s/.test(char)){
current++
continue;
}
// 如果是字符串解析为一个字符
if (/[a-z]/.test(char)){
let charValue = ''
while(/[a-z]/.test(input[current])){
charValue += input[current++]
}
tokens.push({
type: 'name',
value: charValue
})
continue;
}
// 如果是数字解析为一个数字及其类型
if (/[0-9]/.test(input[current])){
let numberValue = ''
while(/[0-9]/.test(input[current])){
numberValue += input[current++]
}
tokens.push({
type: 'number',
value: numberValue
})
continue;
}
// 这里忘记写不是这些类型的判断
throw new TypeError("未能识别的分词字符");
}
return tokens
}
// 转换函数
function parser(input){
// 分词
const token = generateToken(input)
console.log("🚀 ~ file: index-xjm.js ~ line 43 ~ parser ~ token", token)
}
module.exports = parser
打印信息,解析的token 令牌信息如下:
2、ast结构函数
function generateAST(tokens) {
let current = 0;
// 初始化一个抽象语法结构,tokens 的内容都放入body体中
let ast = {
type: 'Program',
body: []
}
while(current < tokens.length){
//TODO walk() 根据类别放入body
ast.body.push(walk())
}
return ast
}
完善walk
//处理参数及函数关系的ast结构数据
function walk(){
// 当前的令牌
let token = tokens[current]
// 处理number类型
if (token.type == 'number') {
current++;
return {
type: 'NumberLiteral',
value: token.value,
}
}
// 处理函数体以(开始
if (token.type == 'paren' && token.value == '(') {
// 读取下一个token 'add'
token = tokens[++current];
// 定义函数
let node = {
type: 'CallExpression',
name: token.value, //eg: add 函数名
params: [],
}
// 读取下一个字符
token = tokens[++current];
// 循环不为结束符号)的,都为函数里面的参数
while(
(token.type != 'paren')
|| (token.type == 'paren' && token.value !==')')
){
// 可能有嵌套的参数需要walk 处理
node.params.push(walk())
// params递归的条件
token = tokens[current];
}
current++;
return node;
}
}
打印出来ast 结构
3、继续把函数转化为可以执行的ast 对象
function transformer(ast){
let newAst = {
type: 'Program',
body: [],
}
// 旧ast 的_context 属性上挂载新的body 内容
ast._context = newAst.body;
// 深度遍历 ast,做新旧节点转化
DSF(ast, {
"NumberLiteral": {
enter(node, parent) {
// 进入旧的节点,遍历到 NumberLiteral 类型,则给新的节点加 number 类型
parent._context.push({
type: 'NumberLiteral',
value: node.value
})
}
},
// 遍历到 CallExpression 类型,则转化为对应的ast格式
"CallExpression": {
enter(node, parent) {
let expression = {
type: 'CallExpression',
callee: {
type: 'Identifier',
name: node.name,
},
arguments: []
}
// 处理arguments
node._context = expression.arguments;
if (parent.type !== "CallExpression") {
expression = {
type: 'ExpressionStatement',
expression: expression,
}
}
parent._context.push(expression);
}
}
})
return newAst;
}
需要一个辅助函数 DSF
深度遍历ast节点,处理我们传入的回调函数 enter
及 exit
操作
function DSF(ast, visitor){
function traverseArray(children, parent){
children.forEach(child => tranversNode(child, parent))
}
// 执行enter 函数及 exit 函数
function tranversNode(node, parent){
let methods = visitor[node.type];
if (methods && methods.enter) {
// 执行enter 函数
methods.enter(node, parent)
}
switch(node.type) {
case 'Program':
traverseArray(node.body, node)
break;
case 'CallExpression':
traverseArray(node.params, node);
break;
case 'NumberLiteral':
break
}
// 如果有退出函数做退出时候的回调
if(methods && methods.exit) {
methods.exit(node, parent);
}
}
// 转化节点,初始节点是顶级节点没有parent
return tranversNode(ast, null);
}
打印信息
4、生成代码
// 生成代码类型
function generate(ast) {
switch(ast.type) {
case "Program":
return ast.body.map(subAst => generate(subAst));
case "ExpressionStatement":
return generate(ast.expression)+';';
case "CallExpression": return generate(ast.callee) + "(" + ast.arguments.map(arg => generate(arg)).join(', ') + ")";
case "Identifier": return ast.name;
case "NumberLiteral": return ast.value;
}
}
最终引入代码:
// 转换函数
function parser(input){
// 分词
const tokens = generateToken(input)
// 生成抽象语法树
const ast = generateAST(tokens);
// 转化为正常的语法树
const newAst = transformer(ast);
// 基于新的语法树生成代码
const code = generate(newAst);
return code
}
module.exports = parser
打印结果如下
完工~