pub use serde_json::{json, Value};
mod parser {
use super::{Expr, Schema};
use nom::{
branch::alt,
character::complete::{char, multispace0},
multi::separated_list0,
sequence::{delimited, pair, preceded},
IResult,
};
pub fn evaluate(input: &str) -> IResult<&str, Schema> {
alt((or_expr, and_expr, parens, condition))(input)
}
fn condition(input: &str) -> IResult<&str, Schema> {
let (input, cond) = nom::bytes::complete::take_while(|c: char| {
c.is_alphanumeric() || c.is_whitespace() || "><=!".contains(c)
})(input)?;
Ok((
input,
Schema::Expr(Expr::from_string(cond.trim().to_string()).unwrap()),
))
}
fn parens(input: &str) -> IResult<&str, Schema> {
delimited(
pair(char('('), multispace0),
evaluate,
pair(multispace0, char(')')),
)(input)
}
fn and_expr(input: &str) -> IResult<&str, Schema> {
let (input, exprs) = separated_list0(
preceded(multispace0, char('&')),
preceded(multispace0, alt((parens, condition))),
)(input)?;
Ok((input, Schema::And(exprs)))
}
fn or_expr(input: &str) -> IResult<&str, Schema> {
let (input, exprs) = separated_list0(
preceded(multispace0, char('|')),
preceded(multispace0, alt((parens, and_expr))),
)(input)?;
Ok((input, Schema::Or(exprs)))
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum Operand {
GreaterThan,
Equal,
LessThan,
GreaterOrEqual,
LessOrEqual,
Contains,
}
impl Operand {
fn from_str(s: &str) -> Option<Self> {
match s {
">" => Some(Self::GreaterThan),
"==" => Some(Self::Equal),
"<" => Some(Self::LessThan),
">=" => Some(Self::GreaterOrEqual),
"<=" => Some(Self::LessOrEqual),
"contains" => Some(Self::Contains),
_ => None,
}
}
}
#[derive(Debug, Eq, PartialEq)]
struct Expr {
key: String,
op: Operand,
val: Value,
default: Option<bool>,
dict_key: Option<String>,
}
impl Expr {
fn new(key: &str, op: &str, val: &str) -> Self {
Self {
key: key.trim().to_string(),
op: Operand::from_str(op).unwrap(),
val: string_to_serde_value(val.trim()),
default: None,
dict_key: None,
}
}
fn from_string(s: String) -> Option<Self> {
let s = s.trim();
for op in ["==", "<", ">", "<=", ">=", "contains"] {
if let Some((key, val)) = s.split_once(op) {
return Some(Self::new(key, op, val));
}
}
None
}
}
#[derive(Debug, Eq, PartialEq)]
enum Schema {
Expr(Expr),
And(Vec<Schema>),
Or(Vec<Schema>),
}
impl Schema {
fn new(s: String) -> Option<Self> {
let mut schema = parser::evaluate(&s).unwrap().1;
schema.simplify();
Some(schema)
}
fn simplify(&mut self) {
let inner = match self {
Schema::Expr(_) => return,
Schema::And(s) | Schema::Or(s) => {
if s.len() == 1 {
Some(s.remove(0))
} else {
for x in s.iter_mut() {
x.simplify();
}
None
}
}
};
if let Some(i) = inner {
*self = i;
}
}
}
fn string_to_serde_value(input: &str) -> Value {
if let Ok(boolean_value) = input.parse::<bool>() {
return Value::Bool(boolean_value);
}
if let Ok(number_value) = input.parse::<f64>() {
return Value::Number(serde_json::Number::from_f64(number_value).unwrap());
}
Value::String(input.to_string())
}
pub trait Matcher {
fn get_val(&self, key: &str) -> Option<Value>;
fn eval(&self, s: String) -> bool {
let schema = Schema::new(s).unwrap();
dbg!(&schema);
self.eval_schema(&schema)
}
fn eval_schema(&self, schema: &Schema) -> bool {
dbg!(match schema {
Schema::And(exps) => exps.iter().all(|exp| self.eval_schema(exp)),
Schema::Or(exps) => exps.iter().any(|exp| self.eval_schema(exp)),
Schema::Expr(exp) => self.is_match(exp),
})
}
fn is_match(&self, x: &Expr) -> bool {
let op = x.op;
let val = x.val.to_owned();
let arg = self.get_val(&x.key).unwrap().into();
use Operand as OP;
use Value::*;
dbg!(&val, &op, &arg);
dbg!(match (arg, op, val) {
(Number(val), OP::GreaterOrEqual, Number(arg)) => val.as_f64() >= arg.as_f64(),
(Number(val), OP::LessOrEqual, Number(arg)) => val.as_f64() <= arg.as_f64(),
(Number(val), OP::GreaterThan, Number(arg)) => val.as_f64() > arg.as_f64(),
(Number(val), OP::LessThan, Number(arg)) => val.as_f64() < arg.as_f64(),
(String(val), OP::Contains, String(arg)) => val.contains(&arg),
(String(val), OP::Equal, String(arg)) => val == arg,
(Number(val), OP::Equal, Number(arg)) => val.as_f64() == arg.as_f64(),
(Bool(val), OP::Equal, Bool(arg)) => val == arg,
(val, op, arg) => {
panic!(
"incorrect combination of operands and operator: {} {:?} {}",
val, op, arg
);
}
})
}
}
mod tests {
use super::*;
struct TestStruct {}
use serde_json::json;
impl Matcher for TestStruct {
fn get_val(&self, key: &str) -> Option<Value> {
match key {
"age" => Some(json!(5.)),
"height" => Some(json!(180)),
"msg" => Some(json!("hello world")),
_ => None,
}
}
}
#[test]
fn loltest() {
let x = TestStruct {};
let input = "height > 170 && msg contains worl".to_string();
let res = x.eval(input);
dbg!(res);
panic!();
}
#[test]
fn test1() {
let input = "a>4".to_string();
assert_eq!(
Schema::new(input).unwrap(),
Schema::Expr(Expr::new("a", ">", "4"))
);
}
#[test]
fn test2() {
let input = "a < 3".to_string();
assert_eq!(
Schema::new(input).unwrap(),
Schema::Expr(Expr::new("a", "<", "3"))
);
}
#[test]
fn test3() {
let input = "a contains helloworld & b < 4 | c == true".to_string();
assert_eq!(
Schema::new(input).unwrap(),
Schema::Or(vec![
Schema::And(vec![
Schema::Expr(Expr::new("a", "contains", "helloworld")),
Schema::Expr(Expr::new("b", "<", "4"))
]),
Schema::Expr(Expr::new("c", "==", "true"))
])
);
}
#[test]
fn test4() {
let input = "a contains helloworld & (b < 4 | c == true)".to_string();
assert_eq!(
Schema::new(input).unwrap(),
Schema::And(vec![
Schema::Expr(Expr::new("a", "contains", "helloworld")),
Schema::Or(vec![
Schema::Expr(Expr::new("b", "<", "4")),
Schema::Expr(Expr::new("c", "==", "true"))
]),
])
);
}
#[test]
fn test5() {
let input = "a contains heyworld".to_string();
assert_eq!(
Schema::new(input).unwrap(),
Schema::Expr(Expr::new("a", "contains", "heyworld"))
);
}
#[test]
fn tokentest() {
let input = "a contains helloworld & (b < 4 | c == true)".to_string();
let result = Schema::new(input);
dbg!(result);
panic!();
}
}