android内核lua解释器,从零开始实现 Lua 解释器之 AST

警告⚠️:这将是一个又臭又长的系列教程,教程结束的时候,你将拥有一个除了性能差劲、扩展性差、标准库不完善之外,其他方面都和官方相差无几的 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 中,产出下图所示的抽象语法树:

98f26097e2b6

AST 图例

由上图可知,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 如下所示:

98f26097e2b6

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 的账号就更好啦,我也会更加有动力将这个系列写下去。:)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值