1use crate::ast::{AggregateCall, BinaryOp, Criteria, GroupOp, UnaryOp};
4use crate::functions::{Aggregate, Function};
5use crate::value::{DateTime, Value};
6
7#[derive(Clone, Debug, PartialEq, Eq)]
9pub struct ParseError {
10 pub message: String,
12 pub offset: usize,
14}
15
16impl std::fmt::Display for ParseError {
17 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18 write!(f, "{} (at {})", self.message, self.offset)
19 }
20}
21
22impl std::error::Error for ParseError {}
23
24const MAX_DEPTH: u32 = 128;
26
27#[derive(Clone, Debug, PartialEq)]
28enum Tok {
29 Num(f64),
30 Str(String),
31 Date(DateTime),
32 Ident(String),
33 Field(String, usize),
35 Param(String),
36 LParen,
37 RParen,
38 Comma,
39 Dot,
40 Op(&'static str),
41 End,
42}
43
44fn lex(src: &str, base: usize) -> Result<Vec<(Tok, usize)>, ParseError> {
45 let mut out = Vec::new();
46 let mut pos = 0;
47 let err = |message: String, at: usize| ParseError { message, offset: base + at };
48 loop {
49 let rest = &src[pos..];
50 let trimmed = rest.trim_start();
51 pos += rest.len() - trimmed.len();
52 if trimmed.starts_with("/*") {
53 let Some(end) = trimmed.find("*/") else { return Err(err("unterminated comment".into(), pos)) };
54 pos += end + 2;
55 continue;
56 }
57 let start = pos;
58 let Some(c) = trimmed.chars().next() else {
59 out.push((Tok::End, base + start));
60 return Ok(out);
61 };
62 let tok = match c {
63 '0'..='9' => {
64 let digits = trimmed
65 .char_indices()
66 .find(|(i, ch)| {
67 !(ch.is_ascii_digit()
68 || (*ch == '.' && trimmed[i + 1..].starts_with(|n: char| n.is_ascii_digit())))
69 })
70 .map_or(trimmed.len(), |(i, _)| i);
71 let n = trimmed[..digits].parse().map_err(|_| err("invalid number".into(), pos))?;
72 pos += digits;
73 if let Some(s) = src[pos..].chars().next()
75 && matches!(s.to_ascii_lowercase(), 'm' | 'f' | 's' | 'b' | 'l' | 'u')
76 && !src[pos + 1..].starts_with(|n: char| n.is_alphanumeric() || n == '_')
77 {
78 pos += 1;
79 }
80 Tok::Num(n)
81 }
82 '\'' => {
83 let mut s = String::new();
84 let mut chars = trimmed[1..].char_indices();
85 let mut end = None;
86 while let Some((i, ch)) = chars.next() {
87 if ch == '\'' {
88 if trimmed[1 + i + 1..].starts_with('\'') {
89 s.push('\'');
90 chars.next();
91 continue;
92 }
93 end = Some(1 + i + 1);
94 break;
95 }
96 s.push(ch);
97 }
98 let Some(end) = end else { return Err(err("unterminated text".into(), pos)) };
99 pos += end;
100 Tok::Str(s)
101 }
102 '#' => {
103 let Some(close) = trimmed[1..].find('#') else {
104 return Err(err("unterminated date".into(), pos));
105 };
106 let d =
107 DateTime::parse(&trimmed[1..1 + close]).ok_or_else(|| err("invalid date".into(), pos))?;
108 pos += close + 2;
109 Tok::Date(d)
110 }
111 '{' => {
112 let Some(close) = trimmed.find('}') else { return Err(err("unterminated '{'".into(), pos)) };
113 let guid = trimmed[1..close].to_owned();
114 pos += close + 1;
115 Tok::Str(guid)
116 }
117 '[' => {
118 let (mut depth, mut quoted, mut close) = (0, false, None);
120 for (i, ch) in trimmed.char_indices() {
121 match ch {
122 '\'' => quoted = !quoted,
123 '[' if !quoted => depth += 1,
124 ']' if !quoted => {
125 depth -= 1;
126 if depth == 0 {
127 close = Some(i);
128 break;
129 }
130 }
131 _ => {}
132 }
133 }
134 let Some(close) = close else { return Err(err("missing ']'".into(), pos)) };
135 let inner = trimmed[1..close].to_owned();
136 pos += close + 1;
137 Tok::Field(inner, base + start + 1)
138 }
139 '?' => {
140 let len = 1 + trimmed[1..]
141 .find(|ch: char| !(ch.is_alphanumeric() || ch == '_'))
142 .unwrap_or(trimmed.len() - 1);
143 pos += len;
144 Tok::Param(trimmed[1..len].to_owned())
146 }
147 '(' | ')' | ',' | '.' => {
148 pos += 1;
149 match c {
150 '(' => Tok::LParen,
151 ')' => Tok::RParen,
152 ',' => Tok::Comma,
153 _ => Tok::Dot,
154 }
155 }
156 c if c.is_alphabetic() || c == '_' => {
157 let len =
158 trimmed.find(|ch: char| !(ch.is_alphanumeric() || ch == '_')).unwrap_or(trimmed.len());
159 pos += len;
160 Tok::Ident(trimmed[..len].to_owned())
161 }
162 _ => {
163 const OPS: [&str; 19] = [
164 "<>", "!=", "==", "<=", ">=", "&&", "||", "+", "-", "*", "/", "%", "=", "<", ">", "!",
165 "&", "|", "^",
166 ];
167 if let Some(op) = OPS.iter().find(|op| trimmed.starts_with(**op)) {
168 pos += op.len();
169 Tok::Op(op)
170 } else if c == '~' {
171 pos += 1;
172 Tok::Op("~")
173 } else {
174 return Err(err(format!("unexpected '{c}'"), pos));
175 }
176 }
177 };
178 out.push((tok, base + start));
179 }
180}
181
182pub(crate) fn parse_assignments(source: &str) -> Result<Vec<(String, Criteria)>, ParseError> {
185 let mut found = Vec::new();
186 let mut start = 0;
187 for end in top_level(source, b';').into_iter().chain([source.len()]) {
188 let piece = &source[start..end];
189 if !piece.trim().is_empty() {
190 let at = |offset: usize| start + offset;
191 let Some(&equals) = top_level(piece, b'=').first() else {
192 let first = piece.len() - piece.trim_start().len();
193 return Err(ParseError {
194 message: "expected `field = expression`".into(),
195 offset: at(first),
196 });
197 };
198 let name = piece[..equals].trim();
199 let name = name.strip_prefix('[').and_then(|n| n.strip_suffix(']')).unwrap_or(name).trim();
200 let simple = name.starts_with(|c: char| c.is_alphabetic() || c == '_')
201 && name.chars().all(|c| c.is_alphanumeric() || c == '_' || c == '.');
202 if !simple {
203 let first = piece.len() - piece.trim_start().len();
204 return Err(ParseError {
205 message: "expected the name of a field before `=`".into(),
206 offset: at(first),
207 });
208 }
209 let text = &piece[equals + 1..];
210 let expression =
211 parse(text).map_err(|e| ParseError { offset: at(equals + 1 + e.offset), ..e })?;
212 found.push((name.to_owned(), expression));
213 }
214 start = end + 1;
215 }
216 Ok(found)
217}
218
219fn top_level(source: &str, wanted: u8) -> Vec<usize> {
222 let bytes = source.as_bytes();
223 let (mut quoted, mut brackets, mut found) = (false, 0usize, Vec::new());
224 for (i, &byte) in bytes.iter().enumerate() {
225 match byte {
226 b'\'' => quoted = !quoted,
227 _ if quoted => {}
228 b'[' => brackets += 1,
229 b']' => brackets = brackets.saturating_sub(1),
230 _ if brackets > 0 => {}
231 b'=' if wanted == b'=' => {
232 let before = i.checked_sub(1).map(|j| bytes[j]);
233 let after = bytes.get(i + 1).copied();
234 let operator = matches!(before, Some(b'<' | b'>' | b'!' | b'=')) || after == Some(b'=');
235 if !operator {
236 found.push(i);
237 }
238 }
239 _ if byte == wanted => found.push(i),
240 _ => {}
241 }
242 }
243 found
244}
245
246struct Parser {
247 toks: Vec<(Tok, usize)>,
248 i: usize,
249 depth: u32,
250}
251
252pub fn parse(source: &str) -> Result<Criteria, ParseError> {
254 parse_at(source, 0)
255}
256
257fn parse_at(source: &str, base: usize) -> Result<Criteria, ParseError> {
258 let toks = lex(source, base)?;
259 let mut p = Parser { toks, i: 0, depth: 0 };
260 let node = p.or()?;
261 match p.peek() {
262 Tok::End => Ok(node),
263 other => Err(p.err(format!("unexpected {}", describe(other)))),
264 }
265}
266
267fn describe(t: &Tok) -> String {
268 match t {
269 Tok::End => "end of expression".into(),
270 Tok::Ident(s) => format!("'{s}'"),
271 Tok::Op(o) => format!("'{o}'"),
272 Tok::Field(f, _) => format!("'[{f}]'"),
273 Tok::RParen => "')'".into(),
274 Tok::LParen => "'('".into(),
275 Tok::Comma => "','".into(),
276 Tok::Dot => "'.'".into(),
277 Tok::Num(n) => format!("'{n}'"),
278 Tok::Str(s) => format!("'{s}'"),
279 Tok::Date(d) => format!("'{d}'"),
280 Tok::Param(p) => format!("'?{p}'"),
281 }
282}
283
284fn bin(op: BinaryOp, a: Criteria, b: Criteria) -> Criteria {
285 Criteria::Binary(op, Box::new(a), Box::new(b))
286}
287
288impl Parser {
289 fn peek(&self) -> &Tok {
290 &self.toks[self.i].0
291 }
292
293 fn offset(&self) -> usize {
294 self.toks[self.i].1
295 }
296
297 fn err(&self, message: impl Into<String>) -> ParseError {
298 ParseError { message: message.into(), offset: self.offset() }
299 }
300
301 fn bump(&mut self) -> Tok {
302 let t = self.toks[self.i].0.clone();
303 if self.i + 1 < self.toks.len() {
304 self.i += 1;
305 }
306 t
307 }
308
309 fn is_word(&self, word: &str) -> bool {
310 matches!(self.peek(), Tok::Ident(s) if s.eq_ignore_ascii_case(word))
311 }
312
313 fn is_op(&self, ops: &[&str]) -> Option<&'static str> {
314 match self.peek() {
315 Tok::Op(o) if ops.contains(o) => Some(o),
316 _ => None,
317 }
318 }
319
320 fn expect(&mut self, t: &Tok) -> Result<(), ParseError> {
321 if self.peek() == t {
322 self.bump();
323 Ok(())
324 } else {
325 Err(self.err(format!("expected {}, found {}", describe(t), describe(self.peek()))))
326 }
327 }
328
329 fn enter(&mut self) -> Result<(), ParseError> {
330 self.depth += 1;
331 if self.depth > MAX_DEPTH {
332 return Err(self.err("expression is nested too deeply"));
333 }
334 Ok(())
335 }
336
337 fn or(&mut self) -> Result<Criteria, ParseError> {
338 let first = self.and()?;
339 let mut items = vec![first];
340 while self.is_word("or") || self.is_op(&["||"]).is_some() {
341 self.bump();
342 items.push(self.and()?);
343 }
344 Ok(if items.len() == 1 { items.remove(0) } else { Criteria::Group(GroupOp::Or, items) })
345 }
346
347 fn and(&mut self) -> Result<Criteria, ParseError> {
348 let first = self.not()?;
349 let mut items = vec![first];
350 while self.is_word("and") || self.is_op(&["&&"]).is_some() {
351 self.bump();
352 items.push(self.not()?);
353 }
354 Ok(if items.len() == 1 { items.remove(0) } else { Criteria::Group(GroupOp::And, items) })
355 }
356
357 fn not(&mut self) -> Result<Criteria, ParseError> {
358 if self.is_word("not") || self.is_op(&["!"]).is_some() {
359 self.bump();
360 self.enter()?;
361 let inner = self.not()?;
362 self.depth -= 1;
363 return Ok(Criteria::Unary(UnaryOp::Not, Box::new(inner)));
364 }
365 self.bit_or()
366 }
367
368 fn bit_or(&mut self) -> Result<Criteria, ParseError> {
369 let mut left = self.bit_xor()?;
370 while self.is_op(&["|"]).is_some() {
371 self.bump();
372 left = bin(BinaryOp::BitOr, left, self.bit_xor()?);
373 }
374 Ok(left)
375 }
376
377 fn bit_xor(&mut self) -> Result<Criteria, ParseError> {
378 let mut left = self.bit_and()?;
379 while self.is_op(&["^"]).is_some() {
380 self.bump();
381 left = bin(BinaryOp::BitXor, left, self.bit_and()?);
382 }
383 Ok(left)
384 }
385
386 fn bit_and(&mut self) -> Result<Criteria, ParseError> {
387 let mut left = self.comparison()?;
388 while self.is_op(&["&"]).is_some() {
389 self.bump();
390 left = bin(BinaryOp::BitAnd, left, self.comparison()?);
391 }
392 Ok(left)
393 }
394
395 fn comparison(&mut self) -> Result<Criteria, ParseError> {
396 let mut left = self.additive()?;
397 loop {
398 if let Some(op) = self.is_op(&["=", "==", "<>", "!=", "<", "<=", ">", ">="]) {
399 self.bump();
400 let op = match op {
401 "=" | "==" => BinaryOp::Eq,
402 "<>" | "!=" => BinaryOp::Ne,
403 "<" => BinaryOp::Lt,
404 "<=" => BinaryOp::Le,
405 ">" => BinaryOp::Gt,
406 _ => BinaryOp::Ge,
407 };
408 left = bin(op, left, self.additive()?);
409 continue;
410 }
411 let negated = self.is_word("not")
413 && matches!(self.toks.get(self.i + 1), Some((Tok::Ident(w), _))
414 if ["in", "between", "like"].iter().any(|k| w.eq_ignore_ascii_case(k)));
415 if negated {
416 self.bump();
417 }
418 let wrap = |c: Criteria| if negated { Criteria::Unary(UnaryOp::Not, Box::new(c)) } else { c };
419 if self.is_word("like") {
420 self.bump();
421 let pattern = self.additive()?;
422 left = wrap(bin(BinaryOp::Like, left, pattern));
423 } else if self.is_word("in") {
424 self.bump();
425 self.expect(&Tok::LParen)?;
426 let items = self.args()?;
427 left = wrap(Criteria::In(Box::new(left), items));
428 } else if self.is_word("between") {
429 self.bump();
430 self.expect(&Tok::LParen)?;
431 let lo = self.or()?;
432 self.expect(&Tok::Comma)?;
433 let hi = self.or()?;
434 self.expect(&Tok::RParen)?;
435 left = wrap(Criteria::Between(Box::new(left), Box::new(lo), Box::new(hi)));
436 } else if self.is_word("is") {
437 self.bump();
438 let is_not = self.is_word("not");
439 if is_not {
440 self.bump();
441 }
442 if !self.is_word("null") {
443 return Err(self.err("expected 'Null' after 'Is'"));
444 }
445 self.bump();
446 let c = Criteria::IsNull(Box::new(left));
447 left = if is_not { Criteria::Unary(UnaryOp::Not, Box::new(c)) } else { c };
448 } else {
449 return Ok(left);
450 }
451 }
452 }
453
454 fn additive(&mut self) -> Result<Criteria, ParseError> {
455 let mut left = self.multiplicative()?;
456 while let Some(op) = self.is_op(&["+", "-"]) {
457 self.bump();
458 let op = if op == "+" { BinaryOp::Add } else { BinaryOp::Sub };
459 left = bin(op, left, self.multiplicative()?);
460 }
461 Ok(left)
462 }
463
464 fn multiplicative(&mut self) -> Result<Criteria, ParseError> {
465 let mut left = self.unary()?;
466 while let Some(op) = self.is_op(&["*", "/", "%"]) {
467 self.bump();
468 let op = match op {
469 "*" => BinaryOp::Mul,
470 "/" => BinaryOp::Div,
471 _ => BinaryOp::Rem,
472 };
473 left = bin(op, left, self.unary()?);
474 }
475 Ok(left)
476 }
477
478 fn unary(&mut self) -> Result<Criteria, ParseError> {
479 if let Some(op) = self.is_op(&["-", "+", "~"]) {
480 self.bump();
481 self.enter()?;
482 let inner = self.unary()?;
483 self.depth -= 1;
484 return Ok(match op {
485 "-" => Criteria::Unary(UnaryOp::Minus, Box::new(inner)),
486 "~" => Criteria::Unary(UnaryOp::BitNot, Box::new(inner)),
487 _ => inner,
488 });
489 }
490 self.primary()
491 }
492
493 fn args(&mut self) -> Result<Vec<Criteria>, ParseError> {
495 let mut args = Vec::new();
496 if *self.peek() == Tok::RParen {
497 self.bump();
498 return Ok(args);
499 }
500 self.enter()?;
501 loop {
502 args.push(self.or()?);
503 let at = self.offset();
504 match self.bump() {
505 Tok::Comma => {}
506 Tok::RParen => {
507 self.depth -= 1;
508 return Ok(args);
509 }
510 other => {
511 return Err(ParseError {
512 message: format!("expected ',' or ')', found {}", describe(&other)),
513 offset: at,
514 });
515 }
516 }
517 }
518 }
519
520 fn primary(&mut self) -> Result<Criteria, ParseError> {
521 let at = self.offset();
522 match self.bump() {
523 Tok::Num(n) => Ok(Criteria::Value(Value::Number(n))),
524 Tok::Str(s) => Ok(Criteria::Value(Value::Text(s))),
525 Tok::Date(d) => Ok(Criteria::Value(Value::Date(d))),
526 Tok::Param(p) if p.is_empty() => Ok(Criteria::Value(Value::Null)),
527 Tok::Param(p) => Ok(Criteria::Parameter(p)),
528 Tok::LParen => {
529 self.enter()?;
530 let inner = self.or()?;
531 self.depth -= 1;
532 self.expect(&Tok::RParen)?;
533 Ok(inner)
534 }
535 Tok::Field(name, _) if name.trim().is_empty() => {
536 Err(ParseError { message: "empty field '[]'".into(), offset: at })
537 }
538 Tok::Field(name, _) => self.field(&name, at),
539 Tok::Ident(word) => self.word(&word, at),
540 other => Err(ParseError { message: format!("unexpected {}", describe(&other)), offset: at }),
541 }
542 }
543
544 fn field(&mut self, name: &str, at: usize) -> Result<Criteria, ParseError> {
545 let path: Vec<String> = name.split('.').map(|s| s.trim().to_owned()).collect();
546 if path.iter().any(String::is_empty) {
547 return Err(ParseError { message: format!("invalid field '[{name}]'"), offset: at });
548 }
549 if path.len() == 2 && path[0].eq_ignore_ascii_case("parameters") {
551 return Ok(Criteria::Parameter(path[1].clone()));
552 }
553 if let Tok::Field(inner, inner_at) = self.peek().clone() {
555 self.bump();
556 let condition = if inner.trim().is_empty() { None } else { Some(parse_at(&inner, inner_at)?) };
557 self.expect(&Tok::Dot)?;
558 let fat = self.offset();
559 let Tok::Ident(f) = self.bump() else {
560 return Err(ParseError { message: "expected an aggregate after '].'".into(), offset: fat });
561 };
562 let function = Aggregate::from_name(&f)
563 .ok_or_else(|| ParseError { message: format!("unknown aggregate '{f}'"), offset: fat })?;
564 self.expect(&Tok::LParen)?;
565 let args = self.args()?;
566 check_aggregate(function, args.len(), fat)?;
567 return Ok(Criteria::Aggregate(Box::new(AggregateCall {
568 collection: Some(path),
569 condition,
570 function,
571 args,
572 })));
573 }
574 Ok(Criteria::Property(path))
575 }
576
577 fn word(&mut self, word: &str, at: usize) -> Result<Criteria, ParseError> {
578 let lower = word.to_lowercase();
579 match lower.as_str() {
580 "true" => return Ok(Criteria::Value(Value::Bool(true))),
581 "false" => return Ok(Criteria::Value(Value::Bool(false))),
582 "null" => return Ok(Criteria::Value(Value::Null)),
583 _ => {}
584 }
585 if *self.peek() != Tok::LParen {
586 return Err(ParseError {
587 message: format!("unknown name '{word}' (fields go in brackets: [{word}])"),
588 offset: at,
589 });
590 }
591 self.bump();
592 let args = self.args()?;
593 let scalar = Function::from_name(word)
595 .filter(|f| !matches!(f, Function::Min2 | Function::Max2) || args.len() == 2);
596 if let Some(f) = scalar {
597 let (min, max) = f.arity();
598 if args.len() < min || args.len() > max || (f == Function::Iif && args.len() % 2 == 0) {
599 return Err(ParseError {
600 message: format!("wrong number of arguments ({}) for {}", args.len(), f.info().signature),
601 offset: at,
602 });
603 }
604 return Ok(Criteria::Function(f, args));
605 }
606 if let Some(function) = Aggregate::from_name(word) {
607 check_aggregate(function, args.len(), at)?;
608 return Ok(Criteria::Aggregate(Box::new(AggregateCall {
609 collection: None,
610 condition: None,
611 function,
612 args,
613 })));
614 }
615 Ok(Criteria::Custom(word.to_owned(), args))
617 }
618}
619
620fn check_aggregate(function: Aggregate, args: usize, at: usize) -> Result<(), ParseError> {
621 let (min, max) = function.arity();
622 if args < min || args > max {
623 return Err(ParseError {
624 message: format!("wrong number of arguments ({args}) for {}", function.info().signature),
625 offset: at,
626 });
627 }
628 Ok(())
629}