dcbor_pattern/pattern/meta/
search_pattern.rs

1use dcbor::prelude::*;
2
3use crate::pattern::{Matcher, Path, Pattern, vm::Instr};
4
5/// A pattern that searches the entire dCBOR tree for matches.
6///
7/// This pattern recursively traverses the dCBOR tree and applies the inner
8/// pattern at each node, returning all matching paths.
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct SearchPattern(Box<Pattern>);
11
12impl SearchPattern {
13    /// Creates a new `SearchPattern` that searches for the given pattern.
14    pub fn new(pattern: Pattern) -> Self { SearchPattern(Box::new(pattern)) }
15
16    /// Returns a reference to the inner pattern.
17    pub fn pattern(&self) -> &Pattern { &self.0 }
18
19    // Helper method to recursively search through CBOR tree
20    fn search_recursive(
21        &self,
22        cbor: &CBOR,
23        path: Vec<CBOR>,
24        results: &mut Vec<Path>,
25    ) {
26        // Test the pattern against this node
27        let pattern_paths = self.0.paths(cbor);
28
29        // If the pattern matches, add the current path to results
30        if !pattern_paths.is_empty() {
31            results.push(path.clone());
32        }
33
34        // Recursively search children based on CBOR type
35        match cbor.as_case() {
36            CBORCase::Array(arr) => {
37                for child in arr.iter() {
38                    let mut new_path = path.clone();
39                    new_path.push(child.clone());
40                    self.search_recursive(child, new_path, results);
41                }
42            }
43            CBORCase::Map(map) => {
44                for (key, value) in map.iter() {
45                    // Search both keys and values
46                    let mut key_path = path.clone();
47                    key_path.push(key.clone());
48                    self.search_recursive(key, key_path, results);
49
50                    let mut value_path = path.clone();
51                    value_path.push(value.clone());
52                    self.search_recursive(value, value_path, results);
53                }
54            }
55            CBORCase::Tagged(_, content) => {
56                let mut new_path = path.clone();
57                new_path.push(content.clone());
58                self.search_recursive(content, new_path, results);
59            }
60            _ => {
61                // Leaf nodes (primitives) - no children to search
62            }
63        }
64    }
65
66    // Helper method to recursively search through CBOR tree with capture
67    // support
68    fn search_recursive_with_captures(
69        &self,
70        cbor: &CBOR,
71        path: Vec<CBOR>,
72        results: &mut Vec<Path>,
73        all_captures: &mut std::collections::HashMap<String, Vec<Path>>,
74    ) {
75        // Test the pattern against this node with captures
76        let (pattern_paths, captures) = self.0.paths_with_captures(cbor);
77
78        // If the pattern matches, add the current path to results and handle
79        // captures
80        if !pattern_paths.is_empty() {
81            results.push(path.clone());
82
83            // For search patterns, the captured paths should be based on the
84            // current search location, not the inner pattern's
85            // paths
86            for (name, _capture_paths) in captures {
87                // The capture should be the path to the location where the
88                // match occurred
89                all_captures.entry(name).or_default().push(path.clone());
90            }
91        }
92
93        // Recursively search children based on CBOR type
94        match cbor.as_case() {
95            CBORCase::Array(arr) => {
96                for child in arr.iter() {
97                    let mut new_path = path.clone();
98                    new_path.push(child.clone());
99                    self.search_recursive_with_captures(
100                        child,
101                        new_path,
102                        results,
103                        all_captures,
104                    );
105                }
106            }
107            CBORCase::Map(map) => {
108                for (key, value) in map.iter() {
109                    // Search both keys and values
110                    let mut key_path = path.clone();
111                    key_path.push(key.clone());
112                    self.search_recursive_with_captures(
113                        key,
114                        key_path,
115                        results,
116                        all_captures,
117                    );
118
119                    let mut value_path = path.clone();
120                    value_path.push(value.clone());
121                    self.search_recursive_with_captures(
122                        value,
123                        value_path,
124                        results,
125                        all_captures,
126                    );
127                }
128            }
129            CBORCase::Tagged(_, content) => {
130                let mut tagged_path = path.clone();
131                tagged_path.push(content.clone());
132                self.search_recursive_with_captures(
133                    content,
134                    tagged_path,
135                    results,
136                    all_captures,
137                );
138            }
139            _ => {
140                // For primitive types, no further recursion
141            }
142        }
143    }
144}
145
146impl Default for SearchPattern {
147    fn default() -> Self {
148        // Create a default search pattern that matches any value
149        Self::new(Pattern::any())
150    }
151}
152
153impl Matcher for SearchPattern {
154    fn paths(&self, haystack: &CBOR) -> Vec<Path> {
155        let mut result_paths = Vec::new();
156        self.search_recursive(haystack, vec![haystack.clone()], &mut result_paths);
157
158        // Remove duplicates based on CBOR values in the path
159        let mut seen = std::collections::HashSet::new();
160        let mut unique = Vec::new();
161        for path in result_paths {
162            // Create a unique key based on the path's CBOR values
163            let path_key: Vec<_> = path
164                .iter()
165                .map(|cbor| cbor.to_cbor_data()) // Use serialized form as key
166                .collect();
167            if seen.insert(path_key) {
168                unique.push(path);
169            }
170        }
171
172        unique
173    }
174
175    fn paths_with_captures(
176        &self,
177        haystack: &CBOR,
178    ) -> (Vec<Path>, std::collections::HashMap<String, Vec<Path>>) {
179        let mut result_paths = Vec::new();
180        let mut all_captures = std::collections::HashMap::new();
181
182        self.search_recursive_with_captures(
183            haystack,
184            vec![haystack.clone()],
185            &mut result_paths,
186            &mut all_captures,
187        );
188
189        // Remove duplicates from result paths
190        let mut seen = std::collections::HashSet::new();
191        let mut unique_paths = Vec::new();
192        for path in result_paths {
193            let path_key: Vec<_> =
194                path.iter().map(|cbor| cbor.to_cbor_data()).collect();
195            if seen.insert(path_key) {
196                unique_paths.push(path);
197            }
198        }
199
200        (unique_paths, all_captures)
201    }
202
203    fn collect_capture_names(&self, names: &mut Vec<String>) {
204        // Delegate to the inner pattern to collect its capture names
205        self.0.collect_capture_names(names);
206    }
207
208    fn compile(
209        &self,
210        code: &mut Vec<Instr>,
211        literals: &mut Vec<Pattern>,
212        captures: &mut Vec<String>,
213    ) {
214        let idx = literals.len();
215        literals.push((*self.0).clone());
216
217        // Collect capture names from the inner pattern
218        let mut inner_names = Vec::new();
219        self.0.collect_capture_names(&mut inner_names);
220        let mut capture_map = Vec::new();
221
222        for name in inner_names {
223            let pos = if let Some(i) = captures.iter().position(|n| n == &name)
224            {
225                i
226            } else {
227                let i = captures.len();
228                captures.push(name.clone());
229                i
230            };
231            capture_map.push((name, pos));
232        }
233
234        code.push(Instr::Search { pat_idx: idx, capture_map });
235    }
236}
237
238impl std::fmt::Display for SearchPattern {
239    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
240        write!(f, "search({})", self.pattern())
241    }
242}