写点什么

语法分析 之表达式二

作者:Miracle
  • 2025-09-23
    四川
  • 本文字数:1849 字

    阅读完需:约 6 分钟

语法分析 之表达式二

让我们从开始构造一个表达式的开始,这个一个巨大复杂的挑战,是我们整个语法分析的基础,继续上一章的内容。我们先把上一章创建好的几个算子。组合在一起。得到一个基础的表达式我们把它叫做 base_expr


pub fn expr_parser<'a>() -> impl Parser<'a, &'a str, Expr, Extra<'a>> + Clone {    recursive(|expr| {        let base_expr = choice((            expr.clone().delimited_by(just('(').padded(), just(')').padded()).labelled("parentheses"),            number_parser().map(Expr::Value),            string_parser().map(|s| Expr::Value(Dynamic::from(s))),            const_value_parser().map(Expr::Value),            list_parser(expr.clone()).map(Expr::List),            super::ident_parser().map(|name| Expr::Ident(name)),        ));        base_expr    })}
复制代码


这个解析器可以解析数字、字符串、预定义的常量以及列表项。我们可以做一个简单的测试。下面 的测试代码


use chumsky::Parser;
fn main() -> Result<()> { println!("sizeof {}", std::mem::size_of::<Dynamic>()); let parser = parser::expr::expr_parser(); let expr = parser.parse("[1, \"Hello World\", 10.2]").unwrap(); println!("{:?}", expr); Ok(())}
复制代码


运行结果如下


完美!!!!


下面我我们开始扩展这个表达式。在加入这个 2 元操作符之前啊,就是加减。之前我们还有一个更重要的事情,就是我我们要加上成员符号和索引。这个在现代的编程语言里面是很重要的概念。也就是说下标引用,比如说 a 是个数组 a[10] 代表。引用索引为 10 的地方的。A 如果是一个 struct 结构或 者是一个其他的复杂的这个对象,那么他可以用点比如说 a.B1 名字。或者是 a 点 员函数。这是现代。编程语言中非常重要的点,所以我们在加入这个 2 元操作符之前,先把索引和成员加进去 因为这个是优先级最高的操作。我们还可以把调用作为索引的一种,这样的话一起处理,因为它基本上可以认为是后缀操作符的一种,我们把 call 然后 index member 都作为后缀操作符来处理。先引入 参数表, 这个简单


    let args_req = just('(').padded_by(rust_ws()).ignore_then(expr.clone().boxed().padded_by(rust_ws())        .separated_by(just(',')).allow_trailing().collect::<Vec<Expr>>()).then_ignore(just(')'));
复制代码


我们定义一个枚举 Suffix 增加这个定义主要是避免出现 chumsky 的类型地狱,这玩意用类型推导太重度,很容易爆出一堆错误。特别是返回值的问题,它让你忽略类型,自动推导出来,但是一不小心就出问题,所以我会在过程中加入自己的类型强制说明,这样自己看起来更清楚,也不容易出错。


    enum Suffix {        Call { args: Vec<Expr> },        Idx { idx: Expr, args: Option<Vec<Expr>> },    }    let suffix = choice((        args_req.clone().map_with(|args: Vec<Expr>, _span| Suffix::Call { args }),        dot_operator.clone().then(args_req.clone().or_not()).map_with(|(name, maybe_args): (SmolStr, Option<Vec<Expr>>), _span| Suffix::Idx { idx: Expr::Ident(name), args: maybe_args }),        expr.clone().delimited_by(just('['), just(']')).map_with(|idx: Expr, _span| Suffix::Idx { idx, args: None }),    ));
复制代码


通过 递归的使用 suffix 算子 我们可以实现无限层次的 索引 比如说 x[10].y.name[x]


    base_expr.then(suffix.repeated().collect::<Vec<Suffix>>()).map_with(|(base, suffixes): (Expr, Vec<Suffix>), _span| {        suffixes.into_iter().fold(base, |acc, s| match s {            Suffix::Call { args, .. } => Expr::Call { obj: Box::new(acc), args },            Suffix::Idx { idx, args } => {                if let Some(args) = args {                    let obj = Expr::Binary { left: Box::new(acc), op: BinaryOp::Idx, right: Box::new(idx) };                    Expr::Call { obj: Box::new(obj), args }                } else {                    Expr::Binary { left: Box::new(acc), op: BinaryOp::Idx, right: Box::new(idx) }                }            }        })    })
复制代码


检查下效果将表达式换成这样古怪的形式


let expr = parser.parse("[1, "Hello World", 10.2].aaaa(10)").unwrap();
复制代码


解析结果如下


完美 虽然很怪异 不过人家是合法的表达式,也被成功解析出来了

用户头像

Miracle

关注

三十年资深码农 2019-10-25 加入

还未添加个人简介

评论

发布
暂无评论
语法分析 之表达式二_Miracle_InfoQ写作社区