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 {
99        NumberPattern::Any
100    }
101
102    /// Creates a new `NumberPattern` that matches the exact number.
103    pub fn value<T>(value: T) -> Self
104    where
105        T: Into<f64>,
106    {
107        NumberPattern::Value(value.into())
108    }
109
110    /// Creates a new `NumberPattern` that matches numbers within the specified
111    /// range.
112    pub fn range<A>(range: RangeInclusive<A>) -> Self
113    where
114        A: Into<f64> + Copy,
115    {
116        let start = (*range.start()).into();
117        let end = (*range.end()).into();
118        NumberPattern::Range(RangeInclusive::new(start, end))
119    }
120
121    /// Creates a new `NumberPattern` that matches numbers greater than the
122    /// specified value.
123    pub fn greater_than<T>(value: T) -> Self
124    where
125        T: Into<f64>,
126    {
127        NumberPattern::GreaterThan(value.into())
128    }
129
130    /// Creates a new `NumberPattern` that matches numbers greater than or
131    /// equal to the specified value.
132    pub fn greater_than_or_equal<T>(value: T) -> Self
133    where
134        T: Into<f64>,
135    {
136        NumberPattern::GreaterThanOrEqual(value.into())
137    }
138
139    /// Creates a new `NumberPattern` that matches numbers less than the
140    /// specified value.
141    pub fn less_than<T>(value: T) -> Self
142    where
143        T: Into<f64>,
144    {
145        NumberPattern::LessThan(value.into())
146    }
147
148    /// Creates a new `NumberPattern` that matches numbers less than or equal
149    /// to the specified value.
150    pub fn less_than_or_equal<T>(value: T) -> Self
151    where
152        T: Into<f64>,
153    {
154        NumberPattern::LessThanOrEqual(value.into())
155    }
156
157    /// Creates a new `NumberPattern` that matches NaN values.
158    pub fn nan() -> Self {
159        NumberPattern::NaN
160    }
161
162    /// Creates a new `NumberPattern` that matches positive infinity.
163    pub fn infinity() -> Self {
164        NumberPattern::Infinity
165    }
166
167    /// Creates a new `NumberPattern` that matches negative infinity.
168    pub fn neg_infinity() -> Self {
169        NumberPattern::NegInfinity
170    }
171}
172
173impl Matcher for NumberPattern {
174    fn paths(&self, haystack: &CBOR) -> Vec<Path> {
175        let is_hit = match self {
176            NumberPattern::Any => haystack.is_number(),
177            NumberPattern::Value(want) => {
178                if let Ok(value) = f64::try_from_cbor(haystack) {
179                    value == *want
180                } else {
181                    false
182                }
183            }
184            NumberPattern::Range(want) => {
185                if let Ok(value) = f64::try_from_cbor(haystack) {
186                    want.contains(&value)
187                } else {
188                    false
189                }
190            }
191            NumberPattern::GreaterThan(want) => {
192                if let Ok(value) = f64::try_from_cbor(haystack) {
193                    value > *want
194                } else {
195                    false
196                }
197            }
198            NumberPattern::GreaterThanOrEqual(want) => {
199                if let Ok(value) = f64::try_from_cbor(haystack) {
200                    value >= *want
201                } else {
202                    false
203                }
204            }
205            NumberPattern::LessThan(want) => {
206                if let Ok(value) = f64::try_from_cbor(haystack) {
207                    value < *want
208                } else {
209                    false
210                }
211            }
212            NumberPattern::LessThanOrEqual(want) => {
213                if let Ok(value) = f64::try_from_cbor(haystack) {
214                    value <= *want
215                } else {
216                    false
217                }
218            }
219            NumberPattern::NaN => {
220                if let Ok(value) = f64::try_from_cbor(haystack) {
221                    value.is_nan()
222                } else {
223                    false
224                }
225            }
226            NumberPattern::Infinity => {
227                if let Ok(value) = f64::try_from_cbor(haystack) {
228                    value.is_infinite() && value.is_sign_positive()
229                } else {
230                    false
231                }
232            }
233            NumberPattern::NegInfinity => {
234                if let Ok(value) = f64::try_from_cbor(haystack) {
235                    value.is_infinite() && value.is_sign_negative()
236                } else {
237                    false
238                }
239            }
240        };
241
242        if is_hit {
243            vec![vec![haystack.clone()]]
244        } else {
245            vec![]
246        }
247    }
248
249    fn compile(
250        &self,
251        code: &mut Vec<Instr>,
252        literals: &mut Vec<Pattern>,
253        _captures: &mut Vec<String>,
254    ) {
255        let idx = literals.len();
256        literals.push(Pattern::Value(crate::pattern::ValuePattern::Number(
257            self.clone(),
258        )));
259        code.push(Instr::MatchPredicate(idx));
260    }
261}
262
263impl std::fmt::Display for NumberPattern {
264    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
265        match self {
266            NumberPattern::Any => write!(f, "number"),
267            NumberPattern::Value(value) => write!(f, "{}", value),
268            NumberPattern::Range(range) => {
269                write!(f, "{}...{}", range.start(), range.end())
270            }
271            NumberPattern::GreaterThan(value) => {
272                write!(f, ">{}", value)
273            }
274            NumberPattern::GreaterThanOrEqual(value) => {
275                write!(f, ">={}", value)
276            }
277            NumberPattern::LessThan(value) => write!(f, "<{}", value),
278            NumberPattern::LessThanOrEqual(value) => {
279                write!(f, "<={}", value)
280            }
281            NumberPattern::NaN => write!(f, "NaN"),
282            NumberPattern::Infinity => write!(f, "Infinity"),
283            NumberPattern::NegInfinity => write!(f, "-Infinity"),
284        }
285    }
286}
287
288#[cfg(test)]
289mod tests {
290    use super::*;
291
292    #[test]
293    fn test_number_pattern_display() {
294        assert_eq!(NumberPattern::any().to_string(), "number");
295        assert_eq!(NumberPattern::value(42.0).to_string(), "42");
296        assert_eq!(NumberPattern::range(1.0..=10.0).to_string(), "1...10");
297        assert_eq!(NumberPattern::greater_than(5.0).to_string(), ">5");
298        assert_eq!(
299            NumberPattern::greater_than_or_equal(5.0).to_string(),
300            ">=5"
301        );
302        assert_eq!(NumberPattern::less_than(5.0).to_string(), "<5");
303        assert_eq!(NumberPattern::less_than_or_equal(5.0).to_string(), "<=5");
304        assert_eq!(NumberPattern::nan().to_string(), "NaN");
305        assert_eq!(NumberPattern::infinity().to_string(), "Infinity");
306        assert_eq!(NumberPattern::neg_infinity().to_string(), "-Infinity");
307    }
308
309    #[test]
310    fn test_number_pattern_matching() {
311        let int_cbor = 42.to_cbor();
312        let float_cbor = 3.2222.to_cbor();
313        let negative_cbor = (-10).to_cbor();
314        let nan_cbor = f64::NAN.to_cbor();
315        let text_cbor = "not a number".to_cbor();
316
317        // Test Any pattern
318        let any_pattern = NumberPattern::any();
319        assert!(any_pattern.matches(&int_cbor));
320        assert!(any_pattern.matches(&float_cbor));
321        assert!(any_pattern.matches(&negative_cbor));
322        assert!(any_pattern.matches(&nan_cbor));
323        assert!(!any_pattern.matches(&text_cbor));
324
325        // Test exact patterns
326        let exact_pattern = NumberPattern::value(42.0);
327        assert!(exact_pattern.matches(&int_cbor));
328        assert!(!exact_pattern.matches(&float_cbor));
329        assert!(!exact_pattern.matches(&text_cbor));
330
331        // Test range pattern
332        let range_pattern = NumberPattern::range(1.0..=50.0);
333        assert!(range_pattern.matches(&int_cbor));
334        assert!(range_pattern.matches(&float_cbor));
335        assert!(!range_pattern.matches(&negative_cbor));
336        assert!(!range_pattern.matches(&text_cbor));
337
338        // Test comparison patterns
339        let gt_pattern = NumberPattern::greater_than(10.0);
340        assert!(gt_pattern.matches(&int_cbor));
341        assert!(!gt_pattern.matches(&float_cbor));
342        assert!(!gt_pattern.matches(&negative_cbor));
343
344        let lt_pattern = NumberPattern::less_than(50.0);
345        assert!(lt_pattern.matches(&int_cbor));
346        assert!(lt_pattern.matches(&float_cbor));
347        assert!(lt_pattern.matches(&negative_cbor));
348
349        // Test NaN pattern
350        let nan_pattern = NumberPattern::nan();
351        assert!(!nan_pattern.matches(&int_cbor));
352        assert!(!nan_pattern.matches(&float_cbor));
353        assert!(nan_pattern.matches(&nan_cbor));
354        assert!(!nan_pattern.matches(&text_cbor));
355    }
356
357    #[test]
358    fn test_number_pattern_paths() {
359        let int_cbor = 42.to_cbor();
360        let text_cbor = "not a number".to_cbor();
361
362        let any_pattern = NumberPattern::any();
363        let int_paths = any_pattern.paths(&int_cbor);
364        assert_eq!(int_paths.len(), 1);
365        assert_eq!(int_paths[0].len(), 1);
366        assert_eq!(int_paths[0][0], int_cbor);
367
368        let text_paths = any_pattern.paths(&text_cbor);
369        assert_eq!(text_paths.len(), 0);
370
371        let exact_pattern = NumberPattern::value(42.0);
372        let paths = exact_pattern.paths(&int_cbor);
373        assert_eq!(paths.len(), 1);
374        assert_eq!(paths[0].len(), 1);
375        assert_eq!(paths[0][0], int_cbor);
376
377        let no_match_paths = exact_pattern.paths(&text_cbor);
378        assert_eq!(no_match_paths.len(), 0);
379    }
380
381    #[test]
382    fn test_number_conversion() {
383        let int_cbor = 42.to_cbor();
384        let float_cbor = 3.2222.to_cbor();
385        let negative_cbor = (-10).to_cbor();
386        let text_cbor = "not a number".to_cbor();
387
388        // Test direct conversion using try_from_cbor
389        assert_eq!(f64::try_from_cbor(&int_cbor).ok(), Some(42.0));
390        assert_eq!(f64::try_from_cbor(&float_cbor).ok(), Some(3.2222));
391        assert_eq!(f64::try_from_cbor(&negative_cbor).ok(), Some(-10.0));
392        assert_eq!(f64::try_from_cbor(&text_cbor).ok(), None);
393    }
394
395    #[test]
396    fn test_infinity_patterns() {
397        let inf_cbor = f64::INFINITY.to_cbor();
398        let neg_inf_cbor = f64::NEG_INFINITY.to_cbor();
399        let nan_cbor = f64::NAN.to_cbor();
400        let regular_cbor = 42.0.to_cbor();
401        let text_cbor = "not a number".to_cbor();
402
403        // Test positive infinity pattern
404        let inf_pattern = NumberPattern::infinity();
405        assert!(inf_pattern.matches(&inf_cbor));
406        assert!(!inf_pattern.matches(&neg_inf_cbor));
407        assert!(!inf_pattern.matches(&nan_cbor));
408        assert!(!inf_pattern.matches(&regular_cbor));
409        assert!(!inf_pattern.matches(&text_cbor));
410
411        // Test negative infinity pattern
412        let neg_inf_pattern = NumberPattern::neg_infinity();
413        assert!(!neg_inf_pattern.matches(&inf_cbor));
414        assert!(neg_inf_pattern.matches(&neg_inf_cbor));
415        assert!(!neg_inf_pattern.matches(&nan_cbor));
416        assert!(!neg_inf_pattern.matches(&regular_cbor));
417        assert!(!neg_inf_pattern.matches(&text_cbor));
418
419        // Test that any pattern matches all number types including infinities
420        let any_pattern = NumberPattern::any();
421        assert!(any_pattern.matches(&inf_cbor));
422        assert!(any_pattern.matches(&neg_inf_cbor));
423        assert!(any_pattern.matches(&nan_cbor));
424        assert!(any_pattern.matches(&regular_cbor));
425        assert!(!any_pattern.matches(&text_cbor));
426
427        // Test display formatting
428        assert_eq!(inf_pattern.to_string(), "Infinity");
429        assert_eq!(neg_inf_pattern.to_string(), "-Infinity");
430    }
431}