1use 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#[derive(Clone, Debug)]
24pub struct XPath {
25 path: String,
26 elements: Vec<PathElement>,
27}
28
29impl XPath {
30 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 #[must_use]
48 pub fn path(&self) -> &str {
49 &self.path
50 }
51
52 #[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 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#[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)] mod 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}