前两天可以通过传入一个整数,拿到token和AST了。今天打算把AST编译一下,得到一个函数。
还是利用昨天提到的责任链模式,把一个新的compiler对象和之前的Lexer以及ASTBuilder对象结合起来,这样也就终于有了一个编译原理提到的三步走流程:
- 词法分析
- 构建抽象语法树
- 编译得到目标结果
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;
}
}
为上面的代码做一个简单说明:
-
type==Literal
的节点肯定是一个叶节点,所以遇到这种节点就可以直接返回叶节点的value(因为Literal类型的节点肯定有一个value值) - 下一步应该建立一个临时数组,让每次解析出来的实际内容都放到数组中,然后把这个数组转换成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(' '));
};
现在测试通过了:
到这里,就可以得到一个最初的目标了:一个可以编译整数的表达式编译器。