dcbor_pattern/pattern/value/
number_pattern.rs1use std::ops::RangeInclusive;
2
3use dcbor::prelude::*;
4
5use crate::pattern::{Matcher, Path, Pattern, vm::Instr};
6
7#[derive(Debug, Clone)]
9pub enum NumberPattern {
10 Any,
12 Value(f64),
14 Range(RangeInclusive<f64>),
16 GreaterThan(f64),
18 GreaterThanOrEqual(f64),
20 LessThan(f64),
22 LessThanOrEqual(f64),
24 NaN,
26 Infinity,
28 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 pub fn any() -> Self { NumberPattern::Any }
99
100 pub fn value<T>(value: T) -> Self
102 where
103 T: Into<f64>,
104 {
105 NumberPattern::Value(value.into())
106 }
107
108 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 pub fn greater_than<T>(value: T) -> Self
122 where
123 T: Into<f64>,
124 {
125 NumberPattern::GreaterThan(value.into())
126 }
127
128 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 pub fn less_than<T>(value: T) -> Self
140 where
141 T: Into<f64>,
142 {
143 NumberPattern::LessThan(value.into())
144 }
145
146 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 pub fn nan() -> Self { NumberPattern::NaN }
157
158 pub fn infinity() -> Self { NumberPattern::Infinity }
160
161 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 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 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 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 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 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 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 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(®ular_cbor));
401 assert!(!inf_pattern.matches(&text_cbor));
402
403 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(®ular_cbor));
409 assert!(!neg_inf_pattern.matches(&text_cbor));
410
411 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(®ular_cbor));
417 assert!(!any_pattern.matches(&text_cbor));
418
419 assert_eq!(inf_pattern.to_string(), "Infinity");
421 assert_eq!(neg_inf_pattern.to_string(), "-Infinity");
422 }
423}