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 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 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}