dcbor_pattern/pattern/meta/
not_pattern.rs

1use dcbor::prelude::*;
2
3use crate::pattern::{Matcher, Path, Pattern, vm::Instr};
4
5/// A pattern that negates another pattern; matches when the inner pattern does
6/// not match.
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct NotPattern(Box<Pattern>);
9
10impl NotPattern {
11    /// Creates a new `NotPattern` with the given pattern.
12    pub fn new(pattern: Pattern) -> Self { NotPattern(Box::new(pattern)) }
13
14    /// Returns the pattern being negated.
15    pub fn pattern(&self) -> &Pattern { &self.0 }
16}
17
18impl Matcher for NotPattern {
19    fn paths(&self, haystack: &CBOR) -> Vec<Path> {
20        // If the inner pattern doesn't match, then we return the current
21        // CBOR value as a match
22        if !self.pattern().matches(haystack) {
23            vec![vec![haystack.clone()]]
24        } else {
25            vec![]
26        }
27    }
28
29    fn paths_with_captures(
30        &self,
31        haystack: &CBOR,
32    ) -> (Vec<Path>, std::collections::HashMap<String, Vec<Path>>) {
33        // For NOT patterns, we match if the inner pattern does NOT match
34        let (inner_paths, _inner_captures) =
35            self.pattern().paths_with_captures(haystack);
36        if inner_paths.is_empty() {
37            // Inner pattern doesn't match, so NOT matches
38            (vec![vec![haystack.clone()]], std::collections::HashMap::new())
39        } else {
40            // Inner pattern matches, so NOT doesn't match
41            (vec![], std::collections::HashMap::new())
42        }
43    }
44
45    /// Compile into byte-code (NOT = negation of the inner pattern).
46    fn compile(
47        &self,
48        code: &mut Vec<Instr>,
49        literals: &mut Vec<Pattern>,
50        _captures: &mut Vec<String>,
51    ) {
52        // NOT = check that pattern doesn't match
53        let idx = literals.len();
54        literals.push(self.pattern().clone());
55        code.push(Instr::NotMatch { pat_idx: idx });
56    }
57
58    fn collect_capture_names(&self, names: &mut Vec<String>) {
59        // NOT patterns do not expose their inner pattern's captures
60        // since the semantics would be unclear - what does it mean to
61        // capture from a pattern that must NOT match?
62        // So we do nothing here.
63        let _ = names; // Suppress unused warning
64    }
65
66    fn is_complex(&self) -> bool {
67        // NOT patterns are always considered complex for display purposes
68        true
69    }
70}
71
72impl std::fmt::Display for NotPattern {
73    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74        if self.pattern().is_complex() {
75            write!(f, "!({})", self.pattern())
76        } else {
77            write!(f, "!{}", self.pattern())
78        }
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    #[test]
87    fn test_not_pattern_display() {
88        let not_pattern = NotPattern::new(Pattern::number(5));
89        assert_eq!(not_pattern.to_string(), "!5");
90    }
91
92    #[test]
93    fn test_not_pattern_display_complex() {
94        let and_pattern =
95            Pattern::Meta(crate::pattern::meta::MetaPattern::And(
96                crate::pattern::meta::AndPattern::new(vec![
97                    Pattern::number(5),
98                    Pattern::text("hello"),
99                ]),
100            ));
101        let not_pattern = NotPattern::new(and_pattern);
102        assert_eq!(not_pattern.to_string(), r#"!(5 & "hello")"#);
103    }
104
105    #[test]
106    fn test_not_pattern_matches_when_inner_fails() {
107        let pattern = NotPattern::new(Pattern::number(5));
108
109        let cbor_42 = CBOR::from(42); // Not 5, so NOT pattern should match
110        assert!(pattern.matches(&cbor_42));
111
112        let cbor_text = CBOR::from("hello"); // Not a number, so NOT pattern should match
113        assert!(pattern.matches(&cbor_text));
114    }
115
116    #[test]
117    fn test_not_pattern_fails_when_inner_matches() {
118        let pattern = NotPattern::new(Pattern::number(5));
119
120        let cbor_5 = CBOR::from(5); // Exactly 5, so NOT pattern should fail
121        assert!(!pattern.matches(&cbor_5));
122    }
123}