use crate::types::Value;
#[derive(Debug)]
pub enum Criterion {
NumEq(f64),
NumNe(f64),
NumLt(f64),
NumGt(f64),
NumLe(f64),
NumGe(f64),
TextEq(String),
TextNe(String),
WildcardEq(Vec<char>),
BoolEq(bool),
}
pub fn flatten_to_vec(v: &Value) -> Vec<&Value> {
match v {
Value::Array(arr) => arr.iter().collect(),
other => vec![other],
}
}
pub fn parse_criterion(v: &Value) -> Criterion {
match v {
Value::Number(n) => Criterion::NumEq(*n),
Value::Bool(b) => Criterion::BoolEq(*b),
Value::Text(s) => parse_criterion_str(s),
_ => Criterion::TextEq(String::new()), }
}
fn parse_criterion_str(s: &str) -> Criterion {
let (op, rest) = if let Some(r) = s.strip_prefix("<>") {
("<>", r)
} else if let Some(r) = s.strip_prefix(">=") {
(">=", r)
} else if let Some(r) = s.strip_prefix("<=") {
("<=", r)
} else if let Some(r) = s.strip_prefix('>') {
(">", r)
} else if let Some(r) = s.strip_prefix('<') {
("<", r)
} else if let Some(r) = s.strip_prefix('=') {
("=", r)
} else {
("", s)
};
if !op.is_empty() || rest.parse::<f64>().is_ok() {
if let Ok(n) = rest.parse::<f64>() {
return match op {
"<>" => Criterion::NumNe(n),
">=" => Criterion::NumGe(n),
"<=" => Criterion::NumLe(n),
">" => Criterion::NumGt(n),
"<" => Criterion::NumLt(n),
_ => Criterion::NumEq(n), };
}
if op == "<>" {
return Criterion::TextNe(rest.to_lowercase());
}
return Criterion::TextEq(s.to_lowercase());
}
if rest.contains('*') || rest.contains('?') {
return Criterion::WildcardEq(rest.to_lowercase().chars().collect());
}
Criterion::TextEq(rest.to_lowercase())
}
pub fn matches_criterion(value: &Value, crit: &Criterion) -> bool {
match crit {
Criterion::NumEq(n) => match value {
Value::Number(v) => (v - n).abs() < 1e-10,
_ => false,
},
Criterion::NumNe(n) => match value {
Value::Number(v) => (v - n).abs() >= 1e-10,
_ => true, },
Criterion::NumLt(n) => matches!(value, Value::Number(v) if v < n),
Criterion::NumGt(n) => matches!(value, Value::Number(v) if v > n),
Criterion::NumLe(n) => matches!(value, Value::Number(v) if v <= n),
Criterion::NumGe(n) => matches!(value, Value::Number(v) if v >= n),
Criterion::TextEq(pat) => match value {
Value::Text(s) => s.to_lowercase() == *pat,
Value::Bool(b) => {
let s = if *b { "true" } else { "false" };
s == pat.as_str()
}
_ => false,
},
Criterion::TextNe(pat) => match value {
Value::Text(s) => s.to_lowercase() != *pat,
_ => true,
},
Criterion::WildcardEq(pattern) => match value {
Value::Text(s) => {
let text: Vec<char> = s.to_lowercase().chars().collect();
wildcard_match(pattern, &text)
}
_ => false,
},
Criterion::BoolEq(b) => matches!(value, Value::Bool(v) if v == b),
}
}
fn wildcard_match(pattern: &[char], text: &[char]) -> bool {
match (pattern.first(), text.first()) {
(None, None) => true,
(None, _) => false,
(Some('*'), _) => {
for i in 0..=text.len() {
if wildcard_match(&pattern[1..], &text[i..]) {
return true;
}
}
false
}
(Some(_), None) => false,
(Some(p), Some(t)) => {
if *p == '?' || *p == *t {
wildcard_match(&pattern[1..], &text[1..])
} else {
false
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::Value;
fn num(n: f64) -> Value { Value::Number(n) }
fn text(s: &str) -> Value { Value::Text(s.to_string()) }
#[test]
fn numeric_eq() {
let c = parse_criterion(&num(3.0));
assert!(matches_criterion(&num(3.0), &c));
assert!(!matches_criterion(&num(4.0), &c));
}
#[test]
fn text_criterion_gt() {
let c = parse_criterion(&text(">2"));
assert!(matches_criterion(&num(3.0), &c));
assert!(!matches_criterion(&num(1.0), &c));
}
#[test]
fn text_criterion_ne_num() {
let c = parse_criterion(&text("<>2"));
assert!(matches_criterion(&num(3.0), &c));
assert!(!matches_criterion(&num(2.0), &c));
}
#[test]
fn text_criterion_exact() {
let c = parse_criterion(&text("apple"));
assert!(matches_criterion(&text("Apple"), &c)); assert!(!matches_criterion(&text("banana"), &c));
}
#[test]
fn text_criterion_wildcard_star() {
let c = parse_criterion(&text("a*"));
assert!(matches_criterion(&text("apple"), &c));
assert!(matches_criterion(&text("a"), &c));
assert!(!matches_criterion(&text("banana"), &c));
}
#[test]
fn text_criterion_wildcard_question() {
let c = parse_criterion(&text("ap?"));
assert!(matches_criterion(&text("apt"), &c));
assert!(matches_criterion(&text("ape"), &c));
assert!(!matches_criterion(&text("apple"), &c));
}
#[test]
fn bool_criterion() {
let c = parse_criterion(&Value::Bool(true));
assert!(matches_criterion(&Value::Bool(true), &c));
assert!(!matches_criterion(&Value::Bool(false), &c));
}
#[test]
fn flatten_array() {
let arr = Value::Array(vec![num(1.0), num(2.0), num(3.0)]);
let flat = flatten_to_vec(&arr);
assert_eq!(flat.len(), 3);
}
#[test]
fn flatten_scalar() {
let v = num(5.0);
let flat = flatten_to_vec(&v);
assert_eq!(flat.len(), 1);
}
}