ganit_core/eval/functions/math/
criterion.rs1use crate::types::Value;
2
3#[derive(Debug)]
5pub enum Criterion {
6 NumEq(f64),
7 NumNe(f64),
8 NumLt(f64),
9 NumGt(f64),
10 NumLe(f64),
11 NumGe(f64),
12 TextEq(String),
14 TextNe(String),
16 WildcardEq(Vec<char>),
18 BoolEq(bool),
19}
20
21pub fn flatten_to_vec(v: &Value) -> Vec<&Value> {
24 match v {
25 Value::Array(arr) => arr.iter().collect(),
26 other => vec![other],
27 }
28}
29
30pub fn parse_criterion(v: &Value) -> Criterion {
32 match v {
33 Value::Number(n) => Criterion::NumEq(*n),
34 Value::Bool(b) => Criterion::BoolEq(*b),
35 Value::Text(s) => parse_criterion_str(s),
36 _ => Criterion::TextEq(String::new()), }
38}
39
40fn parse_criterion_str(s: &str) -> Criterion {
42 let (op, rest) = if let Some(r) = s.strip_prefix("<>") {
44 ("<>", r)
45 } else if let Some(r) = s.strip_prefix(">=") {
46 (">=", r)
47 } else if let Some(r) = s.strip_prefix("<=") {
48 ("<=", r)
49 } else if let Some(r) = s.strip_prefix('>') {
50 (">", r)
51 } else if let Some(r) = s.strip_prefix('<') {
52 ("<", r)
53 } else if let Some(r) = s.strip_prefix('=') {
54 ("=", r)
55 } else {
56 ("", s)
57 };
58
59 if !op.is_empty() || rest.parse::<f64>().is_ok() {
61 if let Ok(n) = rest.parse::<f64>() {
62 return match op {
63 "<>" => Criterion::NumNe(n),
64 ">=" => Criterion::NumGe(n),
65 "<=" => Criterion::NumLe(n),
66 ">" => Criterion::NumGt(n),
67 "<" => Criterion::NumLt(n),
68 _ => Criterion::NumEq(n), };
70 }
71 if op == "<>" {
73 return Criterion::TextNe(rest.to_lowercase());
74 }
75 return Criterion::TextEq(s.to_lowercase());
77 }
78
79 if rest.contains('*') || rest.contains('?') {
81 return Criterion::WildcardEq(rest.to_lowercase().chars().collect());
82 }
83
84 Criterion::TextEq(rest.to_lowercase())
85}
86
87pub fn matches_criterion(value: &Value, crit: &Criterion) -> bool {
89 match crit {
90 Criterion::NumEq(n) => match value {
91 Value::Number(v) => (v - n).abs() < 1e-10,
92 _ => false,
93 },
94 Criterion::NumNe(n) => match value {
95 Value::Number(v) => (v - n).abs() >= 1e-10,
96 _ => true, },
98 Criterion::NumLt(n) => matches!(value, Value::Number(v) if v < n),
99 Criterion::NumGt(n) => matches!(value, Value::Number(v) if v > n),
100 Criterion::NumLe(n) => matches!(value, Value::Number(v) if v <= n),
101 Criterion::NumGe(n) => matches!(value, Value::Number(v) if v >= n),
102 Criterion::TextEq(pat) => match value {
103 Value::Text(s) => s.to_lowercase() == *pat,
104 Value::Bool(b) => {
105 let s = if *b { "true" } else { "false" };
106 s == pat.as_str()
107 }
108 _ => false,
109 },
110 Criterion::TextNe(pat) => match value {
111 Value::Text(s) => s.to_lowercase() != *pat,
112 _ => true,
113 },
114 Criterion::WildcardEq(pattern) => match value {
115 Value::Text(s) => {
116 let text: Vec<char> = s.to_lowercase().chars().collect();
117 wildcard_match(pattern, &text)
118 }
119 _ => false,
120 },
121 Criterion::BoolEq(b) => matches!(value, Value::Bool(v) if v == b),
122 }
123}
124
125fn wildcard_match(pattern: &[char], text: &[char]) -> bool {
128 match (pattern.first(), text.first()) {
129 (None, None) => true,
130 (None, _) => false,
131 (Some('*'), _) => {
132 for i in 0..=text.len() {
134 if wildcard_match(&pattern[1..], &text[i..]) {
135 return true;
136 }
137 }
138 false
139 }
140 (Some(_), None) => false,
141 (Some(p), Some(t)) => {
142 if *p == '?' || *p == *t {
143 wildcard_match(&pattern[1..], &text[1..])
144 } else {
145 false
146 }
147 }
148 }
149}
150
151#[cfg(test)]
154mod tests {
155 use super::*;
156 use crate::types::Value;
157
158 fn num(n: f64) -> Value { Value::Number(n) }
159 fn text(s: &str) -> Value { Value::Text(s.to_string()) }
160
161 #[test]
162 fn numeric_eq() {
163 let c = parse_criterion(&num(3.0));
164 assert!(matches_criterion(&num(3.0), &c));
165 assert!(!matches_criterion(&num(4.0), &c));
166 }
167
168 #[test]
169 fn text_criterion_gt() {
170 let c = parse_criterion(&text(">2"));
171 assert!(matches_criterion(&num(3.0), &c));
172 assert!(!matches_criterion(&num(1.0), &c));
173 }
174
175 #[test]
176 fn text_criterion_ne_num() {
177 let c = parse_criterion(&text("<>2"));
178 assert!(matches_criterion(&num(3.0), &c));
179 assert!(!matches_criterion(&num(2.0), &c));
180 }
181
182 #[test]
183 fn text_criterion_exact() {
184 let c = parse_criterion(&text("apple"));
185 assert!(matches_criterion(&text("Apple"), &c)); assert!(!matches_criterion(&text("banana"), &c));
187 }
188
189 #[test]
190 fn text_criterion_wildcard_star() {
191 let c = parse_criterion(&text("a*"));
192 assert!(matches_criterion(&text("apple"), &c));
193 assert!(matches_criterion(&text("a"), &c));
194 assert!(!matches_criterion(&text("banana"), &c));
195 }
196
197 #[test]
198 fn text_criterion_wildcard_question() {
199 let c = parse_criterion(&text("ap?"));
200 assert!(matches_criterion(&text("apt"), &c));
201 assert!(matches_criterion(&text("ape"), &c));
202 assert!(!matches_criterion(&text("apple"), &c));
203 }
204
205 #[test]
206 fn bool_criterion() {
207 let c = parse_criterion(&Value::Bool(true));
208 assert!(matches_criterion(&Value::Bool(true), &c));
209 assert!(!matches_criterion(&Value::Bool(false), &c));
210 }
211
212 #[test]
213 fn flatten_array() {
214 let arr = Value::Array(vec![num(1.0), num(2.0), num(3.0)]);
215 let flat = flatten_to_vec(&arr);
216 assert_eq!(flat.len(), 3);
217 }
218
219 #[test]
220 fn flatten_scalar() {
221 let v = num(5.0);
222 let flat = flatten_to_vec(&v);
223 assert_eq!(flat.len(), 1);
224 }
225}