use crate::query::{
Matcher,
parse::{
parser::{format_unexpected_token, format_unknown_key},
tokenizer::{Token, TokenKind, TokenSpan},
},
queryable::{QueryKeyValue, Queryable},
};
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error<E: Queryable>
where
<E::KeyValue as QueryKeyValue>::Err: std::fmt::Debug + std::fmt::Display,
{
#[error(fmt = format_unexpected_token)]
UnexpectedToken { expected: TokenKind, found: Token },
#[error("The input stream contained {found} but expected one of: {expected:?}")]
UnexpectedTokens {
expected: Vec<TokenKind>,
found: Token,
},
#[error(fmt = format_unknown_key::<E>)]
UnknownKey {
err: <E::KeyValue as QueryKeyValue>::Err,
key: String,
at: TokenSpan,
},
#[error("The Query was not completely parsed. Parsing stopped at: {0}")]
LeftoverTokens(Token),
}
impl<E: Queryable> From<super::parsing::Error<E>> for Error<E>
where
<E::KeyValue as QueryKeyValue>::Err: std::fmt::Debug + std::fmt::Display,
{
fn from(value: super::parsing::Error<E>) -> Self {
match value {
super::parsing::Error::UnexpectedToken { expected, found } => {
Self::UnexpectedToken { expected, found }
}
super::parsing::Error::UnknownKey { err, key, at } => Self::UnknownKey { err, key, at },
}
}
}
pub(super) struct Parser<'a, E: Queryable> {
parent: &'a mut super::Parser<'a, E>,
}
impl<'a, E: Queryable> Parser<'a, E>
where
<E::KeyValue as QueryKeyValue>::Err: std::fmt::Debug + std::fmt::Display,
{
pub(super) fn parse(parent: &'a mut super::Parser<'a, E>) -> Result<Matcher<E>, Error<E>> {
let mut me = Parser { parent };
let output = me.parse_matcher()?;
let next = me.parent.tokenizer.next_token();
if next.kind() == TokenKind::Eof {
Ok(output)
} else {
Err(Error::LeftoverTokens(next))
}
}
fn parse_matcher(&mut self) -> Result<Matcher<E>, Error<E>> {
if self.parent.tokenizer.peek().kind() == TokenKind::ParenOpen {
self.parent.expect(TokenKind::ParenOpen)?;
let lhs = self.parse_matcher()?;
self.parent.expect(TokenKind::Break)?;
let mode = match self.parent.tokenizer.next_token() {
token if token.kind == TokenKind::And => TokenKind::And,
token if token.kind == TokenKind::Or => TokenKind::Or,
other => {
return Err(Error::UnexpectedTokens {
expected: vec![TokenKind::And, TokenKind::Or],
found: other,
});
}
};
self.parent.expect(TokenKind::Break)?;
let rhs = self.parse_matcher()?;
self.parent.expect(TokenKind::ParenClose)?;
match mode {
TokenKind::And => Ok(Matcher::And {
lhs: Box::new(lhs),
rhs: Box::new(rhs),
}),
TokenKind::Or => Ok(Matcher::Or {
lhs: Box::new(lhs),
rhs: Box::new(rhs),
}),
_ => unreachable!("Checked above"),
}
} else {
let key_tokens = self
.parent
.take_while(|t| matches!(t, TokenKind::Char(_)))?;
self.parent.expect(TokenKind::Colon)?;
let value_tokens = self
.parent
.take_while(|t| matches!(t, TokenKind::Char(_)))?;
let value = super::Parser::<'a, E>::parse_value_from(&value_tokens);
let key_value = self.parent.parse_key_from(
&key_tokens,
value,
value_tokens.last().map_or(0, |t| t.span().stop()),
)?;
Ok(Matcher::Match { key_value })
}
}
}
#[cfg(test)]
mod test {
use pretty_assertions::assert_eq;
use crate::query::{
Matcher::{And, Match, Or},
ParseMode, Query,
parse::parser::test::{
QueryTestKey1::{Value1, Value2},
QueryTestKeyValue::{Key1, Key2, Key3},
QueryTestObj,
},
};
#[test]
fn test_simple_query() {
let input = "(key1:value1 AND 'key2:state p2 ')";
let query = Query::<QueryTestObj>::from_continuous_str(&(), input, ParseMode::Strict)
.map_err(|err| panic!("{err}"))
.unwrap();
assert_eq!(
query,
Query {
root: Some(And {
lhs: Box::new(Match {
key_value: Key1(Value1),
}),
rhs: Box::new(Match {
key_value: Key2("state p2 \u{f0d58}".to_owned()),
}),
},)
}
);
}
#[test]
fn test_complexer_query() {
let input = "((key1:value1 AND 'key2:state p2 ') OR ((key1:value2 AND 'key2:state ') \
AND key3:20))";
let query = Query::<QueryTestObj>::from_continuous_str(&(), input, ParseMode::Strict)
.map_err(|err| panic!("{err}"))
.unwrap();
assert_eq!(
query,
Query {
root: Some(Or {
lhs: Box::new(And {
lhs: Box::new(Match {
key_value: Key1(Value1,),
}),
rhs: Box::new(Match {
key_value: Key2("state p2 \u{f0d58}".to_owned()),
}),
}),
rhs: Box::new(And {
lhs: Box::new(And {
lhs: Box::new(Match {
key_value: Key1(Value2),
}),
rhs: Box::new(Match {
key_value: Key2("state \u{f0d58}".to_owned()),
}),
}),
rhs: Box::new(Match {
key_value: Key3 {
_value: 20,
original: "20".to_owned(),
},
}),
}),
},),
}
);
}
}