本文将实现最后一个需求: 格式化表达式
源码: src/traversal/format.rs
实现目标
将这个表达式 "1*2+(3/(4+(-5)))"
处理成 "1 * 2 + 3 / (4 + (-5))"
也就是说
- 去除冗余括号
- 只保留必要括号
- 在操作符左右插入空格
写一下
Formatter 定义
像前面的 eval
一样, 这里直接定义一个结构体来承载逻辑
pub struct Formatter {
output: String,
}
impl Formatter {
pub fn new() -> Self {
Formatter { output: String::new() }
}
pub fn format(&mut self, node: &Node) -> &str {
// 随后就会实现 Visitor Trait
self.visit(node);
self.output.as_str()
}
}
工具函数实现
考虑到格式化会需要插入一些固定的符号, 如空格, 括号之类的, 因此先来简单实现一些工具函数
impl Formatter {
fn push(&mut self, str: &str) {
self.output.push_str(str)
}
// 插入一个空格
fn ws(&mut self) {
self.push(" ");
}
}
Visitor 实现
下面来正式实现格式化逻辑, 比较简单, 直接贴代码
// 不需要返回值, 因此传入 ()
impl Visitor<()> for Formatter {
// Literal 直接插入
fn visit_num(&mut self, _: i32, raw: &str) {
self.push(raw)
}
fn visit_expr(&mut self, left: &Node, op: &str, right: &Node) {
// 优先访问左子树
self.visit(left);
// 插入空格
self.ws();
// 插入操作符
self.push(op);
// 插入空格
self.ws();
// 访问右子树
self.visit(right);
}
}
处理括号
这里简单的分析一下, 何时需要添加括号
第一反应想到的就是操作数是一个负数 "1 + (-2)"
, 这种情况下括号不可省略, 但是如果第一个操作数是负数呢 "-1 + 2"
, 这种情况下括号可省略, 综合一下, 我们的第一种情况得到了
- 右子树是一个负数
第二种情况就需要思考一下括号的语义了, 在数学四则运算中, 括号表示提高表达式的计算优先级, 意为优先计算, 而我们在 Parser
阶段本就会根据计算优先级来构建 AST, 也就是说在 AST 中本来就已经隐含了计算优先级的问题, 也就是 节点所处深度越大, 优先级越高
但是如果碰到如下图中的 AST 呢
很显然, 这不符合常理, 因为加法的优先级就应该比乘法要低, 但是这里为什么加法节点的深度要比乘法节点的深度大呢
因为这里 AST(Abstract Syntax Tree) abstract 掉了一对括号, 这棵 AST 的表达式应该是 "1 * (2 + 3)"
发现了这点就很容易得出第二个情况了, 在这种情况下我们必须添加括号
- 右子树的符号的优先级小于当前符号的优先级
以下是代码的具体实现
impl Formatter {
// 将给定表达式包裹在括号中
fn push_paren_expr(&mut self, node: &Node) {
// 先添加括号
self.push("(");
// 在访问节点
self.visit(node);
self.push(")");
}
}
impl Visitor<()> for Formatter {
// 数字依然直接插入
fn visit_num(&mut self, _: i32, raw: &str) {
self.push(raw)
}
fn visit_expr(&mut self, left: &Node, op: &str, right: &Node) {
// 左子树依然直接访问
self.visit(left);
self.ws();
// 符号依然直接插入
self.push(op);
self.ws();
// 访问右子树前先做个判断
match right {
// 若当前符号的优先级大于右子树符号的优先级
Node::Expr { op: next_op, .. }
if SyntaxKind::get_op_priority(op)
> SyntaxKind::get_op_priority(next_op.into_str()) =>
{
// 把右子树裹在括号里
self.push_paren_expr(right);
}
// 若右子树是个负数
Node::Literal { raw, .. } if raw.chars().nth(0) == Some('-') => {
// 把右子树裹在括号里
self.push_paren_expr(right);
}
// 其他情况不需要特殊处理
_ => {
self.visit(right);
}
}
}
}
跑一下
确实是没有问题
#[test]
fn smoke_test() {
let mut f = Formatter::new();
assert_eq!(
"1 * 2 + 3 / (4 + (-5))",
f.format(&get_node("1*2+(3/(4+(-5)))"))
)
}
模块导出
源码: src/traversal/mod.rs
如下
pub fn eval(root: &Node) -> i32 {
Executor::new().eval(root)
}
pub fn format(root: &Node) -> String {
Formatter::new().format(root).to_string()
}