自制前端框架 Day3. 编译抽象语法树

前两天可以通过传入一个整数,拿到token和AST了。今天打算把AST编译一下,得到一个函数。
还是利用昨天提到的责任链模式,把一个新的compiler对象和之前的Lexer以及ASTBuilder对象结合起来,这样也就终于有了一个编译原理提到的三步走流程:

  1. 词法分析
  2. 构建抽象语法树
  3. 编译得到目标结果
function Compiler(astBuilder){
  this.astBuilder = astBuilder;
}
Compiler.prototype.compile = function (expression) {
  this.astBuilder.ast(expression);
  //return function 这里需要return一个function。
};

关于为什么要返回这个函数在前面也写了很详细了,这里我再提醒自己一下:比如我运行Compiler.compile('233'),那么我需要返回的函数是这个函数:

function (){
    return 233;
}

显然,这个返回的函数内容是和表达式相关的,那么如何返回一个动态内容的函数就是我面临的一个问题,幸好有angular源码,angular的做法是,利用Function构建方法,先试试这个构建方法看看效果:

describe("Function构造方法",function(){
  it('试试Function构造方法',function () {
    var FnA = new Function('return 233;');
    var value = FnA();
    expect(value).toBe(233);
  })
})

上面这个案例经过测试是通过的。这也就说明,我想动态生成一个函数,只要调用Function构造方法并且往里面传入一些字符串就行了。
思路有了就开始干,首先要写一个方法来遍历AST树,反正看到遍历树就肯定想到递归方法,那就写个递归方法:

Compiler.prototype.recurse=function(ast){
  switch (ast.type) {
    case ASTBuilder.Program:
      //这里暂时没想好
      break;
    case ASTBuilder.Literal:
      return ast.value;
      break;
  }
}

为上面的代码做一个简单说明:

  1. type==Literal的节点肯定是一个叶节点,所以遇到这种节点就可以直接返回叶节点的value(因为Literal类型的节点肯定有一个value值)
  2. 下一步应该建立一个临时数组,让每次解析出来的实际内容都放到数组中,然后把这个数组转换成string,传入Function构造方法,从而得到一个函数。
Compiler.prototype.compile = function (expression) {
  this.state = {body:[]};
  this.astBuilder.ast(expression);
};

接着完成递归方法:

Compiler.prototype.recurse=function(ast){
  switch (ast.type) {
    case ASTBuilder.Program:
      this.state.body.push('return ',this.recurse(ast.body),' ;');
      break;
    case ASTBuilder.Literal:
      return ast.value;
      break;
  }
}

完成compile方法:

Compiler.prototype.compile = function (expression) {
  this.state = {body:[]};
  var ast = this.astBuilder.ast(expression);
  this.recurse(ast);
};

好了,现在完成了,全部代码是这样:

function Compiler(astBuilder){
  this.astBuilder = astBuilder;
}
Compiler.prototype.compile = function (expression) {
  this.state = {body:[]};
  var ast = this.astBuilder.ast(expression);
  this.recurse(ast);
};
Compiler.prototype.recurse=function(ast){
      switch (ast.type) {
        case ASTBuilder.Program:
          this.state.body.push('return ',this.recurse(ast.body),' ;');
          break;
        case ASTBuilder.Literal:
          return ast.value;
          break;
      }
}

测试案例走起来试试:

describe("compiler对象",function() {
  it("可以编译一个整数",function(){
    var expression = '233';
    var lexer = new Lexer();
    var astbuilder = new ASTBuilder(lexer);
    var compiler = new Compiler(astbuilder);
    var FnA = compiler.compile(expression);
    expect(FnA()).toBe(233);
  })
})

报错了,没通过,检查一下,原来是忘了return一个函数。加上,然后试试:

Compiler.prototype.compile = function (expression) {
  this.state = {body:[]};
  var ast = this.astBuilder.ast(expression);
  this.recurse(ast);
  return Function(this.state.body.join(' '));
};

现在测试通过了:

Paste_Image.png

到这里,就可以得到一个最初的目标了:一个可以编译整数的表达式编译器。

你可能感兴趣的:(自制前端框架 Day3. 编译抽象语法树)