use crate::pred::cmp::Operator;
use std::error::Error;
use std::fmt::Display;
pub(crate) struct OpParser;
#[derive(Debug)]
pub(crate) enum OpParserError<'a> {
InvalidCharsBeforeOp {
text_before_op: &'a str,
op_index: usize,
propagate_message: String,
},
#[allow(dead_code)]
Other(String),
}
impl<'a> Display for OpParserError<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidCharsBeforeOp {
propagate_message, ..
} => write!(f, "{}", propagate_message),
Self::Other(message) => write!(f, "{message}"),
}
}
}
impl<'a> Error for OpParserError<'a> {}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
struct ParsedOp {
character: char,
index: usize,
}
impl ParsedOp {
fn new(character: char, index: usize) -> Self {
Self { character, index }
}
fn relative_starting_from(self, other: &Self) -> Self {
Self::new(self.character, self.index + other.index + 1)
}
}
impl OpParser {
fn parse_op<'a, 'b>(
s: &'a str,
allowed_chars: &'b [char],
) -> Result<Option<ParsedOp>, OpParserError<'a>> {
let op_part = s.find(|c| allowed_chars.contains(&c));
match op_part {
None => Ok(None),
Some(idx) => {
let op_char_idx = {
let mut boundary = idx;
loop {
if boundary >= s.len() || s.is_char_boundary(boundary + 1) {
break boundary;
}
boundary += 1;
}
};
let subs = &s[..=op_char_idx].trim();
if subs.chars().count() > 1 {
return Err(OpParserError::InvalidCharsBeforeOp {
text_before_op: &subs[..idx],
op_index: idx,
propagate_message: format!("invalid operator: {subs}"),
});
}
let Some(char) = subs.chars().next() else {
unreachable!("prior check guarantees this should never fail");
};
Ok(Some(ParsedOp::new(char, op_char_idx)))
}
}
}
pub(crate) fn simple_op_parser(str: &str) -> Result<(Operator, &str), OpParserError<'_>> {
const SINGLE_CHAR_OP: &[char] = &['!', '≠', '=', '>', '<'];
const DOUBLE_CHAR_OP: &[char] = &['>', '<'];
const DOUBLE_CHAR_OP_NEXT: &[char] = &['='];
let op_part = Self::parse_op(str, SINGLE_CHAR_OP)?;
let operator = match op_part {
None => (Operator::Eq, str),
Some(parsed_op) => {
let new_substr = &str[parsed_op.index + 1..];
let second_op = if DOUBLE_CHAR_OP.contains(&parsed_op.character) {
Self::parse_op(new_substr, DOUBLE_CHAR_OP_NEXT)
.ok()
.and_then(|op| op.map(|op| op.relative_starting_from(&parsed_op)))
} else {
None
};
let new_str = second_op
.as_ref()
.map(|op| &str[op.index + 1..])
.unwrap_or(new_substr);
let operator = match (parsed_op.character, second_op.map(|op| op.character)) {
('!' | '≠', None) => Operator::Ne,
('=', None) => Operator::Eq,
('>', None) => Operator::Gt,
('<', None) => Operator::Lt,
('>', Some('=')) => Operator::Gte,
('<', Some('=')) => Operator::Lte,
('>', Some(c)) => unreachable!("{c} found but not included in the original array, `parse_op` guarantees this never happens."),
_ => unreachable!("prior checks guarantee this should never happen"),
};
(operator, new_str)
}
};
Ok(operator)
}
}
#[cfg(test)]
mod tests {
macro_rules! test_cases {
($($operator:expr => [$($input:literal),+] = $expected:literal,)+) => {$($(
{
let input = $input;
let (operator, rest) = OpParser::simple_op_parser(input).unwrap();
assert_eq!(operator, $operator, "input: {input}");
assert_eq!(rest, $expected, "input: {input}");
}
)+)+};
}
#[test]
fn test_simple_op_parser_good_cases() {
use super::OpParser;
use crate::pred::cmp::Operator;
test_cases!(
Operator::Eq => ["=10", "10", " =10"] = "10",
Operator::Eq => ["= 10", " 10", " = 10"] = " 10",
Operator::Eq => ["==10"] = "=10",
Operator::Ne => ["≠10", "!10", " ≠10"] = "10",
Operator::Ne => ["≠ 10", "! 10", " ≠ 10"] = " 10",
Operator::Ne => ["≠=10", "!=10"] = "=10",
Operator::Gt => [">10", " >10", " >10"] = "10",
Operator::Gte => [">=10", " >=10", " >=10"] = "10",
Operator::Gte => [">= 10", " >= 10", " >= 10"] = " 10",
Operator::Lt => ["<10", " <10", " <10"] = "10",
Operator::Lte => ["<=10", " <=10", " <=10"] = "10",
Operator::Lte => ["<= 10", " <= 10", " <= 10"] = " 10",
Operator::Lt => ["<<10", " <<10", " <<10"] = "<10",
Operator::Lt => ["<a=10", " <a=10", " <a=10"] = "a=10",
Operator::Gt => [">>10", " >>10", " >>10"] = ">10",
Operator::Gt => [">a=10", " >a=10", " >a=10"] = "a=10",
);
}
#[test]
fn test_simple_op_parser_bad_cases() {
use super::OpParser;
let bad_cases = &["a>10", "a>=10", "c<10", "c<=10", "c=10", "c!10"];
for input in bad_cases {
let result = OpParser::simple_op_parser(input);
assert!(result.is_err(), "input: {input}");
}
}
}