使用Logos实现简易计算器:从词法分析到表达式求值
【免费下载链接】logos Create ridiculously fast Lexers 项目地址: https://gitcode.com/gh_mirrors/log/logos
还在为手写词法分析器而头疼?想要快速构建一个高性能的计算器应用?本文将带你使用Rust生态中的Logos库,从零开始实现一个完整的算术表达式计算器,涵盖词法分析、语法解析和表达式求值全流程。
通过本文,你将掌握:
- Logos词法分析器的核心用法和优势
- 如何定义复杂的Token枚举和正则匹配规则
- 与Chumsky解析器的无缝集成技巧
- 抽象语法树(AST)的设计与递归求值实现
- 完整的计算器项目架构和最佳实践
1. 项目概述与技术选型
1.1 为什么选择Logos?
Logos是一个Rust语言的词法分析器生成库,具有以下核心优势:
| 特性 | 优势描述 | 性能表现 |
|---|---|---|
| 编译时生成 | 零运行时开销,极致性能 | 1200+ MB/s处理速度 |
| 确定性状态机 | 无回溯,避免ReDoS攻击 | 稳定的时间复杂度 |
| 查找表优化 | 分支预测优化 | 比手写lexer快2-3倍 |
1.2 技术栈架构
2. 环境准备与项目设置
2.1 创建Rust项目
cargo new calculator-demo
cd calculator-demo
2.2 添加依赖配置
# Cargo.toml
[dependencies]
logos = "0.13"
chumsky = "1.0"
3. 词法分析器实现
3.1 Token枚举定义
use logos::Logos;
#[derive(Logos, Debug, PartialEq, Eq, Hash, Clone)]
#[logos(skip r"[ \t\n]+")] // 跳过空白字符
#[logos(error = String)] // 自定义错误类型
enum Token {
// 算术运算符
#[token("+")]
Plus,
#[token("-")]
Minus,
#[token("*")]
Multiply,
#[token("/")]
Divide,
// 括号
#[token("(")]
LParen,
#[token(")")]
RParen,
// 整数匹配与值提取
#[regex("[0-9]+", |lex| lex.slice().parse::<isize>().unwrap())]
Integer(isize),
}
3.2 Token匹配规则详解
| Token类型 | 匹配模式 | 返回值 | 示例输入 |
|---|---|---|---|
Plus | 字面量"+" | Token本身 | 1 + 2 |
Minus | 字面量"-" | Token本身 | 5 - 3 |
Multiply | 字面量"*" | Token本身 | 2 * 4 |
Divide | 字面量"/" | Token本身 | 8 / 2 |
Integer | 数字正则 | 解析后的整数值 | 123 → 123 |
4. 抽象语法树设计
4.1 AST节点定义
#[derive(Debug)]
enum Expr {
// 整数字面量
Int(isize),
// 一元负号操作
Neg(Box<Expr>),
// 二元运算操作
Add(Box<Expr>, Box<Expr>),
Sub(Box<Expr>, Box<Expr>),
Mul(Box<Expr>, Box<Expr>),
Div(Box<Expr>, Box<Expr>),
}
4.2 AST结构示例
对于表达式 1 + 2 * 3,生成的AST结构为:
5. 语法解析器实现
5.1 使用Chumsky构建解析器
use chumsky::prelude::*;
fn parser<'src>() -> impl Parser<'src, &'src [Token], Expr> {
recursive(|p| {
// 原子表达式(整数或括号表达式)
let atom = {
let parenthesized = p
.clone()
.delimited_by(just(Token::LParen), just(Token::RParen));
let integer = select! {
Token::Integer(n) => Expr::Int(n),
};
parenthesized.or(integer)
};
// 一元运算符处理
let unary = just(Token::Minus)
.repeated()
.foldr(atom, |_op, rhs| Expr::Neg(Box::new(rhs)));
// 乘除运算符(高优先级)
let binary_1 = unary.clone().foldl(
just(Token::Multiply)
.or(just(Token::Divide))
.then(unary)
.repeated(),
|lhs, (op, rhs)| match op {
Token::Multiply => Expr::Mul(Box::new(lhs), Box::new(rhs)),
Token::Divide => Expr::Div(Box::new(lhs), Box::new(rhs)),
_ => unreachable!(),
},
);
// 加减运算符(低优先级)
let binary_2 = binary_1.clone().foldl(
just(Token::Plus)
.or(just(Token::Minus))
.then(binary_1)
.repeated(),
|lhs, (op, rhs)| match op {
Token::Plus => Expr::Add(Box::new(lhs), Box::new(rhs)),
Token::Minus => Expr::Sub(Box::new(lhs), Box::new(rhs)),
_ => unreachable!(),
},
);
binary_2
})
}
5.2 运算符优先级处理
6. 表达式求值器
6.1 递归求值实现
impl Expr {
fn eval(&self) -> isize {
match self {
Expr::Int(n) => *n,
Expr::Neg(rhs) => -rhs.eval(),
Expr::Add(lhs, rhs) => lhs.eval() + rhs.eval(),
Expr::Sub(lhs, rhs) => lhs.eval() - rhs.eval(),
Expr::Mul(lhs, rhs) => lhs.eval() * rhs.eval(),
Expr::Div(lhs, rhs) => lhs.eval() / rhs.eval(),
}
}
}
6.2 求值过程示例
对于表达式 - (1 + 2 * 3):
7. 完整的主函数实现
7.1 集成所有组件
use std::env;
fn main() {
// 从命令行读取表达式
let input = env::args()
.nth(1)
.expect("请输入算术表达式,如: '1 + 2 * 3'");
// 词法分析:字符串 → Token序列
let lexer = Token::lexer(&input);
let mut tokens = vec![];
for (token, span) in lexer.spanned() {
match token {
Ok(token) => tokens.push(token),
Err(e) => {
eprintln!("词法分析错误 at {:?}: {}", span, e);
return;
}
}
}
// 语法解析:Token序列 → AST
let ast = match parser().parse(&tokens).into_result() {
Ok(expr) => {
println!("[抽象语法树]\n{:#?}", expr);
expr
}
Err(e) => {
eprintln!("语法解析错误: {:#?}", e);
return;
}
};
// 表达式求值:AST → 计算结果
println!("\n[计算结果]\n{}", ast.eval());
}
7.2 运行示例
# 编译运行
cargo run -- "1 + 7 * (3 - 4) / 2"
# 输出结果
[抽象语法树]
Add(
Int(1),
Div(
Mul(
Int(7),
Sub(
Int(3),
Int(4),
),
),
Int(2),
),
)
[计算结果]
-2
8. 进阶功能扩展
8.1 错误处理增强
impl Expr {
fn eval(&self) -> Result<isize, String> {
match self {
Expr::Int(n) => Ok(*n),
Expr::Neg(rhs) => Ok(-rhs.eval()?),
Expr::Add(lhs, rhs) => Ok(lhs.eval()? + rhs.eval()?),
Expr::Sub(lhs, rhs) => Ok(lhs.eval()? - rhs.eval()?),
Expr::Mul(lhs, rhs) => Ok(lhs.eval()? * rhs.eval()?),
Expr::Div(lhs, rhs) => {
let rhs_val = rhs.eval()?;
if rhs_val == 0 {
Err("除零错误".to_string())
} else {
Ok(lhs.eval()? / rhs_val)
}
}
}
}
}
8.2 支持更多运算符
// 在Token枚举中添加
#[token("%")]
Modulo,
// 在AST中添加
Mod(Box<Expr>, Box<Expr>),
// 在求值器中添加
Expr::Mod(lhs, rhs) => Ok(lhs.eval()? % rhs.eval()?),
9. 性能优化建议
9.1 Logos编译期优化
// 使用lookup table优化
#[logos(optimize = true)]
enum Token {
// ... token definitions
}
9.2 内存分配优化
// 使用Arena分配器减少Box分配
use bumpalo::Bump;
fn eval_in_arena<'a>(&self, arena: &'a Bump) -> isize {
// 在arena中分配AST节点
}
10. 总结与最佳实践
通过本文的完整实现,我们构建了一个高性能的计算器应用,展示了Logos在词法分析领域的强大能力。关键收获:
- Logos优势:编译时生成、无回溯设计、极致性能
- 架构清晰:词法分析→语法解析→求值执行的三层架构
- 扩展性强:易于添加新运算符和错误处理机制
- 生产就绪:包含完整的错误处理和用户交互
性能对比表
| 实现方式 | 处理速度 | 内存占用 | 代码复杂度 |
|---|---|---|---|
| 手写词法分析器 | 基准 | 基准 | 高 |
| Logos生成 | 2-3倍更快 | 相当 | 低 |
| 正则表达式 | 慢3-5倍 | 较高 | 中等 |
这个计算器项目不仅是一个学习示例,更是一个可以扩展到完整编程语言解释器的坚实基础。尝试在此基础上实现变量支持、函数定义等高级特性,探索编译原理的更多奥秘!
【免费下载链接】logos Create ridiculously fast Lexers 项目地址: https://gitcode.com/gh_mirrors/log/logos
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



