编程多年,你知道实现自己喜欢的语法多简单吗。

lxf2023-02-17 01:52:18

实现的一个方便自己的语法,必须先了解ast(抽象语法树),当然这个并不是很难的东西,只是一个树型结构,因为纯粹的文本不方便去分析代码结构,转成一颗树就方便我们去进行分析啦。

ast树就长这样

编程多年,你知道实现自己喜欢的语法多简单吗。

有了ast树可以做什么呢,可以去通过分析,实现代码压缩,代码转换,代码分析,等等啦,这就开看看我们要实现的一个简单例子

//我们希望可以执行的代码
//应该看起来还是比较好理解把
function log(fn,args){
  console.log(fn.name,'开始执行');
  fn(args);
  console.log(fn.name,'开始结束');
}
@wrap(log)
function so(args){
  console.log(args);
}
//转换之后可以运行代码
function log(fn, args) {
  console.log(fn.name, 开始执行);
  fn(args);
  console.log(fn.name, 开始结束);
}
let so = args => log(function (args) {
  console.log(args);
}, args);

js目前装饰器是用在类上,上面的例子是作用在函数上,那么就需要我们去实现一个, 那么解析ast这个工作我们就需要请出我们的主角acorn.js来帮我们进行翻译(这是一个小巧强大的解释器,作用在了很多开源库上)。

acorn使用起来也是非常的简单

//这样就可以获取需要的ast了
const acorn = require("acorn");
const ast = acorn.parse(`console.log('good')`,{})
console.log(ast);

但是acorn只理解标准的js语法,我们自定义的语法并不理解,所以我们需要去对其进行扩展,

const acorn = require("acorn");
//继承acorn的parse实现然后我们只需要对特定函数进行重写就要可以达到扩展的目的
const parse = acorn.Parser.extend(Parser => {
  return class extends Parser {
    //在这里重写对应的方法
  }
})
const ast = parse.parse(`console.log('good')`,{})
console.log(ast);

在重写解析函数之前我们需要先创建类似关键字也就是对应的分词,有了分词主要让acorn知道这个符号不是乱写的,是有意义的。

//比如这样就创建的一个分词的类型
const atToken = new acorn.TokenType("@");

接下来附上基本的代码

const parse = acorn.Parser.extend((Parser) => {
  const skipWhiteSpace = /(?:\s|\/\/.*|\/\*[^]*?\*\/)*/g;
  const whiteSpace = (acorn) => {
    skipWhiteSpace.lastIndex = acorn.pos;
    const skip = skipWhiteSpace.exec(acorn.input);
    return skip[0].length;
  };
  //判断是否at开头的函数
  const isAtFunction = (acorn) => {
    const next = acorn.pos + whiteSpace(acorn);
    return (
      acorn.type.label === "@" && acorn.input.slice(next, next + 4) === "wrap"
    );
  };
  const atToken = new acorn.TokenType("@");
  return class extends Parser {
    //acorn解析token时需要执行函数在这里让acorn知道我们的符号是有意义的
    readToken(code) {
      if (code === 64) {
        ++this.pos;
        return this.finishToken(atToken);
      }
      return super.readToken(code);
    }
    parseStatement(context, topLevel, exports) {
      if (isAtFunction(this)) {
        //消耗掉当前的token符号
        this.eat(atToken);
        const wrap = [];
        let isAsync = false;
        //一直获取到时function类型的时候停止
        while (this.type !== acorn.tokTypes._function) {
          if (this.isContextual("async")) {
            isAsync = true;
            //迭代到下一个token
            this.next();
            break;
          }
          wrap.push(this.parseExpression(null, null));
          this.eat(atToken);
        }
        //解析当前函数
        const functionNode = this.parseFunctionStatement(
          this.startNode,
          isAsync,
          !context
        );
        //把at的信息添加到一个我们自己定义的对象中保存
        functionNode.wrap = wrap;
        return functionNode;
      }
      //如果不是at开头的函数按正常去解析
      return super.parseStatement(context, topLevel, exports);
    }
  };
});

编程多年,你知道实现自己喜欢的语法多简单吗。

有了这些数据之后我们可以通过各种支持转换ast的库来帮助进行ast转换转换为标准可以执行的ast,因为我们现在的ast属性wrap是自定义的,标准化之后就可以用来生成可以执行的代码。

//这里是使用babel因为babel用起来简单嘛
const buildHoc = (wrap, replaceAst, id) => {
  const buildWrap = babel.template`let FUNCTIONNAME = (args)=> WRAPAST`;
  while (wrap.length) {
    const current = wrap.shift();
    const args = [
      replaceAst,
      ...current
        .get("arguments")
        .slice(1)
        .map((v) => v.node),
    ];
    replaceAst = babel.types.callExpression(current.get("arguments.0").node, [
      ...args,
      babel.types.identifier("args"),
    ]);
  }
  return buildWrap({
    FUNCTIONNAME: id,
    WRAPAST: replaceAst,
  });
};

const transitionCode = (code) => {
  const ast = {
    type: "File",
    program: parse.parse(code, {}),
  };
  babel.traverse(ast, {
    FunctionDeclaration(path) {
      const wrap = path.get("wrap");
      if (Array.isArray(wrap)) {
        const node = path.node;
        let id = node.id;
        delete node.wrap;
        node.id = null;
        node.type = "FunctionExpression";
        path.replaceWith(buildHoc(wrap, node, id));
      }
    },
  });
  return generate(ast);
};

好了,具体就是这样,完整代码点这里

我叫zr,新人一枚,请多多关照哈