typed_eval/parser/
parser.rs

1use crate::{BinOp, Errors, Expr, Spanned, UnOp, WithSpan};
2use chumsky::{
3    cache::{Cache, Cached},
4    prelude::*,
5};
6use std::cell::LazyCell;
7
8pub fn parse_expr(input: &str) -> (Option<Expr>, Errors) {
9    let (expr, errors) = PARSER
10        .with(|parser| parser.get().parse(input))
11        .into_output_errors();
12    (
13        expr,
14        errors
15            .into_iter()
16            .map(|e| e.into())
17            .collect::<Vec<_>>()
18            .into(),
19    )
20}
21
22thread_local! {
23    static PARSER: LazyCell<Cache<ExprParser>> = LazyCell::new(Cache::default);
24}
25
26#[derive(Default)]
27struct ExprParser;
28
29impl Cached for ExprParser {
30    type Parser<'src> = Box<
31        dyn Parser<'src, &'src str, Expr, extra::Err<Rich<'src, char>>> + 'src,
32    >;
33
34    fn make_parser<'src>(self) -> Self::Parser<'src> {
35        Box::new(expr_parser())
36    }
37}
38
39fn expr_parser<'src>()
40-> impl Parser<'src, &'src str, Expr, extra::Err<Rich<'src, char>>> {
41    recursive(|expr| {
42        let num = text::int(10)
43            .then(just('.').then(text::int(10).or_not()).or_not())
44            .then(
45                just('e')
46                    .then(just('-').or_not())
47                    .then(text::int(10).or_not())
48                    .or_not(),
49            )
50            .to_slice()
51            .map_with(|s: &str, e| {
52                if s.contains(['.', 'e']) {
53                    s.parse()
54                        .map(|val: f64| Expr::Float(val.with_span(e.span())))
55                        .unwrap_or_else(|err| Expr::InvalidLiteral(err.to_string().with_span(e.span())))
56                } else {
57                    s.parse()
58                        .map(|val: i64| Expr::Int(val.with_span(e.span())))
59                        .unwrap_or_else(|err| Expr::InvalidLiteral(err.to_string().with_span(e.span())))
60                }
61            });
62
63        let string = choice((
64            none_of::<_, &str, _>("\"\\"),
65            just(r#"\\"#).to('\\'),
66            just(r#"\""#).to('"'),
67            just(r#"\n"#).to('\n'),
68            just(r#"\t"#).to('\t'),
69            just(r#"\r"#).to('\r'),
70        ))
71        .repeated()
72        .collect::<String>()
73        .delimited_by(just('"'), just('"'))
74        .map_with(|s, e| Expr::String(s.with_span(e.span())));
75
76        let var = text::ident::<&str, _>().map_with(|s: &str, extra| {
77            Expr::Var(s.to_owned().with_span(extra.span()))
78        });
79
80        let parens = expr
81            .clone()
82            .delimited_by(just('('), just(')'))
83            .recover_with(via_parser(nested_delimiters(
84                '(',
85                ')',
86                [],
87                |_| Expr::ParseError,
88            )));
89
90        let atom = choice((num, string, var, parens)).padded();
91
92        enum Postfix {
93            Field(Spanned<String>),
94            Call(Spanned<Vec<Expr>>),
95        }
96        let postfix = atom.foldl(
97            choice((
98                //field access
99                just('.').ignore_then((text::ident::<&str, _>()).map_with(
100                    |field: &str, e| {
101                        Postfix::Field(field.to_string().with_span(e.span()))
102                    },
103                )),
104                // function call
105                expr.separated_by(
106                    just(',')
107                        .padded()
108                        .recover_with(skip_then_retry_until(any().ignored(),one_of(",)").ignored()))
109                    )
110                    .collect::<Vec<Expr>>()
111                    .delimited_by(just('('), just(')'))
112                    .map_with(|args: Vec<Expr>, extra| {
113                        Postfix::Call(args.with_span(extra.span()))
114                    }).recover_with(via_parser(nested_delimiters(
115                        '(',
116                        ')',
117                        [],
118                        |span: SimpleSpan| Postfix::Call(vec![Expr::ParseError].with_span(span)),
119                    )))
120            ))
121            .repeated(),
122            |lhs, postfix| match postfix {
123                Postfix::Field(field) => {
124                    Expr::FieldAccess(Box::new(lhs), field)
125                }
126                Postfix::Call(args) => {
127                    Expr::FuncCall(Box::new(lhs), args)
128                }
129            },
130        );
131
132        let unary = one_of::<_, &str, _>("+-")
133            .padded()
134            .map_with(|c, e| match c {
135                '+' => UnOp::Plus.with_span(e.span()),
136                '-' => UnOp::Neg.with_span( e.span()),
137                _ => unreachable!("unexpected symbol: one_of should not return anything unexpected"),
138            })
139            .repeated()
140            .foldr(postfix, |op, rhs| {
141                Expr::UnOp(op, Box::new(rhs))
142            });
143
144        let product = unary.clone().foldl(
145            one_of("*/")
146                .padded()
147                .map_with(|c, e| match c {
148                    '*' => BinOp::Mul.with_span( e.span()),
149                    '/' => BinOp::Div.with_span(e.span()),
150                    _ => unreachable!("unexpected symbol: one_of should not return anything unexpected"),
151                })
152                .then(unary)
153                .repeated(),
154            |lhs, (op, rhs)| {
155
156                Expr::BinOp(op, Box::new(lhs), Box::new(rhs))
157            },
158        );
159
160        let sum = product.clone().foldl(
161            one_of("+-")
162                .padded()
163                .map_with(|c,e| match c {
164                    '+' => BinOp::Add.with_span(e.span()),
165                    '-' => BinOp::Sub.with_span(e.span()),
166                    _ => unreachable!("unexpected symbol: one_of should not return anything unexpected"),
167                })
168                .then(product)
169                .repeated(),
170            |lhs, (op, rhs)| {
171                Expr::BinOp(op, Box::new(lhs), Box::new(rhs))
172            },
173        );
174
175        #[allow(clippy::let_and_return)]
176        sum
177    }).padded().then_ignore(end())
178}