警告⚠️:这将是一个又臭又长的系列教程,教程结束的时候,你将拥有一个除了性能差劲、扩展性差、标准库不完善之外,其他方面都和官方相差无几的 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 如下所示:
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 的账号就更好啦,我也会更加有动力将这个系列写下去。:)