使用Logos实现简易计算器:从词法分析到表达式求值

使用Logos实现简易计算器:从词法分析到表达式求值

【免费下载链接】logos Create ridiculously fast Lexers 【免费下载链接】logos 项目地址: 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 技术栈架构

mermaid

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数字正则解析后的整数值123123

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结构为:

mermaid

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 运算符优先级处理

mermaid

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)

mermaid

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在词法分析领域的强大能力。关键收获:

  1. Logos优势:编译时生成、无回溯设计、极致性能
  2. 架构清晰:词法分析→语法解析→求值执行的三层架构
  3. 扩展性强:易于添加新运算符和错误处理机制
  4. 生产就绪:包含完整的错误处理和用户交互

性能对比表

实现方式处理速度内存占用代码复杂度
手写词法分析器基准基准
Logos生成2-3倍更快相当
正则表达式慢3-5倍较高中等

这个计算器项目不仅是一个学习示例,更是一个可以扩展到完整编程语言解释器的坚实基础。尝试在此基础上实现变量支持、函数定义等高级特性,探索编译原理的更多奥秘!

【免费下载链接】logos Create ridiculously fast Lexers 【免费下载链接】logos 项目地址: https://gitcode.com/gh_mirrors/log/logos

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值