1use std::ops::Range;
11
12use chumsky::prelude::*;
13use rust_decimal::Decimal;
14
15use crate::ast::{BinaryOp, Expr, Literal, TypeSpecifier};
16use crate::lex::{Token, lex};
17use crate::quantity::Unit;
18use crate::temporal::parse::{Temporal, literal};
19
20#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
22pub enum ParseError {
23 #[error("no token of the grammar matches {text:?} at byte {offset}")]
26 Lex {
27 offset: usize,
29 text: String,
31 },
32 #[error("the expression leaves the grammar at byte {offset}")]
34 Syntax {
35 offset: usize,
38 },
39 #[error("the literal {text:?} at byte {offset} names no value")]
42 Literal {
43 offset: usize,
45 text: String,
47 },
48}
49
50#[expect(
52 unused_qualifications,
53 reason = "the alias shadows the name it qualifies, so the path keeps the definition from naming itself"
54)]
55type Extra<'t> = chumsky::extra::Err<Simple<'t, Token>>;
56
57pub fn parse(text: &str) -> Result<Expr, ParseError> {
65 let spanned = lex(text)?;
66 for (token, span) in &spanned {
67 check_literal(token, span)?;
68 }
69 let tokens: Vec<Token> = spanned.iter().map(|(token, _)| token.clone()).collect();
70 grammar()
71 .parse(tokens.as_slice())
72 .into_result()
73 .map_err(|errors| {
74 let index = errors
75 .first()
76 .map_or(tokens.len(), |error| error.span().start);
77 ParseError::Syntax {
78 offset: spanned
79 .get(index)
80 .map_or(text.len(), |(_, span)| span.start),
81 }
82 })
83}
84
85fn check_literal(token: &Token, span: &Range<usize>) -> Result<(), ParseError> {
87 let refuse = |text: &str| ParseError::Literal {
88 offset: span.start,
89 text: String::from(text),
90 };
91 match token {
92 Token::Temporal(text) if literal(text).is_none() => Err(refuse(text)),
93 Token::Number(text) if number(text).is_none() => Err(refuse(text)),
94 _ => Ok(()),
95 }
96}
97
98fn number(text: &str) -> Option<Literal> {
101 if text.contains('.') {
102 text.parse::<Decimal>().ok().map(Literal::Decimal)
103 } else {
104 text.parse::<i32>()
105 .ok()
106 .map(|value| Literal::Integer(i64::from(value)))
107 }
108}
109
110fn temporal(text: &str) -> Option<Literal> {
112 Some(match literal(text)? {
113 Temporal::Date(value) => Literal::Date(value),
114 Temporal::DateTime(value) => Literal::DateTime(value),
115 Temporal::Time(value) => Literal::Time(value),
116 })
117}
118
119fn level<'t>(
121 operand: impl Parser<'t, &'t [Token], Expr, Extra<'t>> + Clone + 't,
122 operators: impl Parser<'t, &'t [Token], BinaryOp, Extra<'t>> + Clone + 't,
123) -> impl Parser<'t, &'t [Token], Expr, Extra<'t>> + Clone {
124 operand
125 .clone()
126 .foldl(operators.then(operand).repeated(), |left, (op, right)| {
127 Expr::Binary {
128 op,
129 left: Box::new(left),
130 right: Box::new(right),
131 }
132 })
133 .boxed()
134}
135
136enum Postfix {
138 Invoke(Expr),
140 Index(Expr),
142}
143
144fn identifier<'t>() -> impl Parser<'t, &'t [Token], String, Extra<'t>> + Clone {
147 select! {
148 Token::Identifier(name) => name,
149 Token::Delimited(name) => name,
150 Token::As => String::from("as"),
151 Token::Contains => String::from("contains"),
152 Token::In => String::from("in"),
153 Token::Is => String::from("is"),
154 }
155}
156
157fn type_specifier<'t>() -> impl Parser<'t, &'t [Token], TypeSpecifier, Extra<'t>> + Clone {
159 identifier()
160 .then(just(Token::Dot).ignore_then(identifier()).or_not())
161 .map(|(first, second)| match second {
162 Some(name) => TypeSpecifier {
163 namespace: Some(first),
164 name,
165 },
166 None => TypeSpecifier {
167 namespace: None,
168 name: first,
169 },
170 })
171}
172
173fn literal_term<'t>() -> impl Parser<'t, &'t [Token], Expr, Extra<'t>> + Clone {
175 let unit = select! {
176 Token::Text(code) => Unit::from_code(code),
177 Token::Calendar(calendar) => Unit::Calendar(calendar),
178 };
179 let numeric = select! { Token::Number(text) => text }
180 .then(unit.or_not())
181 .try_map(|(text, unit), span| match (number(&text), unit) {
182 (Some(Literal::Integer(value)), Some(unit)) => {
183 Ok(Literal::Quantity(Decimal::from(value), unit))
184 }
185 (Some(Literal::Decimal(value)), Some(unit)) => Ok(Literal::Quantity(value, unit)),
186 (Some(literal), None) => Ok(literal),
187 _ => Err(Simple::new(None, span)),
188 });
189 choice((
190 just(Token::OpenBrace)
191 .then(just(Token::CloseBrace))
192 .to(Expr::Empty),
193 just(Token::True).to(Expr::Literal(Literal::Boolean(true))),
194 just(Token::False).to(Expr::Literal(Literal::Boolean(false))),
195 select! { Token::Text(text) => Expr::Literal(Literal::String(text)) },
196 select! { Token::Temporal(text) => text }.try_map(|text, span| {
197 temporal(&text)
198 .map(Expr::Literal)
199 .ok_or_else(|| Simple::new(None, span))
200 }),
201 numeric.map(Expr::Literal),
202 ))
203}
204
205fn invocation<'t>(
207 expression: impl Parser<'t, &'t [Token], Expr, Extra<'t>> + Clone + 't,
208) -> impl Parser<'t, &'t [Token], Expr, Extra<'t>> + Clone {
209 let arguments = expression
210 .clone()
211 .separated_by(just(Token::Comma))
212 .collect::<Vec<Expr>>()
213 .delimited_by(just(Token::OpenParen), just(Token::CloseParen));
214 let direction = select! {
215 Token::Identifier(word) if word == "asc" => false,
216 Token::Identifier(word) if word == "desc" => true,
217 };
218 let sort_arguments = expression
219 .then(direction.or_not())
220 .map(|(key, direction)| match direction {
221 Some(descending) => Expr::SortKey {
222 key: Box::new(key),
223 descending,
224 },
225 None => key,
226 })
227 .separated_by(just(Token::Comma))
228 .collect::<Vec<Expr>>()
229 .delimited_by(just(Token::OpenParen), just(Token::CloseParen));
230 choice((
231 select! { Token::Identifier(name) if name == "sort" => name }
234 .then(sort_arguments)
235 .map(|(name, args)| Expr::Call {
236 input: None,
237 name,
238 args,
239 }),
240 identifier()
241 .then(arguments.or_not())
242 .map(|(name, args)| match args {
243 Some(args) => Expr::Call {
244 input: None,
245 name,
246 args,
247 },
248 None => Expr::Member { input: None, name },
249 }),
250 just(Token::This).to(Expr::This),
251 just(Token::Index).to(Expr::Index),
252 just(Token::Total).to(Expr::Total),
253 ))
254}
255
256fn invoked<'t>(
259 expression: impl Parser<'t, &'t [Token], Expr, Extra<'t>> + Clone + 't,
260) -> impl Parser<'t, &'t [Token], Expr, Extra<'t>> + Clone {
261 let external = just(Token::Percent)
262 .ignore_then(choice((
263 identifier(),
264 select! { Token::Text(text) => text },
265 )))
266 .map(Expr::External);
267 let term = choice((
268 literal_term(),
269 external,
270 invocation(expression.clone()),
271 expression
272 .clone()
273 .delimited_by(just(Token::OpenParen), just(Token::CloseParen)),
274 ));
275 let postfix = choice((
276 just(Token::Dot)
277 .ignore_then(invocation(expression.clone()))
278 .map(Postfix::Invoke),
279 expression
280 .delimited_by(just(Token::OpenBracket), just(Token::CloseBracket))
281 .map(Postfix::Index),
282 ));
283 term.foldl(postfix.repeated(), |input, postfix| match postfix {
284 Postfix::Invoke(Expr::Member { name, .. }) => Expr::Member {
285 input: Some(Box::new(input)),
286 name,
287 },
288 Postfix::Invoke(Expr::Call { name, args, .. }) => Expr::Call {
289 input: Some(Box::new(input)),
290 name,
291 args,
292 },
293 Postfix::Invoke(variable) => Expr::Invocation {
294 input: Box::new(input),
295 variable: Box::new(variable),
296 },
297 Postfix::Index(index) => Expr::Indexer {
298 input: Box::new(input),
299 index: Box::new(index),
300 },
301 })
302 .boxed()
303}
304
305fn arithmetic<'t>(
308 invoked: impl Parser<'t, &'t [Token], Expr, Extra<'t>> + Clone + 't,
309) -> impl Parser<'t, &'t [Token], Expr, Extra<'t>> + Clone {
310 let polarity = choice((just(Token::Plus).to(true), just(Token::Minus).to(false)))
311 .repeated()
312 .foldr(invoked, |plus, operand| {
313 if plus {
314 Expr::Plus(Box::new(operand))
315 } else {
316 Expr::Negate(Box::new(operand))
317 }
318 })
319 .boxed();
320 let multiplicative = level(
321 polarity,
322 choice((
323 just(Token::Star).to(BinaryOp::Multiply),
324 just(Token::Slash).to(BinaryOp::Divide),
325 just(Token::Div).to(BinaryOp::Div),
326 just(Token::Mod).to(BinaryOp::Mod),
327 )),
328 );
329 let additive = level(
330 multiplicative,
331 choice((
332 just(Token::Plus).to(BinaryOp::Add),
333 just(Token::Minus).to(BinaryOp::Subtract),
334 just(Token::Ampersand).to(BinaryOp::Concatenate),
335 )),
336 );
337 additive
338 .foldl(
339 choice((just(Token::Is).to(true), just(Token::As).to(false)))
340 .then(type_specifier())
341 .repeated(),
342 |operand, (is, type_specifier)| {
343 let operand = Box::new(operand);
344 if is {
345 Expr::Is {
346 operand,
347 type_specifier,
348 }
349 } else {
350 Expr::As {
351 operand,
352 type_specifier,
353 }
354 }
355 },
356 )
357 .boxed()
358}
359
360fn logical<'t>(
363 typed: impl Parser<'t, &'t [Token], Expr, Extra<'t>> + Clone + 't,
364) -> impl Parser<'t, &'t [Token], Expr, Extra<'t>> + Clone {
365 let union = level(typed, just(Token::Pipe).to(BinaryOp::Union));
366 let inequality = level(
367 union,
368 choice((
369 just(Token::LessOrEqual).to(BinaryOp::LessOrEqual),
370 just(Token::Less).to(BinaryOp::Less),
371 just(Token::GreaterOrEqual).to(BinaryOp::GreaterOrEqual),
372 just(Token::Greater).to(BinaryOp::Greater),
373 )),
374 );
375 let equality = level(
376 inequality,
377 choice((
378 just(Token::Equal).to(BinaryOp::Equal),
379 just(Token::Tilde).to(BinaryOp::Equivalent),
380 just(Token::NotEqual).to(BinaryOp::NotEqual),
381 just(Token::NotTilde).to(BinaryOp::NotEquivalent),
382 )),
383 );
384 let membership = level(
385 equality,
386 choice((
387 just(Token::In).to(BinaryOp::In),
388 just(Token::Contains).to(BinaryOp::Contains),
389 )),
390 );
391 let and = level(membership, just(Token::And).to(BinaryOp::And));
392 let or = level(
393 and,
394 choice((
395 just(Token::Or).to(BinaryOp::Or),
396 just(Token::Xor).to(BinaryOp::Xor),
397 )),
398 );
399 level(or, just(Token::Implies).to(BinaryOp::Implies))
400}
401
402fn grammar<'t>() -> impl Parser<'t, &'t [Token], Expr, Extra<'t>> {
404 recursive(|expression| logical(arithmetic(invoked(expression)))).then_ignore(end())
405}