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            (
39                vec![vec![haystack.clone()]],
40                std::collections::HashMap::new(),
41            )
42        } else {
43            // Inner pattern matches, so NOT doesn't match
44            (vec![], std::collections::HashMap::new())
45        }
46    }
47
48    /// Compile into byte-code (NOT = negation of the inner pattern).
49    fn compile(
50        &self,
51        code: &mut Vec<Instr>,
52        literals: &mut Vec<Pattern>,
53        _captures: &mut Vec<String>,
54    ) {
55        // NOT = check that pattern doesn't match
56        let idx = literals.len();
57        literals.push(self.pattern().clone());
58        code.push(Instr::NotMatch { pat_idx: idx });
59    }
60
61    fn collect_capture_names(&self, names: &mut Vec<String>) {
62        // NOT patterns do not expose their inner pattern's captures
63        // since the semantics would be unclear - what does it mean to
64        // capture from a pattern that must NOT match?
65        // So we do nothing here.
66        let _ = names; // Suppress unused warning
67    }
68
69    fn is_complex(&self) -> bool {
70        // NOT patterns are always considered complex for display purposes
71        true
72    }
73}
74
75impl std::fmt::Display for NotPattern {
76    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77        if self.pattern().is_complex() {
78            write!(f, "!({})", self.pattern())
79        } else {
80            write!(f, "!{}", self.pattern())
81        }
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    #[test]
90    fn test_not_pattern_display() {
91        let not_pattern = NotPattern::new(Pattern::number(5));
92        assert_eq!(not_pattern.to_string(), "!5");
93    }
94
95    #[test]
96    fn test_not_pattern_display_complex() {
97        let and_pattern =
98            Pattern::Meta(crate::pattern::meta::MetaPattern::And(
99                crate::pattern::meta::AndPattern::new(vec![
100                    Pattern::number(5),
101                    Pattern::text("hello"),
102                ]),
103            ));
104        let not_pattern = NotPattern::new(and_pattern);
105        assert_eq!(not_pattern.to_string(), r#"!(5 & "hello")"#);
106    }
107
108    #[test]
109    fn test_not_pattern_matches_when_inner_fails() {
110        let pattern = NotPattern::new(Pattern::number(5));
111
112        let cbor_42 = CBOR::from(42); // Not 5, so NOT pattern should match
113        assert!(pattern.matches(&cbor_42));
114
115        let cbor_text = CBOR::from("hello"); // Not a number, so NOT pattern should match
116        assert!(pattern.matches(&cbor_text));
117    }
118
119    #[test]
120    fn test_not_pattern_fails_when_inner_matches() {
121        let pattern = NotPattern::new(Pattern::number(5));
122
123        let cbor_5 = CBOR::from(5); // Exactly 5, so NOT pattern should fail
124        assert!(!pattern.matches(&cbor_5));
125    }
126}