Rust 实现一个表达式 Parser

1,075 阅读2分钟

首先感谢 Eve 大手子绘制的封面~

阅读之前, 读者应该拥有以下基础

  1. 阅读代码的能力
  2. 阅读 Rust 代码的能力

本文目的

  1. 避开生涩枯燥的理论, 尽量用大白话简单分享编译相关知识
  2. 太久没写博客了, 写个专栏玩玩

引言

笔者的毕设做了一个代码格式化工具, 期间学习了一些编译原理相关的知识, 写个专栏来分享一下

由于完整的代码格式化工具体量很大, 且机械性的重复逻辑很多, 核心部分其实非常简单, 因此选择以精简过后的四则运算表达式解析器作为本专栏的项目

需求如下:

  • 0 外部依赖
  • 对疑似表达式的字符串进行解析, 若为合法输入则进行以下处理
    • 求值
      • 支持计算求值
      • 支持正负数
      • 支持括号提升运算优先级
    • 格式化
      • 去除冗余符号
      • 操作符左右添加空格

举例如下

合法输入: "1*(+2+-3)"
求值输出: -1
格式输出: "1 * (2 + (-3))"

非法输入: "01 + 1"
非法输入: "1+++1"

成果展示

源码传送门, 目前已经完成了上述需求, 下面的测试将顺利通过

#[test]
fn smoke() {
    let expr = "1*2+(3/(4+(-5)))";
    let ast = build_ast(expr).unwrap();

    assert_eq!(-1, eval(&ast));
    assert_eq!("1 * 2 + 3 / (4 + (-5))", format(&ast));
}

目录

  1. 前置干货
  2. 整体设计
  3. 类型定义
  4. dfa 实现
  5. lexer 实现
  6. 文法编写及优化
  7. parser combinator 简单封装
  8. parser 实现
  9. 访问者实现
  10. 求值功能实现
  11. 格式化功能实现
  12. 总结

Q&A

Q: 为什么要用 Rust?

A: 个人兴趣

Q: 你不是前端仔么, 为什么捣鼓编译?

A: 个人兴趣

Q: 为什么非得要 0 外部依赖

A: 没有到非得追求性能和可靠性的时候, 还是想更多的把握实现细节