1pub use serde_json::{json, Value};
2
3mod parser {
4 use super::{Expr, Schema};
5 use nom::{
6 branch::alt,
7 character::complete::{char, multispace0},
8 multi::separated_list0,
9 sequence::{delimited, pair, preceded},
10 IResult,
11 };
12
13 pub fn evaluate(input: &str) -> IResult<&str, Schema> {
15 alt((or_expr, and_expr, parens, condition))(input)
16 }
17
18 fn condition(input: &str) -> IResult<&str, Schema> {
19 let (input, cond) = nom::bytes::complete::take_while(|c: char| {
20 c.is_alphanumeric() || c.is_whitespace() || "><=!.".contains(c)
21 })(input)?;
22 Ok((
23 input,
24 Schema::Expr(Expr::from_string(cond.trim().to_string()).unwrap()),
25 ))
26 }
27
28 fn parens(input: &str) -> IResult<&str, Schema> {
29 delimited(
30 pair(char('('), multispace0),
31 evaluate,
32 pair(multispace0, char(')')),
33 )(input)
34 }
35
36 fn and_expr(input: &str) -> IResult<&str, Schema> {
37 let (input, exprs) = separated_list0(
38 preceded(multispace0, char('&')),
39 preceded(multispace0, alt((parens, condition))),
40 )(input)?;
41 Ok((input, Schema::And(exprs)))
42 }
43
44 fn or_expr(input: &str) -> IResult<&str, Schema> {
45 let (input, exprs) = separated_list0(
46 preceded(multispace0, char('|')),
47 preceded(multispace0, alt((parens, and_expr))),
48 )(input)?;
49 Ok((input, Schema::Or(exprs)))
50 }
51}
52
53#[derive(Debug, Clone, Copy, Eq, PartialEq)]
54pub enum Operand {
55 GreaterThan,
56 Equal,
57 LessThan,
58 GreaterOrEqual,
59 LessOrEqual,
60 Contains,
61}
62
63impl Operand {
64 fn from_str(s: &str) -> Option<Self> {
65 match s {
66 ">" => Some(Self::GreaterThan),
67 "==" => Some(Self::Equal),
68 "<" => Some(Self::LessThan),
69 ">=" => Some(Self::GreaterOrEqual),
70 "<=" => Some(Self::LessOrEqual),
71 "contains" => Some(Self::Contains),
72 _ => None,
73 }
74 }
75}
76
77#[derive(Debug, Eq, PartialEq)]
78pub struct Expr {
79 key: String,
80 op: Operand,
81 val: Value,
82 default: Option<bool>,
84 dict_key: Option<String>,
86}
87
88impl Expr {
89 fn new(key: &str, op: &str, val: &str) -> Self {
90 Self {
91 key: key.trim().to_string(),
92 op: Operand::from_str(op).unwrap(),
93 val: string_to_serde_value(val.trim()),
94 default: None,
95 dict_key: None,
96 }
97 }
98
99 fn from_string(s: String) -> Option<Self> {
100 let s = s.trim();
101
102 for op in ["==", "<", ">", "<=", ">=", "contains"] {
103 if let Some((key, val)) = s.split_once(op) {
104 return Some(Self::new(key, op, val));
105 }
106 }
107
108 None
109 }
110}
111
112#[derive(Debug, Eq, PartialEq)]
113pub enum Schema {
114 Expr(Expr),
115 And(Vec<Schema>),
116 Or(Vec<Schema>),
117}
118
119impl Schema {
120 fn new(s: String) -> Option<Self> {
121 let mut schema = parser::evaluate(&s).unwrap().1;
122 schema.simplify();
123 Some(schema)
124 }
125
126 fn simplify(&mut self) {
127 let inner = match self {
128 Schema::Expr(_) => return,
129 Schema::And(s) | Schema::Or(s) => {
130 if s.len() == 1 {
131 Some(s.remove(0))
132 } else {
133 for x in s.iter_mut() {
134 x.simplify();
135 }
136 None
137 }
138 }
139 };
140
141 if let Some(i) = inner {
142 *self = i;
143 }
144 }
145}
146
147fn string_to_serde_value(input: &str) -> Value {
148 if let Ok(boolean_value) = input.parse::<bool>() {
149 return Value::Bool(boolean_value);
150 }
151
152 if let Ok(number_value) = input.parse::<f64>() {
153 return Value::Number(serde_json::Number::from_f64(number_value).unwrap());
154 }
155
156 Value::String(input.to_string())
157}
158
159pub trait Matcher: std::fmt::Debug {
160 fn get_val(&self, key: &str) -> Option<Value>;
161
162 fn eval(&self, s: String) -> bool {
163 let schema = Schema::new(s).unwrap();
164 self.eval_schema(&schema)
165 }
166
167 fn eval_schema(&self, schema: &Schema) -> bool {
168 match schema {
169 Schema::And(exps) => exps.iter().all(|exp| self.eval_schema(exp)),
170 Schema::Or(exps) => exps.iter().any(|exp| self.eval_schema(exp)),
171 Schema::Expr(exp) => self.is_match(exp),
172 }
173 }
174
175 fn is_match(&self, x: &Expr) -> bool {
176 let op = x.op;
177 let val = x.val.to_owned();
178 let arg = self.get_val(&x.key).unwrap().into();
179
180 use Operand as OP;
181 use Value::*;
182
183 match (arg, op, val) {
184 (Number(val), OP::GreaterOrEqual, Number(arg)) => val.as_f64() >= arg.as_f64(),
185 (Number(val), OP::LessOrEqual, Number(arg)) => val.as_f64() <= arg.as_f64(),
186 (Number(val), OP::GreaterThan, Number(arg)) => val.as_f64() > arg.as_f64(),
187 (Number(val), OP::LessThan, Number(arg)) => val.as_f64() < arg.as_f64(),
188 (String(val), OP::Contains, String(arg)) => val.contains(&arg),
189 (String(val), OP::Equal, String(arg)) => val == arg,
190 (Number(val), OP::Equal, Number(arg)) => val.as_f64() == arg.as_f64(),
191 (Bool(val), OP::Equal, Bool(arg)) => val == arg,
192 (val, op, arg) => {
193 panic!(
194 "incorrect combination of operands and operator: {} {:?} {}",
195 val, op, arg
196 );
197 }
198 }
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205
206 #[derive(Debug)]
207 struct TestStruct {}
208
209 use serde_json::json;
210
211 impl Matcher for TestStruct {
212 fn get_val(&self, key: &str) -> Option<Value> {
213 match key {
214 "age" => Some(json!(5.)),
215 "height" => Some(json!(180)),
216 "msg" => Some(json!("hello world")),
217 _ => None,
218 }
219 }
220 }
221
222 #[test]
223 fn loltest() {
224 let x = TestStruct {};
225 let input = "height > 170 && msg contains worl".to_string();
226 let res = x.eval(input);
227 dbg!(res);
228 panic!();
229 }
230
231 #[test]
232 fn test1() {
233 let input = "a>4".to_string();
234 assert_eq!(
235 Schema::new(input).unwrap(),
236 Schema::Expr(Expr::new("a", ">", "4"))
237 );
238 }
239
240 #[test]
241 fn test2() {
242 let input = "a < 3".to_string();
243 assert_eq!(
244 Schema::new(input).unwrap(),
245 Schema::Expr(Expr::new("a", "<", "3"))
246 );
247 }
248
249 #[test]
250 fn test3() {
251 let input = "a contains helloworld & b < 4 | c == true".to_string();
252 assert_eq!(
253 Schema::new(input).unwrap(),
254 Schema::Or(vec![
255 Schema::And(vec![
256 Schema::Expr(Expr::new("a", "contains", "helloworld")),
257 Schema::Expr(Expr::new("b", "<", "4"))
258 ]),
259 Schema::Expr(Expr::new("c", "==", "true"))
260 ])
261 );
262 }
263
264 #[test]
265 fn test4() {
266 let input = "a contains helloworld & (b < 4 | c == true)".to_string();
267 assert_eq!(
268 Schema::new(input).unwrap(),
269 Schema::And(vec![
270 Schema::Expr(Expr::new("a", "contains", "helloworld")),
271 Schema::Or(vec![
272 Schema::Expr(Expr::new("b", "<", "4")),
273 Schema::Expr(Expr::new("c", "==", "true"))
274 ]),
275 ])
276 );
277 }
278
279 #[test]
280 fn test5() {
281 let input = "a contains heyworld".to_string();
282 assert_eq!(
283 Schema::new(input).unwrap(),
284 Schema::Expr(Expr::new("a", "contains", "heyworld"))
285 );
286 }
287
288 #[test]
289 fn tokentest() {
290 let input = "a contains helloworld & (b < 4 | c == true)".to_string();
291 let result = Schema::new(input);
292 dbg!(result);
293 panic!();
294 }
295}