- 作用:词法分析器(
Tokenizer
)负责将输入的原始字符串(比如"10 - 3 * 2"
)分解成一个个有意义的最小单元,即令牌(Token). - 设计:
Tokenizer
实现了Iterator
trait,这让它可以像一个流一样,按需惰性地生成令牌.它会处理数字、运算符和括号等,并跳过空格.scan_number
方法负责连续读取数字,而scan_operator
则负责识别单个字符的运算符.
- 作用:
Token
枚举定义了所有可能的令牌类型.每个令牌不仅代表一个符号,还包含了重要的元数据. - 设计:
is_operator()
:简单判断一个令牌是否为运算符.certain_priority()
:返回每个运算符的优先级.例如,乘除的优先级高于加减.associativity()
:定义运算符的结合性,即计算方向(从左到右或从右到左).例如,加减是左结合的,幂运算(^)是右结合的.compute()
:执行具体的计算操作.
-
作用:核心的解析逻辑,它使用 Pratt 解析算法,一个基于运算符优先级的递归下降解析器.
-
设计:
-
Expr
结构体持有Tokenizer
的可偷看迭代器(Peekable Iterator),这使得在不消耗令牌的情况下检查下一个令牌成为可能,这是 Pratt 算法的关键. -
compute_atom()
:这是递归的基石.它只处理最基本的“原子”表达式:单个数字或被括号包围的子表达式.当遇到括号时,它会递归调用compute_expression
来处理括号内的内容. -
compute_expression()
:这是核心的循环和递归部分.它遵循以下逻辑:- 先获取左侧的原子值.
- 进入一个循环,检查下一个令牌.
- 如果下一个令牌是优先级足够高的运算符,它会递归调用自身来处理右边的子表达式.
- 通过给递归调用传入更高的最小优先级(
min_precedence
),确保了高优先级的运算先于低优先级运算被处理. - 对于左结合的运算符(如
+
),它会增加优先级,强制递归调用提前返回,从而让当前的循环先完成计算,确保从左到右的顺序.
eval()
:作为公共入口,它调用compute_expression
并检查整个表达式是否被完全解析,防止多余的字符.
-
整个设计通过 “职责分离”(词法分析器负责生成令牌,解析器负责组合令牌)和 “相互递归”(compute_atom
和 compute_expression
相互调用)的方式,优雅地实现了对复杂表达式的求值.