警告??:這將是一個又臭又長的系列教程,教程結束的時候,你將擁有一個除了性能差勁、擴展性差、標準庫不完善之外,其他方面都和官方相差無幾的 Lua 語言解釋器。說白了,這個系列的教程實現的是一個玩具語言,僅供學習,無實用性。請謹慎 Follow,請謹慎 Follow,請謹慎 Follow。
這是本系列教程的第四篇,如果你沒有看過之前的文章,請從頭觀看。
前言
從本節起,我們開始實現語法分析器,也就是一直被神化的所謂的 Parser。也許看完本系列教程之后,你就會覺得 Parser 并沒有那么難。關于 Parser,我不想解釋太多概念性的東西,如果你不了解它,請先看這篇文章:《談談Parser》 - 王垠,看完以后再繼續往下看。
在實現 Parser 之前,我們首先要定義抽象語法樹(Abstract Syntax Tree,AST),將詞法分析器生成的 Token 列表轉換為 AST 是 Parser 的主要任務。在 AST 中,每個節點都表示源代碼中的一種結構,之所以說它是抽象的,是因為它并沒有表示出真實語法中出現的每個細節,而是使用一種抽象的樹形結構來表現。
語法分析階段生成的 AST 中并沒有包含相應的語義信息,即上下文無關。
語義信息指的是上下文相關的信息,例如數字和 nil 不能相加,break 語句必須在某個循環之內,未定義的變量不能使用(在 Lua 中未定義的變量被看作 nil)等等。
在語義分析階段,相應的語義信息才會被加入到 AST 中,這時的 AST 也就變為上下文相關的了。
語法分析流程
舉個例子來說,下面這段程序:
local name, age = 'Bob', 18
age = age + 1
首先會被詞法分析器解析為下面 Token 列表:
類型 | 值 |
---|---|
TokenLocal | <空> |
TokenID | name |
TokenComma | <空> |
TokenID | age |
TokenAssign | <空> |
TokenString | Bob |
TokenComma | <空> |
TokenNumber | 18 |
TokenID | age |
TokenAssign | <空> |
TokenID | age |
TokenAdd | <空> |
TokenNumber | 1 |
然后這個列表被送入 Parser 中,產出下圖所示的抽象語法樹:
由上圖可知,AST 的節點有很多種類型組成,每種語法結構都會生成固定類型的子樹。下面我們具體解釋一下 AST 中節點的組成以及每個節點對應的源代碼。
AST 組成
首先,為了后續的方便,我們統一使用 SyntaxTree 來表示一切節點,而不是使用具體的類型。它的定義如下:
type SyntaxTree interface{}
Chunk
Chunk 是 AST 的根節點,用來表示整個程序,它在語法結構上和 Block 相似,但因為是根節點,所以并不能包含自身。它包含且僅包含一個 Block。
type Chunk struct {
Block SyntaxTree
}
Block
Block 表示一個代碼塊,它由零到多個語句組成。首先,整個源文件中的所有代碼被看作是一個 Block;其次,do ... end 語句、循環以及函數等內部包含的代碼塊也都是一個 Block。
type Block struct {
Stmts []SyntaxTree
}
DoStatement
用來表示 do ... end 語句。注意到,它包含一個 Block。
type DoStatement struct {
Block SyntaxTree
}
WhileStatement
表示 While 循環語句。其中 Exp 表示判斷循環是否繼續的表達式,Block 表示的是函數內部的代碼塊。
type WhileStatement struct {
Exp SyntaxTree
Block SyntaxTree
}
舉例:
while a < 10 do a = a + 1 end
IfStatement
用來表示 If 語句。其中 Exp 表示判斷條件是否成立的表達式,TrueBranch 表示條件成立時要執行的代碼塊,FalseBranch 則可能是 ElseifStatement、ElseStatement 或者為空(nil)
IfStatement struct {
Exp SyntaxTree
TrueBranch SyntaxTree
FalseBranch SyntaxTree
}
ElseifStatement
用來表示 Elseif 語句。其中 Exp 表示判斷條件是否成立的表達式,TrueBranch 表示條件成立時要執行的代碼塊,FalseBranch 則可能是 ElseifStatement、ElseStatement 或者為空(nil)
ElseifStatement struct {
Exp SyntaxTree
TrueBranch SyntaxTree
FalseBranch SyntaxTree
}
ElseStatement
用來表示 Else 語句。其中 Block 是要執行的代碼塊。
ElseStatement struct {
Block SyntaxTree
}
舉例:為了幫助讀者更好的理解 if ... elseif ... else ... end 語句生成的 AST 的樣子,我們舉個例子:
if (exp1) then block
elseif (exp2) then block
else block end
對于上面的代碼,生成的 AST 如下所示:
AST 實例
LocalNameListStatement
用來表示定義 Local 變量的語句。其中 NameList 表示的是欲定義的局部變量列表,ExpList 是賦值給前面的 NameList 的表達式列表。
LocalNameListStatement struct {
NameList SyntaxTree
ExpList SyntaxTree
}
舉例:
local a, b, c = 12, "abc", 3.14
AssignmentStatement
用來表示變量的賦值和全局變量的聲明。其中 VarList 表示欲賦值的(欲申請的全局)變量列表,ExpList 是賦值給前面的 VarList 的表達式列表。
AssignmentStatement struct {
VarList SyntaxTree
ExpList SyntaxTree
}
舉例:
a, b = "abc", 3.14
VarList
用于 AssignmentStatement 中的 VarList。
VarList struct {
VarList []SyntaxTree
}
Terminator
終結符,表示無法再往下分解最小的語法單元,AST 中的葉子節點。
Terminator struct {
Tok *scanner.Token
}
BinaryExpression
雙目運算符表達式。
BinaryExpression struct {
Left SyntaxTree
Right SyntaxTree
OpToken *scanner.Token
}
UnaryExpression
單目運算符表達式。
UnaryExpression struct {
Exp SyntaxTree
OpToken *scanner.Token
}
NameList
用于 LocalNameListStatement 中的 NameList。
NameList struct {
Names []*scanner.Token
}
ExpressionList
用于 LocalNameListStatement 和 AssignmentStatement 中的 ExpList。
ExpressionList struct {
ExpList []SyntaxTree
}
至此,構建 AST 所需要的類型已經全部完成了。你可以在此查看完整的源代碼:地址。
獲取源代碼
代碼已托管到 Github 上:SLua,每一個階段的代碼我都會創建一個 release,你可以直接下載作為參照。雖然提供了源代碼,但并不建議直接復制粘貼,因為這樣學到的知識會很容易忘記。
剛開始玩 Github 和簡書,所以沒有任何粉絲和關注量(哭),如果你覺得這篇教程有幫助,請不要吝嗇給文章點個喜歡,給 Github 上的項目點個 Star。如果能 Follow 一下簡書和 Github 的賬號就更好啦,我也會更加有動力將這個系列寫下去。:)