美文网首页自制前端框架Web前端之路让前端飞
自制前端框架 Day3. 编译抽象语法树

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

作者: 蚊子爸爸 | 来源:发表于2017-05-11 10:46 被阅读60次

    前两天可以通过传入一个整数,拿到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. 编译抽象语法树

        本文链接:https://www.haomeiwen.com/subject/togntxtx.html