回顾思路
昨天把思路缕了一下。假设这样编译这样一个字符串parse('user.student.name')
,那首先就是期望传入的scope对象有一个user属性。user属性有一个student属性。student属性有一个name属性。事实上操作起来也是这样一个流程:
- 先取scope.user属性。
- 再取scope.user.student属性。
- 再取scope.user.student.name属性。
再看一下生成的tokens。这tokens很简单,就是五个:user
, .
,student
,.
,name
。
面对这些tokens的时候,AST的编译过程是这样:
- 生成program。
- 进入primary。
- 遇到的userToken是identifier类型,生成identifier节点
{type:ASTBuilder.Identifier,name:'user'}
,同时userToken被消耗掉。 - 遇到
.
token,产生子节点,同时把刚才的userToken生成的identifier节点设置为子节点的object。 - 设置完object以后去查找下一个token,把查到的token设置为子节点的property,也就是studentToken。
- 形成了新的AST
type:ASTBuilder.MemberExpression,
object:{
type:ASTBuilder.Identifier,
name:'user'
},
property:{
type:ASTBuilder.Identifier,
name:'student'
}
}
7 . studentToken被消耗,下一个又遇到了.
token。再次产生子节点。每个子节点都有object属性和property属性。然后把刚才的AST设置为新AST的object,把下一个nameToken设置为新AST的property。
8.AST构建完成。
编译这个AST逻辑稍微有点深,需要好好总结一下思路。
首先要取到scope.user。这时候就要去挖到最深的节点。最深的节点肯定是一个Ident类型的节点,将传入的scope和节点的name属性加在一起,赋给一个变量,返回这个变量。就是
scope.user
赋给一个变量,返回这个变量。所以这里需要创建一个变量,假设为变量v拿到变量v以后,返回到解析子节点部分,这个v就是一个object,将它和property结合,再赋值给一个变量,也就是说
v.student
赋值给一个变量,假设是v1。返回v1。拿到v1,函数栈还是在子节点部分,现在是最外层的,把v1和name结合,赋值给一个变量,假设是v2.返回v2。
综上所述,事实上在编译子节点和identifier类型的节点时候,都需要产生变量,无非是进入函数就产生变量,还是拿到值以后在产生变量。
正题开始了
其实前几天虽然没发文章,但是还是做了一些工作的,写了一些简单的代码,这部分先跳过,以后再补。
总之想要编译这样的代码
it('方括号找属性', function() {
var fn = parse('student["name"]');
expect(fn({student:{name:'wangji'}})).toBe('wangji');
});
之前是遇到.
这个token的时候产生子节点,现在还要加上,遇到[
这个token也应该产生子节点。为了实现这个目标,先把peek和expect方法改造一下:
ASTBuilder.prototype.peek = function (e1,e2,e3,e4) {
if (this.tokens.length > 0) {
var text = this.tokens[0].text;
if(text===e1||text===e2||text===e3||text===e4||(!e1&&!e2&&!e3&&!e4)){
return this.tokens[0];
}
}
}
ASTBuilder.prototype.expect = function (e1,e2,e3,e4) {
var token = this.peek(e1,e2,e3,e4);
if(token){
return this.tokens.shift();
}
}
然后修改代码
ASTBuilder.prototype.primary = function () {
var primary;
if (ASTBuilder.Constants.hasOwnProperty(this.tokens[0].text)) {
primary = ASTBuilder.Constants[this.consume().text];
} else if (this.expect("[")) {
primary = this.arrayDeclaration();
} else if (this.expect("{")) {
primary = this.object()
} else if (this.peek().identifier) {
primary = this.identifier()
} else {
primary = this.constant();
}
var next;
while ((next = this.expect('.', '['))) {
if (next.text === "[") {
primary = {
type: ASTBuilder.MemberExpression,
object: primary,
property: this.primary(),
computed:true
}
this.consume(']');
} else {
primary = {
type: ASTBuilder.MemberExpression,
object: primary,
property: this.identifier(),
computed:false
}
}
}
return primary;
}
现在脑补一下整个过程,'student["name"]'
这个被分成若干个token,student
,[
,name
,]
,其实在生成token的时候,这个name就被解析成了constant类型的token。所以在后面生成AST的时候,如果发现是[
这种token就把下一个token从primary流程走一遍,因为有可能是constant也有可能是identifier。(student['name']或者student[name]).
这样一来,成功生成AST。进入compile阶段。
目前的compile阶段,右值的产生是通过identifier流程,因为用点分割的右值都是确定的identifier。可是用方括号分割的就不确定了,就像刚才说的,有可能是constant,有可能是iden。所以还要写一个ifelse。
case ASTBuilder.MemberExpression:
intoId = this.nextId();
var left = this.recurse(ast.object);
if(ast.computed){
var right = this.recurse(ast.property);
this.if_(left,this.assign(intoId,this.nonComputedMember(left,right)));
}else{
this.if_(left, this.assign(intoId, this.nonComputedMember(left, ast.property.name)))
}
return intoId;
之前的nonComputedMember方法已经不够了,需要一个computedMember方法:
Compiler.prototype.computedMember=function(left,right){
return '('+left+')'+'['+right+']';
}
编译成功。