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 {
99 NumberPattern::Any
100 }
101
102 pub fn value<T>(value: T) -> Self
104 where
105 T: Into<f64>,
106 {
107 NumberPattern::Value(value.into())
108 }
109
110 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 pub fn greater_than<T>(value: T) -> Self
124 where
125 T: Into<f64>,
126 {
127 NumberPattern::GreaterThan(value.into())
128 }
129
130 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 pub fn less_than<T>(value: T) -> Self
142 where
143 T: Into<f64>,
144 {
145 NumberPattern::LessThan(value.into())
146 }
147
148 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 pub fn nan() -> Self {
159 NumberPattern::NaN
160 }
161
162 pub fn infinity() -> Self {
164 NumberPattern::Infinity
165 }
166
167 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 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 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 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 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 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 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 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(®ular_cbor));
409 assert!(!inf_pattern.matches(&text_cbor));
410
411 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(®ular_cbor));
417 assert!(!neg_inf_pattern.matches(&text_cbor));
418
419 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(®ular_cbor));
425 assert!(!any_pattern.matches(&text_cbor));
426
427 assert_eq!(inf_pattern.to_string(), "Infinity");
429 assert_eq!(neg_inf_pattern.to_string(), "-Infinity");
430 }
431}