Skip to main content

mittens_query/
evaluator.rs

1use crate::ast::{
2    AttributeSelector, Combinator, CompoundSelector, QueryAst, SelectorSequence, SimpleSelector,
3};
4
5pub trait QueryTreeAdapter {
6    type NodeId: Copy + Eq;
7
8    fn children_of(&self, node: Self::NodeId) -> Vec<Self::NodeId>;
9
10    fn matches_type(&self, _node: Self::NodeId, _type_name: &str) -> bool {
11        false
12    }
13
14    fn matches_id(&self, _node: Self::NodeId, _id: &str) -> bool {
15        false
16    }
17
18    fn matches_guid(&self, _node: Self::NodeId, _guid: &str) -> bool {
19        false
20    }
21
22    fn matches_name(&self, _node: Self::NodeId, _name: &str) -> bool {
23        false
24    }
25
26    fn matches_class(&self, _node: Self::NodeId, _class_name: &str) -> bool {
27        false
28    }
29
30    fn matches_attribute(&self, _node: Self::NodeId, attribute: &AttributeSelector) -> bool {
31        if attribute.name == "name" {
32            return attribute
33                .value
34                .as_deref()
35                .map(|name| self.matches_name(_node, name))
36                .unwrap_or(false);
37        }
38        false
39    }
40}
41
42#[derive(Debug, Default)]
43pub struct QueryEvaluator;
44
45impl QueryEvaluator {
46    /// Evaluate `ast` against `tree` rooted at `root`. Returns matches in
47    /// DFS pre-order, including `root` itself if it matches.
48    pub fn evaluate<T: QueryTreeAdapter>(
49        tree: &T,
50        root: T::NodeId,
51        ast: &QueryAst,
52    ) -> Vec<T::NodeId> {
53        let mut out = Vec::new();
54        for sequence in &ast.selector_groups {
55            let mut path: Vec<T::NodeId> = Vec::new();
56            collect(tree, root, sequence, &mut path, &mut out);
57        }
58        out
59    }
60
61    fn matches_compound<T: QueryTreeAdapter>(
62        tree: &T,
63        node: T::NodeId,
64        compound: &CompoundSelector,
65    ) -> bool {
66        compound
67            .simple_selectors
68            .iter()
69            .all(|selector| Self::matches_simple(tree, node, selector))
70    }
71
72    fn matches_simple<T: QueryTreeAdapter>(
73        tree: &T,
74        node: T::NodeId,
75        selector: &SimpleSelector,
76    ) -> bool {
77        match selector {
78            SimpleSelector::Universal => true,
79            SimpleSelector::Type(type_name) => tree.matches_type(node, type_name),
80            SimpleSelector::Id(id) => tree.matches_id(node, id),
81            SimpleSelector::Guid(guid) => tree.matches_guid(node, guid),
82            SimpleSelector::Name(name) => tree.matches_name(node, name),
83            SimpleSelector::Class(class_name) => tree.matches_class(node, class_name),
84            SimpleSelector::Attribute(attribute) => tree.matches_attribute(node, attribute),
85        }
86    }
87}
88
89fn collect<T: QueryTreeAdapter>(
90    tree: &T,
91    node: T::NodeId,
92    sequence: &SelectorSequence,
93    path: &mut Vec<T::NodeId>,
94    out: &mut Vec<T::NodeId>,
95) {
96    path.push(node);
97    if matches_with_path::<T>(tree, sequence, path) {
98        out.push(node);
99    }
100    for child in tree.children_of(node) {
101        collect(tree, child, sequence, path, out);
102    }
103    path.pop();
104}
105
106/// `path` is the chain of ancestors from the query root down to (and including)
107/// the candidate node. The candidate is `path[path.len() - 1]`.
108fn matches_with_path<T: QueryTreeAdapter>(
109    tree: &T,
110    sequence: &SelectorSequence,
111    path: &[T::NodeId],
112) -> bool {
113    let segs = &sequence.segments;
114    if segs.is_empty() || path.is_empty() {
115        return false;
116    }
117    let last = segs.len() - 1;
118    let candidate = path[path.len() - 1];
119    if !QueryEvaluator::matches_compound(tree, candidate, &segs[last].compound) {
120        return false;
121    }
122
123    // Walk earlier segments backward through ancestors in `path`.
124    // The combinator on segs[i+1] describes how segs[i+1] relates to segs[i].
125    let mut path_cursor: isize = path.len() as isize - 1;
126    for i in (0..last).rev() {
127        let combinator = segs[i + 1].combinator.unwrap_or(Combinator::Descendant);
128        match combinator {
129            Combinator::Child => {
130                path_cursor -= 1;
131                if path_cursor < 0 {
132                    return false;
133                }
134                if !QueryEvaluator::matches_compound(
135                    tree,
136                    path[path_cursor as usize],
137                    &segs[i].compound,
138                ) {
139                    return false;
140                }
141            }
142            Combinator::Descendant => {
143                path_cursor -= 1;
144                let mut found = false;
145                while path_cursor >= 0 {
146                    if QueryEvaluator::matches_compound(
147                        tree,
148                        path[path_cursor as usize],
149                        &segs[i].compound,
150                    ) {
151                        found = true;
152                        break;
153                    }
154                    path_cursor -= 1;
155                }
156                if !found {
157                    return false;
158                }
159            }
160        }
161    }
162    true
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168    use crate::ast::{CompoundSelector, SelectorSegment, SelectorSequence, SimpleSelector};
169
170    /// Toy tree adapter for combinator tests.
171    /// Nodes are indices into a fixed slice of `(parent, type, name)`.
172    struct ToyTree {
173        nodes: Vec<(Option<usize>, &'static str, &'static str)>,
174    }
175
176    impl QueryTreeAdapter for ToyTree {
177        type NodeId = usize;
178        fn children_of(&self, node: usize) -> Vec<usize> {
179            self.nodes
180                .iter()
181                .enumerate()
182                .filter(|(_, (p, _, _))| *p == Some(node))
183                .map(|(i, _)| i)
184                .collect()
185        }
186        fn matches_type(&self, node: usize, type_name: &str) -> bool {
187            self.nodes[node].1 == type_name
188        }
189        fn matches_name(&self, node: usize, name: &str) -> bool {
190            self.nodes[node].2 == name
191        }
192    }
193
194    fn type_seg(t: &'static str, comb: Option<Combinator>) -> SelectorSegment {
195        SelectorSegment {
196            combinator: comb,
197            compound: CompoundSelector {
198                simple_selectors: vec![SimpleSelector::Type(t.into())],
199            },
200        }
201    }
202
203    /// Build tree:
204    ///   0 root(Root)
205    ///   ├─ 1 a(A)
206    ///   │  └─ 2 b(B)
207    ///   │     └─ 3 c(C)
208    ///   └─ 4 a2(A)
209    ///      └─ 5 c(C)            // C direct child of A
210    fn build_tree() -> ToyTree {
211        ToyTree {
212            nodes: vec![
213                (None, "Root", "root"),
214                (Some(0), "A", "a"),
215                (Some(1), "B", "b"),
216                (Some(2), "C", "c"),
217                (Some(0), "A", "a2"),
218                (Some(4), "C", "c2"),
219            ],
220        }
221    }
222
223    #[test]
224    fn child_combinator_filters_indirect_descendants() {
225        let tree = build_tree();
226        let ast = QueryAst {
227            selector_groups: vec![SelectorSequence {
228                segments: vec![type_seg("A", None), type_seg("C", Some(Combinator::Child))],
229            }],
230        };
231        let matches = QueryEvaluator::evaluate(&tree, 0, &ast);
232        // C node 3 is a grandchild of A — should NOT match A > C.
233        // C node 5 is a direct child of A — should match.
234        assert_eq!(matches, vec![5]);
235    }
236
237    #[test]
238    fn descendant_combinator_matches_at_any_depth() {
239        let tree = build_tree();
240        let ast = QueryAst {
241            selector_groups: vec![SelectorSequence {
242                segments: vec![
243                    type_seg("A", None),
244                    type_seg("C", Some(Combinator::Descendant)),
245                ],
246            }],
247        };
248        let mut matches = QueryEvaluator::evaluate(&tree, 0, &ast);
249        matches.sort();
250        assert_eq!(matches, vec![3, 5]);
251    }
252
253    #[test]
254    fn single_segment_matches_all_descendants() {
255        let tree = build_tree();
256        let ast = QueryAst {
257            selector_groups: vec![SelectorSequence {
258                segments: vec![type_seg("A", None)],
259            }],
260        };
261        let mut matches = QueryEvaluator::evaluate(&tree, 0, &ast);
262        matches.sort();
263        assert_eq!(matches, vec![1, 4]);
264    }
265}