回顧思路
昨天把思路縷了一下官册。假設(shè)這樣編譯這樣一個(gè)字符串parse('user.student.name')
,那首先就是期望傳入的scope對(duì)象有一個(gè)user屬性豁遭。user屬性有一個(gè)student屬性。student屬性有一個(gè)name屬性囚聚。事實(shí)上操作起來(lái)也是這樣一個(gè)流程:
- 先取scope.user屬性靖榕。
- 再取scope.user.student屬性。
- 再取scope.user.student.name屬性顽铸。
再看一下生成的tokens茁计。這tokens很簡(jiǎn)單,就是五個(gè):user
, .
,student
,.
,name
谓松。
面對(duì)這些tokens的時(shí)候星压,AST的編譯過(guò)程是這樣:
- 生成program践剂。
- 進(jìn)入primary。
- 遇到的userToken是identifier類型娜膘,生成identifier節(jié)點(diǎn)
{type:ASTBuilder.Identifier,name:'user'}
,同時(shí)userToken被消耗掉逊脯。 - 遇到
.
token,產(chǎn)生子節(jié)點(diǎn)竣贪,同時(shí)把剛才的userToken生成的identifier節(jié)點(diǎn)設(shè)置為子節(jié)點(diǎn)的object军洼。 - 設(shè)置完object以后去查找下一個(gè)token,把查到的token設(shè)置為子節(jié)點(diǎn)的property演怎,也就是studentToken匕争。
- 形成了新的AST
type:ASTBuilder.MemberExpression,
object:{
type:ASTBuilder.Identifier,
name:'user'
},
property:{
type:ASTBuilder.Identifier,
name:'student'
}
}
7 . studentToken被消耗,下一個(gè)又遇到了.
token爷耀。再次產(chǎn)生子節(jié)點(diǎn)甘桑。每個(gè)子節(jié)點(diǎn)都有object屬性和property屬性。然后把剛才的AST設(shè)置為新AST的object歹叮,把下一個(gè)nameToken設(shè)置為新AST的property扇住。
8.AST構(gòu)建完成。
編譯這個(gè)AST邏輯稍微有點(diǎn)深盗胀,需要好好總結(jié)一下思路艘蹋。
首先要取到scope.user。這時(shí)候就要去挖到最深的節(jié)點(diǎn)票灰。最深的節(jié)點(diǎn)肯定是一個(gè)Ident類型的節(jié)點(diǎn)女阀,將傳入的scope和節(jié)點(diǎn)的name屬性加在一起,賦給一個(gè)變量屑迂,返回這個(gè)變量浸策。就是
scope.user
賦給一個(gè)變量,返回這個(gè)變量惹盼。所以這里需要?jiǎng)?chuàng)建一個(gè)變量庸汗,假設(shè)為變量v拿到變量v以后,返回到解析子節(jié)點(diǎn)部分手报,這個(gè)v就是一個(gè)object蚯舱,將它和property結(jié)合,再賦值給一個(gè)變量掩蛤,也就是說(shuō)
v.student
賦值給一個(gè)變量枉昏,假設(shè)是v1。返回v1揍鸟。拿到v1兄裂,函數(shù)棧還是在子節(jié)點(diǎn)部分,現(xiàn)在是最外層的,把v1和name結(jié)合晰奖,賦值給一個(gè)變量谈撒,假設(shè)是v2.返回v2。
綜上所述匾南,事實(shí)上在編譯子節(jié)點(diǎn)和identifier類型的節(jié)點(diǎn)時(shí)候港华,都需要產(chǎn)生變量,無(wú)非是進(jìn)入函數(shù)就產(chǎn)生變量午衰,還是拿到值以后在產(chǎn)生變量。
正題開始了
其實(shí)前幾天雖然沒(méi)發(fā)文章冒萄,但是還是做了一些工作的臊岸,寫了一些簡(jiǎn)單的代碼,這部分先跳過(guò)尊流,以后再補(bǔ)帅戒。
總之想要編譯這樣的代碼
it('方括號(hào)找屬性', function() {
var fn = parse('student["name"]');
expect(fn({student:{name:'wangji'}})).toBe('wangji');
});
之前是遇到.
這個(gè)token的時(shí)候產(chǎn)生子節(jié)點(diǎn),現(xiàn)在還要加上崖技,遇到[
這個(gè)token也應(yīng)該產(chǎn)生子節(jié)點(diǎn)逻住。為了實(shí)現(xiàn)這個(gè)目標(biāo),先把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;
}
現(xiàn)在腦補(bǔ)一下整個(gè)過(guò)程迎献,'student["name"]'
這個(gè)被分成若干個(gè)token瞎访,student
,[
,name
,]
,其實(shí)在生成token的時(shí)候吁恍,這個(gè)name就被解析成了constant類型的token扒秸。所以在后面生成AST的時(shí)候,如果發(fā)現(xiàn)是[
這種token就把下一個(gè)token從primary流程走一遍冀瓦,因?yàn)橛锌赡苁莄onstant也有可能是identifier伴奥。(student['name']或者student[name]).
這樣一來(lái),成功生成AST翼闽。進(jìn)入compile階段拾徙。
目前的compile階段,右值的產(chǎn)生是通過(guò)identifier流程感局,因?yàn)橛命c(diǎn)分割的右值都是確定的identifier尼啡。可是用方括號(hào)分割的就不確定了询微,就像剛才說(shuō)的玄叠,有可能是constant,有可能是iden拓提。所以還要寫一個(gè)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方法已經(jīng)不夠了,需要一個(gè)computedMember方法:
Compiler.prototype.computedMember=function(left,right){
return '('+left+')'+'['+right+']';
}
編譯成功。