记录ast 学习过程

240 阅读1分钟

跟随课程了解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 令牌信息如下:

1.jpg

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 结构

2.jpg

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节点,处理我们传入的回调函数 enterexit 操作

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);
}

打印信息

3.jpg

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

打印结果如下

4.jpg

完工~