pymatlab_interp 解释器开发教程第二部分: 表达式的语法分析。
pymatlab_interp 解释器开发教程 (2) 语法分析(表达式)
引言
上一个教程中已经完成了词法分析的实现,这个教程将会更近一步,实现一个表达式的语法分析器。
语法分析
在词法分析后,我们得到了一列有序的 Token
.
语法分析的目的就是分析这些 Token
之间的关系,构建抽象语法树(AST).
目前语法分析的方式非常多,这里我们选择最简单实用的方法实现-递归下降。
递归下降分析
递归下降分析(Recursive Descent Parsing)是一种自顶向下的解析技术,广泛应用于编译器设计中,用于识别源代码是否符合给定的文法规则。
在递归下降分析中,递归下降解析器从文法的最高层规则开始解析,逐步深入到更底层的规则,最终到达文法的终端符号。
它允许递归调用,即当文法中的规则允许某个非终结符引用自身时(无论是直接还是间接),解析器函数会递归地调用自己来处理这种情况。
递归下降分析的基本步骤包括:
-
解析输入串:从文法的开始符号开始,依优先级调用相应的函数来解析输入串。
-
匹配:调用每个函数尝试匹配输入串的一部分,并在必要时调用其他函数来完成整个匹配过程。 如果匹配成功,就消费掉匹配部分。
-
错误处理:如果在解析过程中发现输入串不符合文法规则,解析器会停止解析并报告错误。
语法分析器基本定义
基础的语法分析器(Parser)类接收一个词元(tokens)序列作为输入,并提供方法来遍历和解析这些词元。
语法分析器需要提供的方法如下:
基础方法:
- cur_token():返回当前词元。
- prev_token():返回前一个词元。
- is_end():检查是否已达到词元列表的末尾。
- advance():移动到下一个词元,并返回上一个词元。
词元检查方法:
- check(token_t):检查当前词元是否为指定类型。
- match_token_type(types):尝试匹配当前词元是否属于指定的一组类型之一,如果是,则前进到下一个词元。
- consume(token_t, message):如果当前词元类型匹配给定类型,则前进并返回当前词元;如果不匹配,则抛出错误并附带错误信息。
代码实现如下:
1 |
|
表达式
当前的语法分析要构建表达式树,必然要定义表达式节点。
需要定义构建表达式树的类如下,每个类代表了表达式树中的不同类型的节点。
-
Expr
基类,被其他具体表达式类继承。 包含了一个通用的字符串表示方法,一个accept
方法,用于实现访问者模式,这允许我们在不修改类的情况下,向表达式树添加新的操作。 -
Assign
:表示赋值表达式,包含一个左部变量名和一个右部的表达式。 -
Binary
:表示二元运算表达式,如加法、减法、乘法等。 -
Grouping
:表示括号分组表达式,用于改变运算优先级。 -
Literal
:表示字面量表达式,如数字、字符串或布尔值。 -
Logical
:表示逻辑运算表达式,如逻辑与(AND)、逻辑或(OR)。 -
Unary
:表示一元运算表达式,如负号、逻辑非。 -
Variable
:表示变量引用表达式。
这些类共同构成了一个简单的抽象语法树(Abstract Syntax Tree,AST)模型,用于表示源代码中的各种表达式结构。
下面是代码实现,保存在 auto_defs.py
中:
由于这些代码有结构化的特点,实际是用脚本代码自动生成的。
1 |
|
递归下降实现
现在是时候用递归下降来构建表达式树了。
递归下降解析器逐步构建表达式的抽象语法树。
以下每个函数专注于解析特定类型的语法结构,然后递归地调用更低级别的函数来解析子表达式。
当所有子表达式都被解析后,它们通过适当的节点类型(如Binary
、Unary
、Logical
等)组合起来,形成整个表达式的完整树状结构。
-
primary()
函数:这是解析基本表达式的入口点。它处理字面量(如数字、字符串、布尔值)、变量引用以及括号内的表达式分组。 例如,如果遇到数字123
,它会创建一个Literal
节点;如果遇到括号(...)
, 它会先解析括号内的表达式,然后创建一个Grouping
节点包裹这个表达式。 -
unary()
函数:处理一元运算符,如-
、+
、not
等。它首先检查是否遇到了一元运算符,如果是,则读取运算符,然后解析右侧的表达式,并创建一个Unary
节点。 -
factor()
函数:处理乘法和除法等二元运算符。它从unary()
开始解析,然后检查是否遇到了乘法或除法运算符,如果是,则创建一个Binary
节点,连接左右两侧的表达式。 -
term()
函数:处理加法和减法运算符,其工作原理类似于factor()
,但关注的是加减运算。 -
comparison()
、equality()
、and_equal()
和or_equal()
函数:分别处理比较运算符、等于和不等于运算符、逻辑与运算符和逻辑或运算符,采用与factor()
和term()
相似的策略,构建相应的Binary
或Logical
节点。
对于解析器,提供parse
方法作为为解析器的入口点,它直接调用expression
方法来开始解析过程。
代码实现如下:
1 |
|
parser测试
简单起见,直接在 __main__
里测试,代码如下:
1 |
|
运行结果如下,与原式对比,可以验证结果是正确的。
1 |
|
附录:parser.py 完整代码
1 |
|