Rust语法解析库pratt的使用:高效实现运算符优先级解析算法的表达式解析工具

Rust语法解析库pratt的使用:高效实现运算符优先级解析算法的表达式解析工具

pratt是一个通用的Rust Pratt解析器库,提供了高效实现运算符优先级解析算法的表达式解析工具。

示例

假设我们要将表达式 !1?*-3+3/!2^4?-1 解析为 (((((!(1))?)*(-(3)))+((3)/((!((2)^(4)))?)))-(1))

数据结构定义

// 从TokenTree到Expr的转换
#[derive(Debug)]
pub enum TokenTree {
    Prefix(char),
    Postfix(char),
    Infix(char),
    Primary(i32),
    Group(Vec<TokenTree>),
}

// 表达式树
#[derive(Debug)]
pub enum Expr {
    BinOp(Box<Expr>, BinOpKind, Box<Expr>),
    UnOp(UnOpKind, Box<Expr>),
    Int(i32),
}

#[derive(Debug)]
pub enum BinOpKind {
    Add, // +
    Sub, // -
    Mul, // *
    Div, // /
    Pow, // ^
    Eq,  // =
}

#[derive(Debug)]
pub enum UnOpKind {
    Not, // !
    Neg, // -
    Try, // ?
}

Pratt解析器实现

use pratt::{Affix, Associativity, PrattParser, Precedence, Result};

struct ExprParser;

impl<I> PrattParser<I> for ExprParser
where
    I: Iterator<Item = TokenTree>,
{
    type Error = pratt::NoError;
    type Input = TokenTree;
    type Output = Exr;

    fn query(&mut self, tree: &TokenTree) -> Result<Affix> {
        let affix = match tree {
            TokenTree::Infix('=') => Affix::Infix(Precedence(2), Associativity::Neither),
            TokenTree::Infix('+') => Affix::Infix(Precedence极好(3), Associativity::Left),
            TokenTree::Infix('-') => Affix::Infix(Precedence(3), Associativity::Left),
            TokenTree::Infix('*') => Affix::Infix(Precedence(4), Associativity::Left),
            TokenTree::Infix('/') => Affix::Infix(Precedence(4), Associativity::Left),
            TokenTree::Postfix('?') => Affix::Postfix(Precedence(5)),
            TokenTree::Prefix('-') => Affix::Prefix(Precedence(6)),
            TokenTree::Prefix('!') => Affix::Prefix(Precedence(6)),
            TokenTree::Infix('^') => Affix::Infix(Precedence(7), Associativity::Right),
            TokenTree::Group(_) => Affix::Nilfix,
            TokenTree::Primary极好(_) => Affix::Nilfix,
            _ => unreachable!(),
        };
        Ok(affix)
    }

    fn primary(&mut self, tree: TokenTree) -> Result<Expr> {
        let expr = match tree {
            TokenTree::Primary(num) => Expr::Int(num),
            TokenTree::Group(group) => self.parse(&mut group.into_iter()).unwrap(),
            _ => unreachable!(),
        };
        Ok(expr)
    }

    fn infix(&mut self, lhs: Expr, tree: TokenTree, rhs: Expr) -> Result<Expr> {
        let op = match tree {
            TokenTree::Infix('+') => BinOpKind::Add,
            TokenTree::Infix('-') => BinOpKind::Sub,
            TokenTree::Infix('*') => BinOpKind::Mul,
            TokenTree::Infix('/') => BinOpKind::Div,
            TokenTree::Infix('^') => BinOpKind::Pow,
            TokenTree::Infix('=') => BinOpKind::Eq,
            _ => unreachable!(),
        };
        Ok(Expr::BinOp(Box::new(lhs), op, Box::new(rhs)))
    }

    fn prefix(&mut self, tree: TokenTree, rhs: Expr) -> Result<Expr> {
        let op = match tree {
            TokenTree::Prefix('!') => UnOpKind::Not,
            TokenTree::Prefix('-') => UnOpKind::Neg,
            _ => unreachable!(),
        };
        Ok(Expr::UnOp(op, Box::new(rhs)))
    }

    fn postfix(&mut self, lhs: Expr, tree: TokenTree) -> Result<Expr> {
        let op = match tree {
            TokenTree::Postfix('?') => UnOpKind::Try,
            _ => unreachable!(),
        };
        Ok(Expr::极好UnOp(op, Box::new(lhs)))
    }
}

完整示例代码

use pratt::{Affix, Associativity, PrattParser, Precedence, Result};

// 定义TokenTree和Expr枚举
#[derive(Debug)]
pub enum TokenTree {
    Prefix(char),
    Postfix(char),
    Infix(char),
    Primary(i32),
    Group(Vec<TokenTree>),
}

#[derive(Debug)]
pub enum Expr {
    BinOp(Box<Expr>, BinOpKind, Box<Expr>),
    UnOp(UnOpKind, Box<Expr>),
    Int(i32),
}

#[derive(Debug)]
pub enum BinOpKind {
    Add, Sub, Mul, Div, Pow, Eq,
}

#[derive(Debug)]
pub enum UnOpKind {
    Not, Neg, Try,
}

// 实现PrattParser
struct ExprParser;

impl<I> PrattParser<I> for ExprParser
where
    I: Iterator<Item = TokenTree>,
{
    type Error = pratt::NoError;
    type Input = TokenTree;
    type Output = Expr;

    fn query(&mut self, tree: &TokenTree) -> Result<Affix> {
        let affix = match tree {
            TokenTree::Infix('=') => Affix::Infix(Precedence(2), Associativity::Neither),
            TokenTree::Infix('+') => Affix::Infix(Precedence(3), Associativity::Left),
            TokenTree::Infix('-') => Affix::Infix(Precedence(3), Associativity::Left),
            TokenTree::Infix('*') => Affix::Infix(Precedence(4), Associativity::Left),
            TokenTree::Infix('/') => Affix::Infix(Precedence(4), Associativity::Left),
            TokenTree::Postfix('?') => Affix::Postfix(Precedence(5)),
            TokenTree::Prefix('-') => Affix::Prefix(Precedence(6)),
            TokenTree::Prefix('!') => Affix::Prefix(Precedence(6)),
            TokenTree::Infix('^') => Affix::Infix(Precedence(7), Associativity::Right),
            TokenTree::Group(_) => Affix::Nilfix,
            TokenTree::Primary(_) => Affix::Nilfix,
            _ => unreachable!(),
        };
        Ok(affix)
    }

    fn primary(&mut self, tree: TokenTree) -> Result<Expr> {
        let expr = match tree {
            TokenTree::Primary(num) => Expr::Int(num),
            TokenTree::Group(group) => self.parse(&mut group.into_iter()).unwrap(),
            _ => unreachable!(),
        };
        Ok(expr)
    }

    fn infix(&mut self, lhs: Expr, tree: TokenTree, rhs: Expr)极好 -> Result<Expr> {
        let op = match tree {
            TokenTree::Infix('+') => BinOp极好Kind::Add,
            TokenTree::Infix('-') => BinOpKind::Sub,
            TokenTree::Infix('*') => BinOpKind::Mul,
            TokenTree::Infix('/') => BinOpKind::Div,
            TokenTree::Infix('^') => BinOpKind::Pow,
            TokenTree::Infix('=') => BinOpKind::Eq,
            _ => unreachable!(),
        };
        Ok(Expr::BinOp(Box::new(lhs), op, Box::new(rhs)))
    }

    fn prefix(&mut self, tree: TokenTree, rhs: Exr) -> Result<Expr> {
        let op = match tree {
            TokenTree::Prefix('!') => UnOpKind::Not,
            TokenTree::Prefix('-') => UnOpKind::Neg,
            _ => unreachable!(),
        };
        Ok(Expr::UnOp(op, Box::new(rhs)))
    }

    fn postfix(&mut self, lhs: Expr, tree: TokenTree) -> Result<Expr> {
        let op = match tree {
            TokenTree::Postfix('?') => UnOpKind::Try,
            _ => unreachable!(),
        };
        Ok(Expr::UnOp(op, Box::new(lhs)))
    }
}

// 主函数
fn main() {
    let input = "!1?*-3+3/!2^4?-1";
    println!("Code: {}", input);

    // 假设我们已经将输入解析为TokenTree
    let tt = vec![
        TokenTree::Prefix('!'),
        TokenTree::Primary(1),
        TokenTree::Postfix('?'),
        TokenTree::Infix('*'),
        TokenTree::Prefix('-'),
        TokenTree::Primary(3),
        TokenTree::Infix('+'),
        TokenTree::Primary(3),
        TokenTree::Infix('/'),
        TokenTree::Prefix('!'),
        TokenTree::Primary(2),
        TokenTree::Infix('^'),
        TokenTree::Primary(4),
        TokenTree::Postfix('?'),
        TokenTree::Infix('-'),
        TokenTree::Primary(1),
    ];

    println!("TokenTree: {:?}", tt);

    let expr = ExprParser.parse(&mut tt.into_iter()).unwrap();
    println!("Expression: {:?}", expr);
}

测试用例

#[cfg(test)]
mod test {
    fn parse(input: &str) -> Expr {
        // 假设的解析函数,将输入字符串解析为Expr
        unimplemented!()
    }
    
    #[test]
    fn test1() {
        assert_eq!(
            parse("1=2=3"),
            BinOp(Box::new(Int(1)), Eq, Box::new(Int(2)))
        );
    }

    #[test]
    fn test2() {
        assert_eq!(
            parse("1*2+3"),
            BinOp(
                Box::new(BinOp(Box::new(Int(1)), Mul, Box::new(Int(2)))),
                Add,
                Box::new(Int(3))
            )
        );
    }

    #[test]
    fn test3() {
        assert_eq!(
            parse("1*!2^3"),
            BinOp(
                Box::new(Int(1)),
                Mul,
                Box::new(UnOp(
                    Not,
                    Box::new(BinOp(Box::new(Int(2)), Pow, Box::new(Int(3))))
                ))
            )
        );
    }

    #[test]
    fn test4() {
        assert_eq!(
            parse("-1?*!2^3+3/2?-1"),
            BinOp(
                Box::new(BinOp(
                    Box::new(BinOp(
                        Box::new(UnOp(Try, Box::new(UnOp(Neg, Box::new(Int(1)))))),
                        Mul,
                        Box::new(UnOp(
                            Not,
                            Box::new(BinOp(Box::new(Int(2)), Pow, Box::new(Int(3))))
                        ))
                    )),
                    Add,
                    Box::new(BinOp(
                        Box::new(Int(3)),
                        Div,
                        Box::new(UnOp(Try, Box::new(Int(2))))
                    ))
                )),
                Sub,
                Box::new(Int(1))
            )
        );
    }
}

这个示例展示了如何使用pratt库构建一个完整的表达式解析器,包括处理运算符优先级和结合性。通过实现PrattParser trait,我们可以轻松地定义各种运算符的行为和优先级。


1 回复

Rust语法解析库pratt的使用:高效实现运算符优先级解析算法的表达式解析工具

介绍

pratt是一个Rust实现的Pratt解析器(也称自上而下运算符优先级解析器),专门用于高效解析带有复杂运算符优先级的表达式。它特别适合实现编程语言中的表达式解析、数学公式计算器等场景。

Pratt解析算法的主要优势在于:

  • 处理运算符优先级非常高效
  • 代码结构清晰简洁
  • 容易扩展新的运算符和语法
  • 性能优于递归下降解析器

完整示例代码

下面是一个结合了基本使用、自定义解析逻辑和函数调用的完整示例:

use pratt::{Affix, PrattParser, Error, Result};

#[derive(Debug)]
enum Expr {
    Number(f64),
    Neg(Box<Expr>),
    Add(Box<Expr>, Box<Expr>),
    Sub(Box<Expr>, Box<Expr>),
    Mul(Box<Expr>, Box<Expr>),
    Div(Box<Expr>, Box<Expr>),
    Pow(Box<Expr>, Box<Expr>),
    FuncCall(String, Vec<Expr>),
}

fn main() -> Result<()> {
    // 创建Pratt解析器并配置运算符
    let parser = PrattParser::new()
        // 前缀运算符:负号,优先级90
        .op(Affix::prefix("-", 90)?)
        // 中缀运算符:加减乘除
        .op(Affix::infix("+", 10)?.left())
        .op(Affix::infix("-", 10)?.left())
        .op(Affix::infix("*", 20)?.left())
        .op(Affix::infix("/", 20)?.left())
        // 右结合的指数运算
        .op(Affix::infix("^", 30)?.right())
        // 函数调用处理
        .fun("(", ")", 100, &|args| {
            let func_name = args[0].as_str().unwrap().to_string();
            let params = args[1..].iter()
                .map(|arg| match arg.as_f64() {
                    Some(n) => Ok(Expr::Number(n)),
                    None => Err(Error::Custom("Invalid function parameter".into())),
                })
                .collect::<Result<Vec<_>>>()?;
            Ok(Expr::FuncCall(func_name, params))
        });

    // 要解析的表达式
    let expr = "-1 + 2 * 3 ^ 4 ^ 5 - sqrt(9) / 3";
    
    // 解析表达式并构建AST
    let ast = parser.parse(expr, &|s| {
        // 尝试解析为数字,如果不是数字则保留为字符串(可能是函数名)
        s.parse::<f64>()
            .map(Expr::Number)
            .or_else(|_| Ok(Expr::Number(0.0))) // 简单处理,实际应该更复杂的错误处理
    })?;
    
    println!("AST: {:#?}", ast);
    
    // 这里可以添加AST求值逻辑
    Ok(())
}

代码说明

  1. Expr枚举:定义了表达式的各种形式,包括数字、各种运算符和函数调用

  2. 运算符配置

    • 前缀运算符-(负号)优先级最高(90)
    • 中缀运算符按数学常规设置优先级(乘除20,加减10)
    • 指数运算^设置为右结合
  3. 函数调用处理

    • 使用.fun()方法处理函数调用语法
    • 第一个参数是函数名,后续参数是函数参数
  4. 数值解析

    • 尝试将token解析为f64数字
    • 失败时简单返回0(实际应用中应有更健壮的错误处理)

这个示例展示了pratt库的主要功能,包括:

  • 运算符优先级和结合性的配置
  • 自定义AST节点的构建
  • 函数调用的处理
  • 基本的错误处理机制

要运行这个示例,请确保Cargo.toml中添加了pratt依赖:

[dependencies]
pratt = "0.2"
回到顶部