dcbor_pattern/pattern/value/
number_pattern.rs

1use std::ops::RangeInclusive;
2
3use dcbor::prelude::*;
4
5use crate::pattern::{Matcher, Path, Pattern, vm::Instr};
6
7/// Pattern for matching number values in dCBOR.
8#[derive(Debug, Clone)]
9pub enum NumberPattern {
10    /// Matches any number.
11    Any,
12    /// Matches the exact number.
13    Value(f64),
14    /// Matches numbers within a range, inclusive (..=).
15    Range(RangeInclusive<f64>),
16    /// Matches numbers that are greater than the specified value.
17    GreaterThan(f64),
18    /// Matches numbers that are greater than or equal to the specified value.
19    GreaterThanOrEqual(f64),
20    /// Matches numbers that are less than the specified value.
21    LessThan(f64),
22    /// Matches numbers that are less than or equal to the specified value.
23    LessThanOrEqual(f64),
24    /// Matches numbers that are NaN (Not a Number).
25    NaN,
26    /// Matches positive infinity.
27    Infinity,
28    /// Matches negative infinity.
29    NegInfinity,
30}
31
32impl std::hash::Hash for NumberPattern {
33    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
34        match self {
35            NumberPattern::Any => 0u8.hash(state),
36            NumberPattern::Value(value) => {
37                1u8.hash(state);
38                value.to_bits().hash(state);
39            }
40            NumberPattern::Range(range) => {
41                2u8.hash(state);
42                range.start().to_bits().hash(state);
43                range.end().to_bits().hash(state);
44            }
45            NumberPattern::GreaterThan(value) => {
46                3u8.hash(state);
47                value.to_bits().hash(state);
48            }
49            NumberPattern::GreaterThanOrEqual(value) => {
50                4u8.hash(state);
51                value.to_bits().hash(state);
52            }
53            NumberPattern::LessThan(value) => {
54                5u8.hash(state);
55                value.to_bits().hash(state);
56            }
57            NumberPattern::LessThanOrEqual(value) => {
58                6u8.hash(state);
59                value.to_bits().hash(state);
60            }
61            NumberPattern::NaN => 7u8.hash(state),
62            NumberPattern::Infinity => 8u8.hash(state),
63            NumberPattern::NegInfinity => 9u8.hash(state),
64        }
65    }
66}
67
68impl PartialEq for NumberPattern {
69    fn eq(&self, other: &Self) -> bool {
70        match (self, other) {
71            (NumberPattern::Any, NumberPattern::Any) => true,
72            (NumberPattern::Value(a), NumberPattern::Value(b)) => a == b,
73            (NumberPattern::Range(a), NumberPattern::Range(b)) => a == b,
74            (NumberPattern::GreaterThan(a), NumberPattern::GreaterThan(b)) => {
75                a == b
76            }
77            (
78                NumberPattern::GreaterThanOrEqual(a),
79                NumberPattern::GreaterThanOrEqual(b),
80            ) => a == b,
81            (NumberPattern::LessThan(a), NumberPattern::LessThan(b)) => a == b,
82            (
83                NumberPattern::LessThanOrEqual(a),
84                NumberPattern::LessThanOrEqual(b),
85            ) => a == b,
86            (NumberPattern::NaN, NumberPattern::NaN) => true,
87            (NumberPattern::Infinity, NumberPattern::Infinity) => true,
88            (NumberPattern::NegInfinity, NumberPattern::NegInfinity) => true,
89            _ => false,
90        }
91    }
92}
93
94impl Eq for NumberPattern {}
95
96impl NumberPattern {
97    /// Creates a new `NumberPattern` that matches any number.
98    pub fn any() -> Self { NumberPattern::Any }
99
100    /// Creates a new `NumberPattern` that matches the exact number.
101    pub fn value<T>(value: T) -> Self
102    where
103        T: Into<f64>,
104    {
105        NumberPattern::Value(value.into())
106    }
107
108    /// Creates a new `NumberPattern` that matches numbers within the specified
109    /// range.
110    pub fn range<A>(range: RangeInclusive<A>) -> Self
111    where
112        A: Into<f64> + Copy,
113    {
114        let start = (*range.start()).into();
115        let end = (*range.end()).into();
116        NumberPattern::Range(RangeInclusive::new(start, end))
117    }
118
119    /// Creates a new `NumberPattern` that matches numbers greater than the
120    /// specified value.
121    pub fn greater_than<T>(value: T) -> Self
122    where
123        T: Into<f64>,
124    {
125        NumberPattern::GreaterThan(value.into())
126    }
127
128    /// Creates a new `NumberPattern` that matches numbers greater than or
129    /// equal to the specified value.
130    pub fn greater_than_or_equal<T>(value: T) -> Self
131    where
132        T: Into<f64>,
133    {
134        NumberPattern::GreaterThanOrEqual(value.into())
135    }
136
137    /// Creates a new `NumberPattern` that matches numbers less than the
138    /// specified value.
139    pub fn less_than<T>(value: T) -> Self
140    where
141        T: Into<f64>,
142    {
143        NumberPattern::LessThan(value.into())
144    }
145
146    /// Creates a new `NumberPattern` that matches numbers less than or equal
147    /// to the specified value.
148    pub fn less_than_or_equal<T>(value: T) -> Self
149    where
150        T: Into<f64>,
151    {
152        NumberPattern::LessThanOrEqual(value.into())
153    }
154
155    /// Creates a new `NumberPattern` that matches NaN values.
156    pub fn nan() -> Self { NumberPattern::NaN }
157
158    /// Creates a new `NumberPattern` that matches positive infinity.
159    pub fn infinity() -> Self { NumberPattern::Infinity }
160
161    /// Creates a new `NumberPattern` that matches negative infinity.
162    pub fn neg_infinity() -> Self { NumberPattern::NegInfinity }
163}
164
165impl Matcher for NumberPattern {
166    fn paths(&self, haystack: &CBOR) -> Vec<Path> {
167        let is_hit = match self {
168            NumberPattern::Any => haystack.is_number(),
169            NumberPattern::Value(want) => {
170                if let Ok(value) = f64::try_from_cbor(haystack) {
171                    value == *want
172                } else {
173                    false
174                }
175            }
176            NumberPattern::Range(want) => {
177                if let Ok(value) = f64::try_from_cbor(haystack) {
178                    want.contains(&value)
179                } else {
180                    false
181                }
182            }
183            NumberPattern::GreaterThan(want) => {
184                if let Ok(value) = f64::try_from_cbor(haystack) {
185                    value > *want
186                } else {
187                    false
188                }
189            }
190            NumberPattern::GreaterThanOrEqual(want) => {
191                if let Ok(value) = f64::try_from_cbor(haystack) {
192                    value >= *want
193                } else {
194                    false
195                }
196            }
197            NumberPattern::LessThan(want) => {
198                if let Ok(value) = f64::try_from_cbor(haystack) {
199                    value < *want
200                } else {
201                    false
202                }
203            }
204            NumberPattern::LessThanOrEqual(want) => {
205                if let Ok(value) = f64::try_from_cbor(haystack) {
206                    value <= *want
207                } else {
208                    false
209                }
210            }
211            NumberPattern::NaN => {
212                if let Ok(value) = f64::try_from_cbor(haystack) {
213                    value.is_nan()
214                } else {
215                    false
216                }
217            }
218            NumberPattern::Infinity => {
219                if let Ok(value) = f64::try_from_cbor(haystack) {
220                    value.is_infinite() && value.is_sign_positive()
221                } else {
222                    false
223                }
224            }
225            NumberPattern::NegInfinity => {
226                if let Ok(value) = f64::try_from_cbor(haystack) {
227                    value.is_infinite() && value.is_sign_negative()
228                } else {
229                    false
230                }
231            }
232        };
233
234        if is_hit {
235            vec![vec![haystack.clone()]]
236        } else {
237            vec![]
238        }
239    }
240
241    fn compile(
242        &self,
243        code: &mut Vec<Instr>,
244        literals: &mut Vec<Pattern>,
245        _captures: &mut Vec<String>,
246    ) {
247        let idx = literals.len();
248        literals.push(Pattern::Value(crate::pattern::ValuePattern::Number(
249            self.clone(),
250        )));
251        code.push(Instr::MatchPredicate(idx));
252    }
253}
254
255impl std::fmt::Display for NumberPattern {
256    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
257        match self {
258            NumberPattern::Any => write!(f, "number"),
259            NumberPattern::Value(value) => write!(f, "{}", value),
260            NumberPattern::Range(range) => {
261                write!(f, "{}...{}", range.start(), range.end())
262            }
263            NumberPattern::GreaterThan(value) => {
264                write!(f, ">{}", value)
265            }
266            NumberPattern::GreaterThanOrEqual(value) => {
267                write!(f, ">={}", value)
268            }
269            NumberPattern::LessThan(value) => write!(f, "<{}", value),
270            NumberPattern::LessThanOrEqual(value) => {
271                write!(f, "<={}", value)
272            }
273            NumberPattern::NaN => write!(f, "NaN"),
274            NumberPattern::Infinity => write!(f, "Infinity"),
275            NumberPattern::NegInfinity => write!(f, "-Infinity"),
276        }
277    }
278}
279
280#[cfg(test)]
281mod tests {
282    use super::*;
283
284    #[test]
285    fn test_number_pattern_display() {
286        assert_eq!(NumberPattern::any().to_string(), "number");
287        assert_eq!(NumberPattern::value(42.0).to_string(), "42");
288        assert_eq!(NumberPattern::range(1.0..=10.0).to_string(), "1...10");
289        assert_eq!(NumberPattern::greater_than(5.0).to_string(), ">5");
290        assert_eq!(
291            NumberPattern::greater_than_or_equal(5.0).to_string(),
292            ">=5"
293        );
294        assert_eq!(NumberPattern::less_than(5.0).to_string(), "<5");
295        assert_eq!(NumberPattern::less_than_or_equal(5.0).to_string(), "<=5");
296        assert_eq!(NumberPattern::nan().to_string(), "NaN");
297        assert_eq!(NumberPattern::infinity().to_string(), "Infinity");
298        assert_eq!(NumberPattern::neg_infinity().to_string(), "-Infinity");
299    }
300
301    #[test]
302    fn test_number_pattern_matching() {
303        let int_cbor = 42.to_cbor();
304        let float_cbor = 3.2222.to_cbor();
305        let negative_cbor = (-10).to_cbor();
306        let nan_cbor = f64::NAN.to_cbor();
307        let text_cbor = "not a number".to_cbor();
308
309        // Test Any pattern
310        let any_pattern = NumberPattern::any();
311        assert!(any_pattern.matches(&int_cbor));
312        assert!(any_pattern.matches(&float_cbor));
313        assert!(any_pattern.matches(&negative_cbor));
314        assert!(any_pattern.matches(&nan_cbor));
315        assert!(!any_pattern.matches(&text_cbor));
316
317        // Test exact patterns
318        let exact_pattern = NumberPattern::value(42.0);
319        assert!(exact_pattern.matches(&int_cbor));
320        assert!(!exact_pattern.matches(&float_cbor));
321        assert!(!exact_pattern.matches(&text_cbor));
322
323        // Test range pattern
324        let range_pattern = NumberPattern::range(1.0..=50.0);
325        assert!(range_pattern.matches(&int_cbor));
326        assert!(range_pattern.matches(&float_cbor));
327        assert!(!range_pattern.matches(&negative_cbor));
328        assert!(!range_pattern.matches(&text_cbor));
329
330        // Test comparison patterns
331        let gt_pattern = NumberPattern::greater_than(10.0);
332        assert!(gt_pattern.matches(&int_cbor));
333        assert!(!gt_pattern.matches(&float_cbor));
334        assert!(!gt_pattern.matches(&negative_cbor));
335
336        let lt_pattern = NumberPattern::less_than(50.0);
337        assert!(lt_pattern.matches(&int_cbor));
338        assert!(lt_pattern.matches(&float_cbor));
339        assert!(lt_pattern.matches(&negative_cbor));
340
341        // Test NaN pattern
342        let nan_pattern = NumberPattern::nan();
343        assert!(!nan_pattern.matches(&int_cbor));
344        assert!(!nan_pattern.matches(&float_cbor));
345        assert!(nan_pattern.matches(&nan_cbor));
346        assert!(!nan_pattern.matches(&text_cbor));
347    }
348
349    #[test]
350    fn test_number_pattern_paths() {
351        let int_cbor = 42.to_cbor();
352        let text_cbor = "not a number".to_cbor();
353
354        let any_pattern = NumberPattern::any();
355        let int_paths = any_pattern.paths(&int_cbor);
356        assert_eq!(int_paths.len(), 1);
357        assert_eq!(int_paths[0].len(), 1);
358        assert_eq!(int_paths[0][0], int_cbor);
359
360        let text_paths = any_pattern.paths(&text_cbor);
361        assert_eq!(text_paths.len(), 0);
362
363        let exact_pattern = NumberPattern::value(42.0);
364        let paths = exact_pattern.paths(&int_cbor);
365        assert_eq!(paths.len(), 1);
366        assert_eq!(paths[0].len(), 1);
367        assert_eq!(paths[0][0], int_cbor);
368
369        let no_match_paths = exact_pattern.paths(&text_cbor);
370        assert_eq!(no_match_paths.len(), 0);
371    }
372
373    #[test]
374    fn test_number_conversion() {
375        let int_cbor = 42.to_cbor();
376        let float_cbor = 3.2222.to_cbor();
377        let negative_cbor = (-10).to_cbor();
378        let text_cbor = "not a number".to_cbor();
379
380        // Test direct conversion using try_from_cbor
381        assert_eq!(f64::try_from_cbor(&int_cbor).ok(), Some(42.0));
382        assert_eq!(f64::try_from_cbor(&float_cbor).ok(), Some(3.2222));
383        assert_eq!(f64::try_from_cbor(&negative_cbor).ok(), Some(-10.0));
384        assert_eq!(f64::try_from_cbor(&text_cbor).ok(), None);
385    }
386
387    #[test]
388    fn test_infinity_patterns() {
389        let inf_cbor = f64::INFINITY.to_cbor();
390        let neg_inf_cbor = f64::NEG_INFINITY.to_cbor();
391        let nan_cbor = f64::NAN.to_cbor();
392        let regular_cbor = 42.0.to_cbor();
393        let text_cbor = "not a number".to_cbor();
394
395        // Test positive infinity pattern
396        let inf_pattern = NumberPattern::infinity();
397        assert!(inf_pattern.matches(&inf_cbor));
398        assert!(!inf_pattern.matches(&neg_inf_cbor));
399        assert!(!inf_pattern.matches(&nan_cbor));
400        assert!(!inf_pattern.matches(&regular_cbor));
401        assert!(!inf_pattern.matches(&text_cbor));
402
403        // Test negative infinity pattern
404        let neg_inf_pattern = NumberPattern::neg_infinity();
405        assert!(!neg_inf_pattern.matches(&inf_cbor));
406        assert!(neg_inf_pattern.matches(&neg_inf_cbor));
407        assert!(!neg_inf_pattern.matches(&nan_cbor));
408        assert!(!neg_inf_pattern.matches(&regular_cbor));
409        assert!(!neg_inf_pattern.matches(&text_cbor));
410
411        // Test that any pattern matches all number types including infinities
412        let any_pattern = NumberPattern::any();
413        assert!(any_pattern.matches(&inf_cbor));
414        assert!(any_pattern.matches(&neg_inf_cbor));
415        assert!(any_pattern.matches(&nan_cbor));
416        assert!(any_pattern.matches(&regular_cbor));
417        assert!(!any_pattern.matches(&text_cbor));
418
419        // Test display formatting
420        assert_eq!(inf_pattern.to_string(), "Infinity");
421        assert_eq!(neg_inf_pattern.to_string(), "-Infinity");
422    }
423}