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            // Handle captures based on the pattern type:
84            // - For array element captures like [@a(*), @b(*), @c(*)], preserve
85            //   the original paths so that @a points to element 1, @b to
86            //   element 2, etc.
87            // - For complex/nested patterns, captures point to the search
88            //   location where the pattern was found, which provides the
89            //   context for the match.
90            for (name, capture_paths) in captures {
91                if capture_paths.len() > 1
92                    || (capture_paths.len() == 1
93                        && capture_paths[0].len() > path.len())
94                {
95                    // This appears to be an array pattern with element-level
96                    // captures or a nested pattern where we
97                    // want to preserve the original paths
98                    for capture_path in capture_paths {
99                        // Transform the capture path to include the search
100                        // context
101                        let mut transformed_path = path.clone();
102                        // Add the relative path from the matched location to
103                        // the captured value
104                        if capture_path.len() > 1 {
105                            transformed_path
106                                .extend_from_slice(&capture_path[1..]);
107                        }
108                        all_captures
109                            .entry(name.clone())
110                            .or_default()
111                            .push(transformed_path);
112                    }
113                } else {
114                    // For simple captures or when unclear, use the search
115                    // location
116                    all_captures.entry(name).or_default().push(path.clone());
117                }
118            }
119        }
120
121        // Recursively search children based on CBOR type
122        match cbor.as_case() {
123            CBORCase::Array(arr) => {
124                for child in arr.iter() {
125                    let mut new_path = path.clone();
126                    new_path.push(child.clone());
127                    self.search_recursive_with_captures(
128                        child,
129                        new_path,
130                        results,
131                        all_captures,
132                    );
133                }
134            }
135            CBORCase::Map(map) => {
136                for (key, value) in map.iter() {
137                    // Search both keys and values
138                    let mut key_path = path.clone();
139                    key_path.push(key.clone());
140                    self.search_recursive_with_captures(
141                        key,
142                        key_path,
143                        results,
144                        all_captures,
145                    );
146
147                    let mut value_path = path.clone();
148                    value_path.push(value.clone());
149                    self.search_recursive_with_captures(
150                        value,
151                        value_path,
152                        results,
153                        all_captures,
154                    );
155                }
156            }
157            CBORCase::Tagged(_, content) => {
158                let mut tagged_path = path.clone();
159                tagged_path.push(content.clone());
160                self.search_recursive_with_captures(
161                    content,
162                    tagged_path,
163                    results,
164                    all_captures,
165                );
166            }
167            _ => {
168                // For primitive types, no further recursion
169            }
170        }
171    }
172}
173
174impl Default for SearchPattern {
175    fn default() -> Self {
176        // Create a default search pattern that matches any value
177        Self::new(Pattern::any())
178    }
179}
180
181impl Matcher for SearchPattern {
182    fn paths(&self, haystack: &CBOR) -> Vec<Path> {
183        let mut result_paths = Vec::new();
184        self.search_recursive(
185            haystack,
186            vec![haystack.clone()],
187            &mut result_paths,
188        );
189
190        // Remove duplicates based on CBOR values in the path
191        let mut seen = std::collections::HashSet::new();
192        let mut unique = Vec::new();
193        for path in result_paths {
194            // Create a unique key based on the path's CBOR values
195            let path_key: Vec<_> = path
196                .iter()
197                .map(|cbor| cbor.to_cbor_data()) // Use serialized form as key
198                .collect();
199            if seen.insert(path_key) {
200                unique.push(path);
201            }
202        }
203
204        unique
205    }
206
207    fn paths_with_captures(
208        &self,
209        haystack: &CBOR,
210    ) -> (Vec<Path>, std::collections::HashMap<String, Vec<Path>>) {
211        let mut result_paths = Vec::new();
212        let mut all_captures = std::collections::HashMap::new();
213
214        self.search_recursive_with_captures(
215            haystack,
216            vec![haystack.clone()],
217            &mut result_paths,
218            &mut all_captures,
219        );
220
221        // Remove duplicates from result paths
222        let mut seen = std::collections::HashSet::new();
223        let mut unique_paths = Vec::new();
224        for path in result_paths {
225            let path_key: Vec<_> =
226                path.iter().map(|cbor| cbor.to_cbor_data()).collect();
227            if seen.insert(path_key) {
228                unique_paths.push(path);
229            }
230        }
231
232        (unique_paths, all_captures)
233    }
234
235    fn collect_capture_names(&self, names: &mut Vec<String>) {
236        // Delegate to the inner pattern to collect its capture names
237        self.0.collect_capture_names(names);
238    }
239
240    fn compile(
241        &self,
242        code: &mut Vec<Instr>,
243        literals: &mut Vec<Pattern>,
244        captures: &mut Vec<String>,
245    ) {
246        let idx = literals.len();
247        literals.push((*self.0).clone());
248
249        // Collect capture names from the inner pattern
250        let mut inner_names = Vec::new();
251        self.0.collect_capture_names(&mut inner_names);
252        let mut capture_map = Vec::new();
253
254        for name in inner_names {
255            let pos = if let Some(i) = captures.iter().position(|n| n == &name)
256            {
257                i
258            } else {
259                let i = captures.len();
260                captures.push(name.clone());
261                i
262            };
263            capture_map.push((name, pos));
264        }
265
266        code.push(Instr::Search { pat_idx: idx, capture_map });
267    }
268}
269
270impl std::fmt::Display for SearchPattern {
271    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
272        write!(f, "search({})", self.pattern())
273    }
274}