Skip to main content

libmagic_rs/evaluator/operators/
comparison.rs

1// Copyright (c) 2025-2026 the libmagic-rs contributors
2// SPDX-License-Identifier: Apache-2.0
3
4//! Comparison operators for magic rule evaluation
5
6use std::cmp::Ordering;
7
8use crate::parser::ast::Value;
9
10/// Compare two values and return their ordering, if comparable
11///
12/// Returns `Some(Ordering)` for same-type comparisons (integers, strings, bytes)
13/// and cross-type integer comparisons (via `i128` coercion). Returns `None` for
14/// incomparable type combinations.
15///
16/// # Examples
17///
18/// ```
19/// use std::cmp::Ordering;
20/// use libmagic_rs::parser::ast::Value;
21/// use libmagic_rs::evaluator::operators::compare_values;
22///
23/// assert_eq!(compare_values(&Value::Uint(5), &Value::Uint(10)), Some(Ordering::Less));
24/// assert_eq!(compare_values(&Value::Int(-1), &Value::Uint(0)), Some(Ordering::Less));
25/// assert_eq!(compare_values(&Value::Uint(42), &Value::Int(42)), Some(Ordering::Equal));
26/// assert_eq!(compare_values(&Value::Uint(1), &Value::String("1".to_string())), None);
27/// ```
28#[must_use]
29pub fn compare_values(left: &Value, right: &Value) -> Option<Ordering> {
30    match (left, right) {
31        (Value::Uint(a), Value::Uint(b)) => Some(a.cmp(b)),
32        (Value::Int(a), Value::Int(b)) => Some(a.cmp(b)),
33        (Value::Uint(a), Value::Int(b)) => Some(i128::from(*a).cmp(&i128::from(*b))),
34        (Value::Int(a), Value::Uint(b)) => Some(i128::from(*a).cmp(&i128::from(*b))),
35        (Value::Float(a), Value::Float(b)) => a.partial_cmp(b),
36        (Value::String(a), Value::String(b)) => Some(a.cmp(b)),
37        (Value::Bytes(a), Value::Bytes(b)) => Some(a.cmp(b)),
38        _ => None,
39    }
40}
41
42/// Apply less-than comparison between two values
43///
44/// # Examples
45///
46/// ```
47/// use libmagic_rs::parser::ast::Value;
48/// use libmagic_rs::evaluator::operators::apply_less_than;
49///
50/// assert!(apply_less_than(&Value::Uint(5), &Value::Uint(10)));
51/// assert!(!apply_less_than(&Value::Uint(10), &Value::Uint(10)));
52/// assert!(apply_less_than(&Value::Int(-1), &Value::Uint(0)));
53/// ```
54#[must_use]
55pub fn apply_less_than(left: &Value, right: &Value) -> bool {
56    compare_values(left, right) == Some(Ordering::Less)
57}
58
59/// Apply greater-than comparison between two values
60///
61/// # Examples
62///
63/// ```
64/// use libmagic_rs::parser::ast::Value;
65/// use libmagic_rs::evaluator::operators::apply_greater_than;
66///
67/// assert!(apply_greater_than(&Value::Uint(10), &Value::Uint(5)));
68/// assert!(!apply_greater_than(&Value::Uint(10), &Value::Uint(10)));
69/// assert!(apply_greater_than(&Value::Uint(0), &Value::Int(-1)));
70/// ```
71#[must_use]
72pub fn apply_greater_than(left: &Value, right: &Value) -> bool {
73    compare_values(left, right) == Some(Ordering::Greater)
74}
75
76/// Apply less-than-or-equal comparison between two values
77///
78/// # Examples
79///
80/// ```
81/// use libmagic_rs::parser::ast::Value;
82/// use libmagic_rs::evaluator::operators::apply_less_equal;
83///
84/// assert!(apply_less_equal(&Value::Uint(10), &Value::Uint(10)));
85/// assert!(apply_less_equal(&Value::Uint(5), &Value::Uint(10)));
86/// assert!(!apply_less_equal(&Value::Uint(10), &Value::Uint(5)));
87/// ```
88#[must_use]
89pub fn apply_less_equal(left: &Value, right: &Value) -> bool {
90    matches!(
91        compare_values(left, right),
92        Some(Ordering::Less | Ordering::Equal)
93    )
94}
95
96/// Apply greater-than-or-equal comparison between two values
97///
98/// # Examples
99///
100/// ```
101/// use libmagic_rs::parser::ast::Value;
102/// use libmagic_rs::evaluator::operators::apply_greater_equal;
103///
104/// assert!(apply_greater_equal(&Value::Uint(10), &Value::Uint(10)));
105/// assert!(apply_greater_equal(&Value::Uint(10), &Value::Uint(5)));
106/// assert!(!apply_greater_equal(&Value::Uint(5), &Value::Uint(10)));
107/// ```
108#[must_use]
109pub fn apply_greater_equal(left: &Value, right: &Value) -> bool {
110    matches!(
111        compare_values(left, right),
112        Some(Ordering::Greater | Ordering::Equal)
113    )
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn test_compare_values_ordering() {
122        use std::cmp::Ordering::*;
123
124        // Same-type integer comparisons
125        assert_eq!(
126            compare_values(&Value::Uint(5), &Value::Uint(10)),
127            Some(Less)
128        );
129        assert_eq!(
130            compare_values(&Value::Uint(10), &Value::Uint(10)),
131            Some(Equal)
132        );
133        assert_eq!(
134            compare_values(&Value::Uint(10), &Value::Uint(5)),
135            Some(Greater)
136        );
137        assert_eq!(
138            compare_values(&Value::Int(-10), &Value::Int(-5)),
139            Some(Less)
140        );
141        assert_eq!(
142            compare_values(&Value::Int(i64::MIN), &Value::Int(0)),
143            Some(Less)
144        );
145
146        // Cross-type integer comparisons via i128
147        assert_eq!(compare_values(&Value::Int(-1), &Value::Uint(0)), Some(Less));
148        assert_eq!(
149            compare_values(&Value::Uint(42), &Value::Int(42)),
150            Some(Equal)
151        );
152        assert_eq!(
153            compare_values(&Value::Uint(u64::MAX), &Value::Int(-1)),
154            Some(Greater)
155        );
156
157        // String comparisons
158        assert_eq!(
159            compare_values(&Value::String("abc".into()), &Value::String("abd".into())),
160            Some(Less)
161        );
162        assert_eq!(
163            compare_values(&Value::String("abc".into()), &Value::String("abc".into())),
164            Some(Equal)
165        );
166
167        // Bytes comparisons (lexicographic, including different lengths)
168        assert_eq!(
169            compare_values(&Value::Bytes(vec![1]), &Value::Bytes(vec![2])),
170            Some(Less)
171        );
172        assert_eq!(
173            compare_values(&Value::Bytes(vec![1]), &Value::Bytes(vec![1])),
174            Some(Equal)
175        );
176        assert_eq!(
177            compare_values(&Value::Bytes(vec![1]), &Value::Bytes(vec![1, 2])),
178            Some(Less)
179        );
180        assert_eq!(
181            compare_values(&Value::Bytes(vec![]), &Value::Bytes(vec![1])),
182            Some(Less)
183        );
184
185        // Incomparable types return None
186        assert_eq!(
187            compare_values(&Value::Uint(1), &Value::String("1".into())),
188            None
189        );
190        assert_eq!(compare_values(&Value::Int(1), &Value::Bytes(vec![1])), None);
191    }
192
193    #[test]
194    fn test_compare_values_float_ordering() {
195        use std::cmp::Ordering::*;
196
197        assert_eq!(
198            compare_values(&Value::Float(1.0), &Value::Float(2.0)),
199            Some(Less)
200        );
201        assert_eq!(
202            compare_values(&Value::Float(2.0), &Value::Float(2.0)),
203            Some(Equal)
204        );
205        assert_eq!(
206            compare_values(&Value::Float(3.0), &Value::Float(2.0)),
207            Some(Greater)
208        );
209        assert_eq!(
210            compare_values(&Value::Float(-1.0), &Value::Float(1.0)),
211            Some(Less)
212        );
213
214        // Infinity ordering
215        assert_eq!(
216            compare_values(&Value::Float(1.0), &Value::Float(f64::INFINITY)),
217            Some(Less)
218        );
219        assert_eq!(
220            compare_values(&Value::Float(f64::NEG_INFINITY), &Value::Float(1.0)),
221            Some(Less)
222        );
223        assert_eq!(
224            compare_values(&Value::Float(f64::INFINITY), &Value::Float(f64::INFINITY)),
225            Some(Equal)
226        );
227
228        // NaN is not comparable
229        assert_eq!(
230            compare_values(&Value::Float(f64::NAN), &Value::Float(1.0)),
231            None
232        );
233        assert_eq!(
234            compare_values(&Value::Float(1.0), &Value::Float(f64::NAN)),
235            None
236        );
237        assert_eq!(
238            compare_values(&Value::Float(f64::NAN), &Value::Float(f64::NAN)),
239            None
240        );
241
242        // Float vs non-float is incomparable
243        assert_eq!(compare_values(&Value::Float(1.0), &Value::Uint(1)), None);
244        assert_eq!(compare_values(&Value::Int(1), &Value::Float(1.0)), None);
245    }
246
247    #[test]
248    fn test_comparison_operators_float() {
249        // Direct partial_cmp semantics for ordering operators
250        assert!(apply_less_than(&Value::Float(1.0), &Value::Float(2.0)));
251        assert!(!apply_less_than(&Value::Float(2.0), &Value::Float(2.0)));
252        assert!(apply_greater_than(&Value::Float(3.0), &Value::Float(2.0)));
253        assert!(!apply_greater_than(&Value::Float(2.0), &Value::Float(2.0)));
254        assert!(apply_less_equal(&Value::Float(2.0), &Value::Float(2.0)));
255        assert!(apply_less_equal(&Value::Float(1.0), &Value::Float(2.0)));
256        assert!(apply_greater_equal(&Value::Float(2.0), &Value::Float(2.0)));
257        assert!(apply_greater_equal(&Value::Float(3.0), &Value::Float(2.0)));
258
259        // NaN comparisons all return false
260        assert!(!apply_less_than(
261            &Value::Float(f64::NAN),
262            &Value::Float(1.0)
263        ));
264        assert!(!apply_greater_than(
265            &Value::Float(f64::NAN),
266            &Value::Float(1.0)
267        ));
268        assert!(!apply_less_equal(
269            &Value::Float(f64::NAN),
270            &Value::Float(1.0)
271        ));
272        assert!(!apply_greater_equal(
273            &Value::Float(f64::NAN),
274            &Value::Float(1.0)
275        ));
276    }
277
278    #[test]
279    fn test_comparison_operators_consistency() {
280        // Verify all four comparison functions agree with compare_values
281        let pairs = vec![
282            (Value::Uint(5), Value::Uint(10)),
283            (Value::Uint(10), Value::Uint(10)),
284            (Value::Uint(10), Value::Uint(5)),
285            (Value::Int(-10), Value::Int(-5)),
286            (Value::Int(-1), Value::Uint(0)),
287            (Value::Uint(u64::MAX), Value::Int(-1)),
288            (Value::String("abc".into()), Value::String("abd".into())),
289            (Value::Bytes(vec![1, 2]), Value::Bytes(vec![1, 3])),
290            (Value::Bytes(vec![1]), Value::Bytes(vec![1, 2])),
291            (Value::Uint(1), Value::String("1".into())), // incomparable
292        ];
293
294        for (left, right) in &pairs {
295            let ord = compare_values(left, right);
296            assert_eq!(
297                apply_less_than(left, right),
298                ord == Some(Ordering::Less),
299                "< for {left:?}, {right:?}"
300            );
301            assert_eq!(
302                apply_greater_than(left, right),
303                ord == Some(Ordering::Greater),
304                "> for {left:?}, {right:?}"
305            );
306            assert_eq!(
307                apply_less_equal(left, right),
308                matches!(ord, Some(Ordering::Less | Ordering::Equal)),
309                "<= for {left:?}, {right:?}"
310            );
311            assert_eq!(
312                apply_greater_equal(left, right),
313                matches!(ord, Some(Ordering::Greater | Ordering::Equal)),
314                ">= for {left:?}, {right:?}"
315            );
316        }
317    }
318}