Skip to main content

antlr4_runtime/
xpath.rs

1//! ANTLR parse-tree `XPath` queries.
2//!
3//! This is ANTLR's small tree-path dialect, not W3C `XPath`. It supports child
4//! (`/`) and descendant-or-self (`//`) selection, rule and token names,
5//! single-quoted token literals, wildcards, and inverted node tests.
6
7use std::collections::BTreeSet;
8
9use thiserror::Error;
10
11use crate::token::{Token, TokenStoreError};
12use crate::{
13    CommonTokenStream, InputStream, Node, NodeId, NodeKind, Recognizer, TOKEN_EOF, Vocabulary,
14};
15
16mod generated {
17    pub(super) mod x_path_lexer;
18}
19
20use generated::x_path_lexer::{ANYWHERE, BANG, ID, ROOT, STRING, WILDCARD, XPathLexer};
21
22/// A compiled ANTLR parse-tree `XPath` expression.
23#[derive(Clone, Debug)]
24pub struct XPath {
25    path: String,
26    elements: Vec<PathElement>,
27}
28
29impl XPath {
30    /// Compiles `path` against the rule and token names exposed by `recognizer`.
31    pub fn new<R>(recognizer: &R, path: &str) -> Result<Self, XPathError>
32    where
33        R: Recognizer + ?Sized,
34    {
35        let tokens = tokenize(path)?;
36        let elements = compile_elements(&tokens, recognizer.rule_names(), recognizer.vocabulary())?;
37        if elements.is_empty() {
38            return Err(XPathError::MissingPathElement);
39        }
40        Ok(Self {
41            path: path.to_owned(),
42            elements,
43        })
44    }
45
46    /// Returns the source expression used to compile this query.
47    #[must_use]
48    pub fn path(&self) -> &str {
49        &self.path
50    }
51
52    /// Evaluates this expression relative to `root`.
53    ///
54    /// Results retain parse-tree order and contain each node at most once.
55    /// ANTLR's synthetic evaluation root is retained between path steps so a
56    /// wildcard prefix can select `root` later. A final synthetic-root match is
57    /// omitted because it is not part of the caller's parse tree.
58    #[must_use]
59    pub fn evaluate<'tree>(&self, root: Node<'tree>) -> Vec<Node<'tree>> {
60        let mut work = vec![EvaluationNode::VirtualRoot(root)];
61        for element in &self.elements {
62            let mut next = Vec::new();
63            let mut seen = BTreeSet::new();
64            for node in work {
65                if !node.has_children() {
66                    continue;
67                }
68                extend_unique(&mut next, &mut seen, evaluate_element(*element, node));
69            }
70            work = next;
71        }
72        work.into_iter()
73            .filter_map(EvaluationNode::tree_node)
74            .collect()
75    }
76
77    /// Compiles and evaluates `path` relative to `root`.
78    pub fn find_all<'tree, R>(
79        root: Node<'tree>,
80        path: &str,
81        recognizer: &R,
82    ) -> Result<Vec<Node<'tree>>, XPathError>
83    where
84        R: Recognizer + ?Sized,
85    {
86        Ok(Self::new(recognizer, path)?.evaluate(root))
87    }
88}
89
90/// An invalid ANTLR parse-tree `XPath` expression.
91#[derive(Clone, Debug, Eq, Error, PartialEq)]
92pub enum XPathError {
93    #[error("Invalid tokens or characters at index {index} in path '{path}'")]
94    InvalidCharacters { index: usize, path: String },
95    #[error("Missing path element at end of path")]
96    MissingPathElement,
97    #[error("{name} at index {index} isn't a valid token name")]
98    InvalidTokenName { name: String, index: usize },
99    #[error("{name} at index {index} isn't a valid rule name")]
100    InvalidRuleName { name: String, index: usize },
101    #[error("Unknown path element {element} at index {index}")]
102    UnknownPathElement { element: String, index: usize },
103    #[error("Could not tokenize path: {message}")]
104    Tokenization { message: String },
105}
106
107#[derive(Clone, Copy, Debug)]
108struct PathElement {
109    axis: Axis,
110    test: NodeTest,
111    invert: bool,
112}
113
114#[derive(Clone, Copy, Debug)]
115enum Axis {
116    Child,
117    DescendantOrSelf,
118}
119
120#[derive(Clone, Copy, Debug)]
121enum NodeTest {
122    Rule(usize),
123    Token(i32),
124    Wildcard,
125}
126
127#[derive(Clone, Copy, Debug)]
128enum EvaluationNode<'tree> {
129    VirtualRoot(Node<'tree>),
130    Tree(Node<'tree>),
131}
132
133impl<'tree> EvaluationNode<'tree> {
134    fn has_children(self) -> bool {
135        match self {
136            Self::VirtualRoot(_) => true,
137            Self::Tree(node) => node.children().next().is_some(),
138        }
139    }
140
141    const fn id(self) -> Option<NodeId> {
142        match self {
143            Self::VirtualRoot(_) => None,
144            Self::Tree(node) => Some(node.id()),
145        }
146    }
147
148    const fn tree_node(self) -> Option<Node<'tree>> {
149        match self {
150            Self::VirtualRoot(_) => None,
151            Self::Tree(node) => Some(node),
152        }
153    }
154}
155
156#[derive(Clone, Copy, Debug, Eq, PartialEq)]
157enum LexemeKind {
158    Anywhere,
159    Root,
160    Wildcard,
161    Bang,
162    Identifier,
163    String,
164    Eof,
165}
166
167#[derive(Clone, Debug)]
168struct Lexeme {
169    kind: LexemeKind,
170    text: String,
171    index: usize,
172}
173
174fn tokenize(path: &str) -> Result<Vec<Lexeme>, XPathError> {
175    let mut lexer = XPathLexer::new(InputStream::new(path));
176    lexer.remove_error_listeners();
177    let mut stream =
178        CommonTokenStream::try_new(lexer).map_err(|error| tokenization_error(&error))?;
179    stream.fill();
180
181    if let Some(error) = stream.drain_source_errors().into_iter().next() {
182        return Err(XPathError::InvalidCharacters {
183            index: source_index(path, error.line, error.column),
184            path: path.to_owned(),
185        });
186    }
187
188    stream
189        .tokens()
190        .map(|token| {
191            let kind = match token.token_type() {
192                ANYWHERE => LexemeKind::Anywhere,
193                ROOT => LexemeKind::Root,
194                WILDCARD => LexemeKind::Wildcard,
195                BANG => LexemeKind::Bang,
196                ID => LexemeKind::Identifier,
197                STRING => LexemeKind::String,
198                TOKEN_EOF => LexemeKind::Eof,
199                token_type => {
200                    return Err(XPathError::Tokenization {
201                        message: format!("unexpected XPath lexer token type {token_type}"),
202                    });
203                }
204            };
205            Ok(Lexeme {
206                kind,
207                text: token.text_or_empty().to_owned(),
208                index: token.start(),
209            })
210        })
211        .collect()
212}
213
214fn tokenization_error(error: &TokenStoreError) -> XPathError {
215    XPathError::Tokenization {
216        message: error.to_string(),
217    }
218}
219
220fn source_index(path: &str, line: usize, column: usize) -> usize {
221    let mut current_line = 1;
222    let mut line_start = 0;
223    for (index, ch) in path.chars().enumerate() {
224        if current_line == line {
225            return line_start + column;
226        }
227        if ch == '\n' {
228            current_line += 1;
229            line_start = index + 1;
230        }
231    }
232    line_start + column
233}
234
235fn compile_elements(
236    tokens: &[Lexeme],
237    rule_names: &[String],
238    vocabulary: &Vocabulary,
239) -> Result<Vec<PathElement>, XPathError> {
240    let mut elements = Vec::new();
241    let mut cursor = 0;
242    while let Some(token) = tokens.get(cursor) {
243        match token.kind {
244            LexemeKind::Anywhere | LexemeKind::Root => {
245                let axis = if token.kind == LexemeKind::Anywhere {
246                    Axis::DescendantOrSelf
247                } else {
248                    Axis::Child
249                };
250                cursor += 1;
251                let invert = tokens
252                    .get(cursor)
253                    .is_some_and(|next| next.kind == LexemeKind::Bang);
254                cursor += usize::from(invert);
255                let word = tokens.get(cursor).ok_or(XPathError::MissingPathElement)?;
256                if word.kind == LexemeKind::Eof {
257                    return Err(XPathError::MissingPathElement);
258                }
259                elements.push(compile_element(word, axis, invert, rule_names, vocabulary)?);
260                cursor += 1;
261            }
262            LexemeKind::Identifier | LexemeKind::Wildcard => {
263                elements.push(compile_element(
264                    token,
265                    Axis::Child,
266                    false,
267                    rule_names,
268                    vocabulary,
269                )?);
270                cursor += 1;
271            }
272            LexemeKind::Eof => break,
273            LexemeKind::Bang | LexemeKind::String => {
274                return Err(XPathError::UnknownPathElement {
275                    element: token.text.clone(),
276                    index: token.index,
277                });
278            }
279        }
280    }
281    Ok(elements)
282}
283
284fn compile_element(
285    token: &Lexeme,
286    axis: Axis,
287    invert: bool,
288    rule_names: &[String],
289    vocabulary: &Vocabulary,
290) -> Result<PathElement, XPathError> {
291    let node_test = match token.kind {
292        LexemeKind::Wildcard => NodeTest::Wildcard,
293        LexemeKind::String => NodeTest::Token(resolve_token(token, vocabulary)?),
294        LexemeKind::Identifier if token.text.starts_with(char::is_uppercase) => {
295            NodeTest::Token(resolve_token(token, vocabulary)?)
296        }
297        _ => NodeTest::Rule(resolve_rule(token, rule_names)?),
298    };
299    Ok(PathElement {
300        axis,
301        test: node_test,
302        invert,
303    })
304}
305
306fn resolve_token(token: &Lexeme, vocabulary: &Vocabulary) -> Result<i32, XPathError> {
307    vocabulary
308        .token_type(&token.text)
309        .ok_or_else(|| XPathError::InvalidTokenName {
310            name: token.text.clone(),
311            index: token.index,
312        })
313}
314
315fn resolve_rule(token: &Lexeme, rule_names: &[String]) -> Result<usize, XPathError> {
316    rule_names
317        .iter()
318        .rposition(|name| name == &token.text)
319        .ok_or_else(|| XPathError::InvalidRuleName {
320            name: token.text.clone(),
321            index: token.index,
322        })
323}
324
325fn evaluate_element<'tree>(
326    element: PathElement,
327    node: EvaluationNode<'tree>,
328) -> Box<dyn Iterator<Item = EvaluationNode<'tree>> + 'tree> {
329    match node {
330        EvaluationNode::VirtualRoot(root) => evaluate_virtual_root(element, root),
331        EvaluationNode::Tree(node) => evaluate_tree_element(element, node),
332    }
333}
334
335fn evaluate_virtual_root<'tree>(
336    element: PathElement,
337    root: Node<'tree>,
338) -> Box<dyn Iterator<Item = EvaluationNode<'tree>> + 'tree> {
339    match element.axis {
340        Axis::Child => Box::new(
341            std::iter::once(root)
342                .filter(move |node| matches_direct(*node, element.test, element.invert))
343                .map(EvaluationNode::Tree),
344        ),
345        Axis::DescendantOrSelf if matches!(element.test, NodeTest::Wildcard) && !element.invert => {
346            Box::new(
347                std::iter::once(EvaluationNode::VirtualRoot(root))
348                    .chain(evaluate_anywhere(element, root).map(EvaluationNode::Tree)),
349            )
350        }
351        Axis::DescendantOrSelf => {
352            Box::new(evaluate_anywhere(element, root).map(EvaluationNode::Tree))
353        }
354    }
355}
356
357fn evaluate_tree_element<'tree>(
358    element: PathElement,
359    node: Node<'tree>,
360) -> Box<dyn Iterator<Item = EvaluationNode<'tree>> + 'tree> {
361    match element.axis {
362        Axis::Child => Box::new(
363            node.children()
364                .filter(move |child| matches_direct(*child, element.test, element.invert))
365                .map(EvaluationNode::Tree),
366        ),
367        Axis::DescendantOrSelf => {
368            Box::new(evaluate_anywhere(element, node).map(EvaluationNode::Tree))
369        }
370    }
371}
372
373fn evaluate_anywhere<'tree>(
374    element: PathElement,
375    node: Node<'tree>,
376) -> Box<dyn Iterator<Item = Node<'tree>> + 'tree> {
377    match element.test {
378        NodeTest::Wildcard if element.invert => Box::new(std::iter::empty()),
379        NodeTest::Wildcard => Box::new(node.descendants()),
380        NodeTest::Rule(rule_index) => Box::new(node.descendants().filter(move |candidate| {
381            candidate
382                .as_rule()
383                .is_some_and(|rule| rule.rule_index() == rule_index)
384        })),
385        NodeTest::Token(token_type) => Box::new(
386            node.descendants()
387                .filter(move |candidate| node_token_type(*candidate) == Some(token_type)),
388        ),
389    }
390}
391
392fn matches_direct(node: Node<'_>, node_test: NodeTest, invert: bool) -> bool {
393    match node_test {
394        NodeTest::Wildcard => !invert,
395        NodeTest::Rule(rule_index) => node
396            .as_rule()
397            .is_some_and(|rule| (rule.rule_index() == rule_index) != invert),
398        NodeTest::Token(token_type) => {
399            node_token_type(node).is_some_and(|actual| (actual == token_type) != invert)
400        }
401    }
402}
403
404fn node_token_type(node: Node<'_>) -> Option<i32> {
405    match node.kind() {
406        NodeKind::Terminal => node
407            .as_terminal()
408            .map(|terminal| terminal.symbol().token_type()),
409        NodeKind::Error => node.as_error().map(|error| error.symbol().token_type()),
410        NodeKind::Rule => None,
411    }
412}
413
414fn extend_unique<'tree>(
415    nodes: &mut Vec<EvaluationNode<'tree>>,
416    seen: &mut BTreeSet<Option<NodeId>>,
417    matches: impl IntoIterator<Item = EvaluationNode<'tree>>,
418) {
419    for node in matches {
420        if seen.insert(node.id()) {
421            nodes.push(node);
422        }
423    }
424}
425
426#[cfg(test)]
427#[allow(clippy::disallowed_methods)] // `insta` assertion macros unwrap internal I/O.
428mod tests {
429    use super::*;
430    use crate::RecognizerData;
431    use crate::token::{TokenSpec, TokenStore};
432    use crate::tree::{ParseTreeStorage, ParsedFile, ParserRuleContext};
433
434    const PROG: usize = 0;
435    const FUNC: usize = 1;
436    const BODY: usize = 2;
437    const ARG: usize = 3;
438    const STAT: usize = 4;
439    const EXPR: usize = 5;
440    const PRIMARY: usize = 6;
441
442    const DEF: i32 = 1;
443    const LPAREN: i32 = 2;
444    const COMMA: i32 = 3;
445    const RPAREN: i32 = 4;
446    const LBRACE: i32 = 5;
447    const RBRACE: i32 = 6;
448    const SEMI: i32 = 7;
449    const ASSIGN: i32 = 8;
450    const MUL: i32 = 9;
451    const ADD: i32 = 11;
452    const RETURN: i32 = 13;
453    const IDENTIFIER: i32 = 14;
454    const INTEGER: i32 = 15;
455
456    enum TreeSpec {
457        Rule(usize, Vec<Self>),
458        Token(i32, &'static str),
459    }
460
461    fn rule(index: usize, children: Vec<TreeSpec>) -> TreeSpec {
462        TreeSpec::Rule(index, children)
463    }
464
465    const fn token(token_type: i32, text: &'static str) -> TreeSpec {
466        TreeSpec::Token(token_type, text)
467    }
468
469    fn primary_expr(token_type: i32, text: &'static str) -> TreeSpec {
470        rule(EXPR, vec![rule(PRIMARY, vec![token(token_type, text)])])
471    }
472
473    fn binary_expr(left: TreeSpec, operator: TreeSpec, right: TreeSpec) -> TreeSpec {
474        rule(EXPR, vec![left, operator, right])
475    }
476
477    fn first_function() -> TreeSpec {
478        let assignment = rule(
479            STAT,
480            vec![
481                token(IDENTIFIER, "x"),
482                token(ASSIGN, "="),
483                binary_expr(
484                    primary_expr(INTEGER, "3"),
485                    token(ADD, "+"),
486                    primary_expr(INTEGER, "4"),
487                ),
488                token(SEMI, ";"),
489            ],
490        );
491        let print = rule(STAT, vec![primary_expr(IDENTIFIER, "y"), token(SEMI, ";")]);
492        let body = rule(
493            BODY,
494            vec![
495                token(LBRACE, "{"),
496                assignment,
497                print,
498                rule(STAT, vec![token(SEMI, ";")]),
499                token(RBRACE, "}"),
500            ],
501        );
502        rule(
503            FUNC,
504            vec![
505                token(DEF, "def"),
506                token(IDENTIFIER, "f"),
507                token(LPAREN, "("),
508                rule(ARG, vec![token(IDENTIFIER, "x")]),
509                token(COMMA, ","),
510                rule(ARG, vec![token(IDENTIFIER, "y")]),
511                token(RPAREN, ")"),
512                body,
513            ],
514        )
515    }
516
517    fn second_function() -> TreeSpec {
518        let product = binary_expr(
519            primary_expr(INTEGER, "2"),
520            token(MUL, "*"),
521            primary_expr(IDENTIFIER, "x"),
522        );
523        let returned = binary_expr(primary_expr(INTEGER, "1"), token(ADD, "+"), product);
524        let body = rule(
525            BODY,
526            vec![
527                token(LBRACE, "{"),
528                rule(
529                    STAT,
530                    vec![token(RETURN, "return"), returned, token(SEMI, ";")],
531                ),
532                token(RBRACE, "}"),
533            ],
534        );
535        rule(
536            FUNC,
537            vec![
538                token(DEF, "def"),
539                token(IDENTIFIER, "g"),
540                token(LPAREN, "("),
541                rule(ARG, vec![token(IDENTIFIER, "x")]),
542                token(RPAREN, ")"),
543                body,
544            ],
545        )
546    }
547
548    fn materialize(
549        spec: TreeSpec,
550        tokens: &mut TokenStore,
551        storage: &mut ParseTreeStorage,
552    ) -> NodeId {
553        match spec {
554            TreeSpec::Token(token_type, text) => {
555                let token = tokens
556                    .push(TokenSpec::explicit(token_type, text))
557                    .expect("test token should fit");
558                storage.terminal(token)
559            }
560            TreeSpec::Rule(rule_index, children) => {
561                let mut context = ParserRuleContext::new(rule_index, -1);
562                for child in children {
563                    let child = materialize(child, tokens, storage);
564                    storage.add_child(&mut context, child);
565                }
566                storage.finish_rule(context)
567            }
568        }
569    }
570
571    fn sample_tree() -> ParsedFile {
572        let mut tokens = TokenStore::new(None, "Expr");
573        let mut storage = ParseTreeStorage::new();
574        let root = materialize(
575            rule(PROG, vec![first_function(), second_function()]),
576            &mut tokens,
577            &mut storage,
578        );
579        ParsedFile::new(tokens, storage, root)
580    }
581
582    #[derive(Debug)]
583    struct TestRecognizer {
584        data: RecognizerData,
585    }
586
587    impl Recognizer for TestRecognizer {
588        fn data(&self) -> &RecognizerData {
589            &self.data
590        }
591
592        fn data_mut(&mut self) -> &mut RecognizerData {
593            &mut self.data
594        }
595    }
596
597    fn expr_recognizer() -> TestRecognizer {
598        let vocabulary = Vocabulary::new(
599            [
600                None,
601                Some("'def'"),
602                Some("'('"),
603                Some("','"),
604                Some("')'"),
605                Some("'{'"),
606                Some("'}'"),
607                Some("';'"),
608                Some("'='"),
609                Some("'*'"),
610                Some("'/'"),
611                Some("'+'"),
612                Some("'-'"),
613                Some("'return'"),
614                None,
615                None,
616            ],
617            [
618                None,
619                None,
620                None,
621                None,
622                None,
623                None,
624                None,
625                None,
626                None,
627                Some("MUL"),
628                Some("DIV"),
629                Some("ADD"),
630                Some("SUB"),
631                Some("RETURN"),
632                Some("ID"),
633                Some("INT"),
634            ],
635            [None::<&str>; 16],
636        );
637        TestRecognizer {
638            data: RecognizerData::new("Expr.g4", vocabulary)
639                .with_rule_names(["prog", "func", "body", "arg", "stat", "expr", "primary"]),
640        }
641    }
642
643    fn display_nodes(nodes: Vec<Node<'_>>, rule_names: &[String]) -> Vec<String> {
644        nodes
645            .into_iter()
646            .map(|node| {
647                node.as_rule()
648                    .map_or_else(|| node.text(), |rule| rule_names[rule.rule_index()].clone())
649            })
650            .collect()
651    }
652
653    #[test]
654    fn upstream_valid_paths() {
655        let tree = sample_tree();
656        let recognizer = expr_recognizer();
657        let paths = [
658            "/prog/func",
659            "/prog/*",
660            "/*/func",
661            "prog",
662            "/prog",
663            "/*",
664            "*",
665            "//ID",
666            "//expr/primary/ID",
667            "//body//ID",
668            "//'return'",
669            "//RETURN",
670            "//primary/*",
671            "//func/*/stat",
672            "/prog/func/'def'",
673            "//stat/';'",
674            "//expr/primary/!ID",
675            "//expr/!primary",
676            "//!*",
677            "/!*",
678            "//expr//ID",
679        ];
680        let results = paths
681            .into_iter()
682            .map(|path| {
683                let nodes =
684                    XPath::find_all(tree.tree(), path, &recognizer).expect("valid XPath query");
685                (path, display_nodes(nodes, recognizer.data().rule_names()))
686            })
687            .collect::<Vec<_>>();
688
689        insta::assert_debug_snapshot!("upstream_valid_paths", results);
690    }
691
692    #[test]
693    fn upstream_invalid_paths() {
694        let recognizer = expr_recognizer();
695        let paths = ["&", "//w&e/", "///", "//", "//Ick", "/prog/ick"];
696        let errors = paths
697            .into_iter()
698            .map(|path| {
699                (
700                    path,
701                    XPath::new(&recognizer, path)
702                        .expect_err("invalid XPath query")
703                        .to_string(),
704                )
705            })
706            .collect::<Vec<_>>();
707
708        insta::assert_debug_snapshot!("upstream_invalid_paths", errors);
709    }
710
711    #[test]
712    fn lexer_and_parser_edge_cases_are_explicit() {
713        let recognizer = expr_recognizer();
714        let paths = ["", "// ID", "//'return", "!", "//!"];
715        let errors = paths
716            .into_iter()
717            .map(|path| {
718                (
719                    path,
720                    XPath::new(&recognizer, path)
721                        .expect_err("invalid XPath query")
722                        .to_string(),
723                )
724            })
725            .collect::<Vec<_>>();
726
727        insta::assert_debug_snapshot!("lexer_and_parser_edge_cases", errors);
728    }
729
730    #[test]
731    fn generated_lexer_accepts_upstream_unicode_name_ranges() {
732        let recognizer = TestRecognizer {
733            data: RecognizerData::new(
734                "Unicode.g4",
735                Vocabulary::new([None::<&str>; 2], [None, Some("ÄTOKEN")], [None::<&str>; 2]),
736            )
737            .with_rule_names(["éclair", "文"]),
738        };
739
740        assert!(XPath::new(&recognizer, "//ÄTOKEN").is_ok());
741        assert!(XPath::new(&recognizer, "//éclair").is_ok());
742        assert!(XPath::new(&recognizer, "//文").is_ok());
743    }
744
745    #[test]
746    fn named_anywhere_inversion_matches_java_4_13_2() {
747        let tree = sample_tree();
748        let recognizer = expr_recognizer();
749        let paths = ["//ID", "//!ID", "//expr", "//!expr"];
750        let results = paths
751            .into_iter()
752            .map(|path| {
753                let nodes =
754                    XPath::find_all(tree.tree(), path, &recognizer).expect("valid XPath query");
755                (path, display_nodes(nodes, recognizer.data().rule_names()))
756            })
757            .collect::<Vec<_>>();
758
759        insta::assert_debug_snapshot!("named_anywhere_inversion_matches_java_4_13_2", results);
760    }
761
762    #[test]
763    fn wildcard_anywhere_preserves_java_virtual_root_for_later_steps() {
764        let tree = sample_tree();
765        let recognizer = expr_recognizer();
766        let nodes =
767            XPath::find_all(tree.tree(), "//*/prog", &recognizer).expect("valid XPath query");
768
769        insta::assert_debug_snapshot!(
770            "wildcard_anywhere_virtual_root",
771            display_nodes(nodes, recognizer.data().rule_names())
772        );
773    }
774}