先写个parse函数和parser类把现有代码包装一下
今天打算先把之前的代码整理一下,写一个parser类,包装一下Lexer,ASTBuilder,Compiler类。
function Parser(){
var lexer = new Lexer();
var astbilder = new ASTBuilder(lexer);
this.compiler = new Compiler(astbilder);
}
Parser.prototype.parse = function(expression){
return this.compiler.compile(expression);
}
function parse(expression){
var parser = new Parser();
return parser.parse(expression);
}
这样以后调用就方便了,写个测试案例试试:
describe('用parse函数编译',function(){
it('编译整数',function() {
var fn = parse('233')
expect(fn()).toBe(233);
})
})
思路
如果遇到的字符串不是数字也不是引号,而是一个字符,就进入解析identity流程,这个identity就是说在编程中我们遇到的一些已经定义好的值,比如true,false等等,这种值都是确定的,所以叫identify(确定).
先写一个函数判断是不是遇到了identify的值。
Lexer.prototype.isIdent=function(char){
return ('a'<=char && char<='z')||('A'<=char&&char<='Z')||char==="_"||char==="$";
}
如果遇到这样的字符,就进入readIdent流程:
Lexer.prototype.lex=function(expression){
this.tokens=[];
this.text = expression;
this.index = 0;
while (this.index<this.text.length) {
var currentChar = this.text.charAt(this.index);
if(this.isNumber(currentChar)){
this.readNumber();
}else if (currentChar==="'"||currentChar==="\"") {
this.readString(currentChar);
}else if (this.isIdent(currentChar)) {
this.readIdent()
} else{
throw "现在只支持数字,不支持别的字符"
}
}
return this.tokens;
}
readIdent流程和其他的没什么不同,区别就是生成的token不一样,ident这种token没有值,因为值是已经被JS定义好的,只有text属性:
Lexer.prototype.readIdent = function () {
var ident="";
while(this.index<this.text.length){
var currentChar = this.text.charAt(this.index);
if(this.isIdent(currentChar)||this.isNumber(currentChar)){
ident+=currentChar;
this.index++;
}
}
this.tokens.push({
text:ident
})
};
现在虽然有了一个token,但是在构建语法树的时候还是不行,因为构建语法树现在的body是tokens[0].value。显然这个token是没有value的。所以可以完善一下构建语法树的部分了:program的body属性现在是直接调用constant方法,完善一下,program调用一个新方法,primary方法。这个方法的意图是,所有节点的主处理方法。就是说处理其他节点都会通过这个方法过一次。
代码写好:
ASTBuilder.constants={
'true':true,
'false':false,
'null':null
}
ASTBuilder.prototype.ast=function(expression){
this.tokens = this.lexer.lex(expression);
return this.program();
}
ASTBuilder.prototype.program=function(){
return {
type:ASTBuilder.Program,
body:this.primary()
}
}
ASTBuilder.prototype.primary=function(){
if(ASTBuilder.constants.hasOwnProperty(this.tokens[0].text)){
return {type:ASTBuilder.Literal,value:ASTBuilder.constants[this.tokens[0].text]}
}else{
return this.constant();
}
}
先写个测试案例跑一下试试:
it('编译true',function() {
var fn = parse('true')
expect(fn()).toBe(true);
})
it('编译false',function() {
var fn = parse('false')
expect(fn()).toBe(false);
})
it('编译null',function() {
var fn = parse('null')
expect(fn()).toBe(null);
})
编译null的时候出错了,跟一下看看怎么回事:
这下明白了,因为现在生成的函数是这样:
function(){
return ;
}
``
所以其实是什么都没返回,测试案例期待的null就不行。我应该让生成的函数是这样:
function(){
return null ;
}
这样只要修改一下escape方法就行了:
Compiler.prototype.escape=function(value){
if(webframe.isString(value)){
return "'"+
value.replace(this.stringEscapeReg,this.changeToUnicode)
+"'";
}else if (value===null) {
return 'null';
} else{
return value;
}
}
这就编译成功了:
![image.png](http://upload-images.jianshu.io/upload_images/839173-03ff761e4480264c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
今天已经开始写了这个东西第七天了,感觉进度还行,稳扎稳打,之前定的七个月,没准六个月就能完成呢。哈哈。