警告??:這將是一個(gè)又臭又長的系列教程,教程結(jié)束的時(shí)候,你將擁有一個(gè)除了性能差勁、擴(kuò)展性差、標(biāo)準(zhǔn)庫不完善之外,其他方面都和官方相差無幾的 Lua 語言解釋器。說白了,這個(gè)系列的教程實(shí)現(xiàn)的是一個(gè)玩具語言,僅供學(xué)習(xí),無實(shí)用性。請謹(jǐn)慎 Follow,請謹(jǐn)慎 Follow,請謹(jǐn)慎 Follow。
這是本系列教程的第六篇,如果你沒有看過之前的文章,請從頭觀看。
前言
語法分析算是 SLua 解釋器中相對復(fù)雜的一個(gè)模塊了,為了避免篇幅過長,我們將分成多篇來講解,本文是第二部分。
上一節(jié)說到了 parseStatement 的實(shí)現(xiàn),現(xiàn)在我們就來分析一下 SLua 中到底有幾種類型的 Statement:
- Do Statament,這個(gè)語句的作用是將一個(gè) Block 封裝成一個(gè)單獨(dú)的語句。類似于 C 語言中將一段代碼放在一對大括號中,主要用于控制局部變量的作用域。
- While Statement
- If Statement
- Local Statement,局部變量聲明語句。
- Other Statement,全局變量聲明及變量賦值語句,沒錯(cuò),SLua 中的全局變量聲明語句和變量賦值語句的形式是一樣的。
解析 Do Statement
Do Statement 的解析代碼如下:
func (p *Parser) parseDoStatement() syntax.SyntaxTree {
p.nextToken()
assert(p.currentToken.Category == scanner.TokenDo, "not a do statement")
block := p.parseBlock()
if p.nextToken().Category != scanner.TokenEnd {
panic(&Error{
module: p.module,
token: p.currentToken,
str: "expect 'end' for 'do' statement",
})
}
return &syntax.DoStatement{Block: block}
}
首先,我們使用 assert 函數(shù)來確保當(dāng)前 Token 的類型為 TokenDo;然后調(diào)用 parseBlock 函數(shù)來解析 Do Statement 內(nèi)部的 Block;緊接著,在解析完成后,我們檢查下一個(gè) Token 的類型是否為 TokenEnd,如果不是,則拋出錯(cuò)誤。
怎么樣,很簡單吧。其他的語句的解析也跟這個(gè)類似,就像流水線一樣,一步接一步,直到解析完成,將各部分的結(jié)果組裝起來,得到我們需要的東西。
解析 While Statement
在解析之前,我們先來看一下之前在介紹抽象語法樹時(shí)提到過的 WhileStatement 的定義:
type WhileStatement struct {
Exp SyntaxTree
Block SyntaxTree
}
可以看到,While Statement 包含兩部分,Exp 為用于判斷循環(huán)是否繼續(xù)的表達(dá)式,Block 則是循環(huán)體。解析代碼如下:
func (p *Parser) parseWhileStatement() syntax.SyntaxTree {
p.nextToken()
assert(p.currentToken.Category == scanner.TokenWhile,
"not a while statement")
exp := p.parseExp()
if p.nextToken().Category != scanner.TokenDo {
panic(&Error{
module: p.module,
token: p.currentToken,
str: "expect 'do' for 'while' statement",
})
}
block := p.parseBlock()
if p.nextToken().Category != scanner.TokenEnd {
panic(&Error{
module: p.module,
token: p.currentToken,
str: "expect 'end' for 'while' statement",
})
}
return &syntax.WhileStatement {
Exp: exp,
Block: block,
}
}
首先,與解析 Do Statement 相同,首先我們通過調(diào)用 assert 保證當(dāng)前 Token 的類型為 TokenWhile;然后調(diào)用 parseExp 解析緊接著 TokenWhile 后面的循環(huán)表達(dá)式;正常情況下,表達(dá)式后面會跟著 TokenDo,所以,如果不是的話,我們就需要報(bào)告語法錯(cuò)誤然后退出程序;緊跟在 TokenDo 之后的是一個(gè)做為函數(shù)體的 Block,我們調(diào)用之前定義好的 parseBlock 函數(shù)來解析它;最后,我們需要判斷緊跟在 Block 之后的是否為 TokenEnd,如果不是,則需要報(bào)告語法錯(cuò)誤。
parseExp 函數(shù)的實(shí)現(xiàn)是整個(gè)語法分析最復(fù)雜的部分,我會在后面的文章中詳細(xì)地講解它,目前只需要知道它用于解析表達(dá)式就行了。
解析 If Statement
相比于上面介紹的 Do Statement 和 While Statement,If Statement 的結(jié)構(gòu)較為復(fù)雜,它可以包含零到多個(gè) Elseif 分支和零或一個(gè) Else 分支,所以將整個(gè)解析過程放在一個(gè)函數(shù)中無疑會大大增加復(fù)雜度,所以我們將解析函數(shù)拆分成了多個(gè)函數(shù)。
我們在第四篇文章中介紹過 If Statement 的結(jié)構(gòu):

由上圖可以看到,If Statement 包含三個(gè)部分:Exp、True Branch、False Branch,其中 True Branch 為表達(dá)式求值為真的時(shí)候執(zhí)行的 Block,而 FalseBranch 可以是 Elseif Statement 或 Else Statement 或 nil。Elseif Statement 的構(gòu)成與 If Statement 相同,Else Statement 則只包含一個(gè) Block。
下面先來看一下 If Statement 的實(shí)現(xiàn):
func (p *Parser) parseIfStatement() syntax.SyntaxTree {
p.nextToken()
assert(p.currentToken.Category == scanner.TokenIf, "not a if statement")
exp := p.parseExp()
if p.nextToken().Category != scanner.TokenThen {
panic(&Error{
module: p.module,
token: p.currentToken,
str: "expect 'then' for 'if' statement",
})
}
trueBranch := p.parseBlock()
falseBranch := p.parseFalseBranchStatement()
return &syntax.IfStatement{
Exp: exp,
TrueBranch: trueBranch,
FalseBranch: falseBranch,
}
}
首先,自然是判斷當(dāng)前的 Token 類型是否為 TokenIf;然后調(diào)用 parseExp 解析緊隨其后的表達(dá)式;然后判斷下一個(gè) Token 的類型是否為 TokenThen,如果不是,則報(bào)告語法錯(cuò)誤;然后我們調(diào)用 parseBlock 來解析 True Branch,調(diào)用 parseFalseBranchStatement 解析 False Branch。
上面說過,F(xiàn)alse Branch 有三種情況,容易想到,parseFalseBranchStatement 方法會根據(jù)下一個(gè) Token 的類型是 TokenElseif、TokenElse 還是 TokenEnd 來執(zhí)行不同的動(dòng)作,下面就來看一下具體的實(shí)現(xiàn):
func (p *Parser) parseFalseBranchStatement() syntax.SyntaxTree {
if p.lookAhead().Category == scanner.TokenElseif {
return p.parseElseifStatement()
} else if p.lookAhead().Category == scanner.TokenElse {
return p.parseElseStatement()
} else if p.lookAhead().Category == scanner.TokenEnd {
p.nextToken()
} else {
panic(&Error{
module: p.module,
token: p.lookAheadToken,
str: "expect 'end' for 'if' statement",
})
}
return nil
}
parseFalseBranchStatement 方法的實(shí)現(xiàn)很容易懂,這里就不再詳細(xì)解釋了。因?yàn)?Elseif Statement 與 If Statement 的形式完全相同,所以解析它的方法的實(shí)現(xiàn)也基本相同,而 Else Statement 則只是簡單地調(diào)用一個(gè) parseBlock 方法就可以了,也不再細(xì)說,這兩個(gè)方法的實(shí)現(xiàn)在下面:
func (p *Parser) parseElseifStatement() syntax.SyntaxTree {
p.nextToken()
assert(p.currentToken.Category == scanner.TokenElseif,
"not a 'elseif' statement")
exp := p.parseExp()
if p.nextToken().Category != scanner.TokenThen {
panic(&Error{
module: p.module,
token: p.currentToken,
str: "expect 'then' for 'elseif' statement",
})
}
trueBranch := p.parseBlock()
falseBranch := p.parseFalseBranchStatement()
return &syntax.ElseifStatement{
Exp: exp,
TrueBranch: trueBranch,
FalseBranch: falseBranch,
}
}
func (p *Parser) parseElseStatement() syntax.SyntaxTree {
p.nextToken()
assert(p.currentToken.Category == scanner.TokenElse,
"not a 'else' statement")
block := p.parseBlock()
if p.nextToken().Category != scanner.TokenEnd {
panic(&Error{
module: p.module,
token: p.currentToken,
str: "expect 'end' for 'else' statement",
})
}
return &syntax.ElseStatement{Block: block}
}
本節(jié)就先說到這里,在下一節(jié)中,我們將繼續(xù)講解解析 Local Statement 和 Other Statement 的方法實(shí)現(xiàn)。
獲取源代碼
代碼已托管到 GitHub 上:SLua,每一個(gè)階段的代碼我都會創(chuàng)建一個(gè) release,你可以直接下載作為參照。雖然提供了源代碼,但并不建議直接復(fù)制粘貼,因?yàn)檫@樣學(xué)到的知識會很容易忘記。
如果你覺得這篇教程有幫助,請不要吝嗇給文章點(diǎn)個(gè)喜歡,我會更加有動(dòng)力將這個(gè)系列寫下去。關(guān)注一下簡書和 GitHub 可以在第一時(shí)間獲得更新提醒哦。