Skip to main content

libxml_rs/xml/xpath/
parser.rs

1//! XPath 1.0 Expression Parser (§25).
2//!
3//! Parses token streams from the lexer into the AST defined in `ast.rs`.
4//! Implements the full XPath 1.0 grammar.
5//!
6//! # UPSTREAM-PARITY
7//!
8//! Grammar (from XPath 1.0 spec §3.7):
9//!
10//! ```text
11//! Expr        ::= OrExpr
12//! OrExpr      ::= AndExpr ('or' AndExpr)*
13//! AndExpr     ::= EqualityExpr ('and' EqualityExpr)*
14//! EqualityExpr ::= RelationalExpr (('=' | '!=') RelationalExpr)*
15//! RelationalExpr ::= AdditiveExpr (('<' | '>' | '<=' | '>=') AdditiveExpr)*
16//! AdditiveExpr ::= MultiplicativeExpr (('+' | '-') MultiplicativeExpr)*
17//! MultiplicativeExpr ::= UnaryExpr (('*' | 'div' | 'mod') UnaryExpr)*
18//! UnaryExpr   ::= '-'* UnionExpr
19//! UnionExpr   ::= PathExpr ('|' PathExpr)*
20//! PathExpr    ::= LocationPath | FilterExpr (('/' | '//') RelativeLocationPath)?
21//! LocationPath ::= AbsoluteLocationPath | RelativeLocationPath
22//! AbsoluteLocationPath ::= '/' RelativeLocationPath? | '//' RelativeLocationPath
23//! RelativeLocationPath ::= Step (('/' | '//') Step)*
24//! Step        ::= AxisSpecifier NodeTest Predicate*
25//!              |  AbbreviatedStep
26//! AxisSpecifier ::= AxisName '::' | '@'?
27//! AbbreviatedStep ::= '.' | '..'
28//! Predicate   ::= '[' Expr ']'
29//! FilterExpr  ::= PrimaryExpr Predicate*
30//! PrimaryExpr ::= VariableReference | '(' Expr ')' | Literal | Number | FunctionCall
31//! ```
32//!
33//! # Courts
34//!
35//! XPATH-PARSER-*
36//!
37//! # Upstream contract
38//!
39//! Mirrors the compilation half of upstream `xpath.c`
40//! (`SRC-LIBXML2-2.15.0-XPATH-C`, parity target libxml2 2.15.3 oracle):
41//! xmlXPathCompile builds an xmlXPathCompExpr from the same grammar this
42//! recursive-descent parser implements (XPath 1.0 §3.7).
43//!
44//! # Conceptual behavior
45//!
46//! Implements the full XPath 1.0 grammar from the lexer token stream into
47//! the ast.rs expression tree, including precedence (or → and → equality
48//! → relational → additive → multiplicative → unary → union → path),
49//! predicates on steps and filter expressions, abbreviated steps and the
50//! axis-specifier forms.
51//!
52//! # Ownership & safety invariants
53//!
54//! The parser owns the token stream for the duration of the parse and
55//! produces an owned AST; `ParseError` carries an owned message and
56//! position. No C pointers cross the parser boundary — compilation is
57//! safe to run on any thread.
58//!
59//! # Historical quirks & epochs
60//!
61//! R-000105: node tests (`node()`, `text()`, `comment()`, `processing-
62//! instruction()`) were originally parsed as function calls; the fix
63//! distinguishes them at the node-test production, matching the 2.15.3
64//! oracle. The grammar itself is stable across the oracle matrix (the
65//! E-001 epoch changed xmllint node-set output, not expression parsing).
66//!
67//! # Deliberate oddities
68//!
69//! The parser accepts the upstream-lenient forms (e.g. whitespace
70//! handling around abbreviated axes) that a strict grammar would reject,
71//! because compile errors are observable through xmlXPathCompile return
72//! values.
73//!
74//! # Proving courts
75//!
76//! XPATH-PARSER-* differential probes compile expressions against the
77//! oracle and compare success/error byte-identical; the XSLT pattern
78//! courts compile match patterns through this parser.
79//!
80//! # Tempting simplifications that would break parity
81//!
82//! Do not treat node-test names as generic function calls: R-000105
83//! proved that breaks `//text()` style paths. Do not normalize or reject
84//! lenient whitespace forms — xmlXPathCompile error parity is part of the
85//! C ABI.
86
87use crate::xml::xpath::ast::*;
88use crate::xml::xpath::lexer::Token;
89
90/// Errors that can occur during parsing.
91#[derive(Debug, Clone, PartialEq)]
92pub struct ParseError {
93    /// Human-readable description of what went wrong
94    pub message: String,
95    /// Token index at which the error was detected
96    pub pos: usize,
97}
98
99impl std::fmt::Display for ParseError {
100    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101        write!(
102            f,
103            "XPath parse error at position {}: {}",
104            self.pos, self.message
105        )
106    }
107}
108
109// ═══════════════════════════════════════════════════════════════════════════════
110// Parser
111// ═══════════════════════════════════════════════════════════════════════════════
112
113/// Recursive-descent parser for XPath 1.0 expressions.
114///
115/// Consumes the token stream produced by the lexer and builds the
116/// expression AST defined in `crate::xml::xpath::ast`.
117#[derive(Debug)]
118pub struct Parser {
119    tokens: Vec<Token>,
120    pos: usize,
121}
122
123impl Parser {
124    /// Create a parser over a token stream produced by the lexer.
125    pub const fn new(tokens: Vec<Token>) -> Self {
126        Self { tokens, pos: 0 }
127    }
128
129    /// Parse a complete XPath expression.
130    pub fn parse(&mut self) -> Result<Expr, ParseError> {
131        let expr = self.parse_or_expr()?;
132        if !self.is_eof() {
133            Err(self.error(format!("Unexpected token: {}", self.current())))?;
134        }
135        Ok(expr)
136    }
137
138    // ── Current token helpers ────────────────────────────────────────────
139
140    fn current(&self) -> Token {
141        if self.pos < self.tokens.len() {
142            self.tokens[self.pos].clone()
143        } else {
144            Token::Eof
145        }
146    }
147
148    fn peek(&self) -> Token {
149        if self.pos + 1 < self.tokens.len() {
150            self.tokens[self.pos + 1].clone()
151        } else {
152            Token::Eof
153        }
154    }
155
156    const fn advance(&mut self) {
157        if self.pos < self.tokens.len() {
158            self.pos += 1;
159        }
160    }
161
162    fn is_eof(&self) -> bool {
163        matches!(self.current(), Token::Eof)
164    }
165
166    const fn error(&self, msg: String) -> ParseError {
167        ParseError {
168            message: msg,
169            pos: self.pos,
170        }
171    }
172
173    /// Check if the current token matches the given token.
174    fn at(&self, token: &Token) -> bool {
175        std::mem::discriminant(&self.current()) == std::mem::discriminant(token)
176    }
177
178    /// Expect and consume a specific token.
179    fn expect(&mut self, expected: &Token) -> Result<(), ParseError> {
180        if self.at(expected) {
181            self.advance();
182            Ok(())
183        } else {
184            Err(self.error(format!("Expected {}, got {}", expected, self.current())))
185        }
186    }
187
188    // ── Grammar productions ──────────────────────────────────────────────
189
190    /// OrExpr ::= AndExpr ('or' AndExpr)*
191    fn parse_or_expr(&mut self) -> Result<Expr, ParseError> {
192        let mut left = self.parse_and_expr()?;
193        while matches!(self.current(), Token::Or) {
194            self.advance();
195            let right = self.parse_and_expr()?;
196            left = Expr::BinaryOp {
197                op: BinaryOp::Or,
198                left: Box::new(left),
199                right: Box::new(right),
200            };
201        }
202        Ok(left)
203    }
204
205    /// AndExpr ::= EqualityExpr ('and' EqualityExpr)*
206    fn parse_and_expr(&mut self) -> Result<Expr, ParseError> {
207        let mut left = self.parse_equality_expr()?;
208        while matches!(self.current(), Token::And) {
209            self.advance();
210            let right = self.parse_equality_expr()?;
211            left = Expr::BinaryOp {
212                op: BinaryOp::And,
213                left: Box::new(left),
214                right: Box::new(right),
215            };
216        }
217        Ok(left)
218    }
219
220    /// EqualityExpr ::= RelationalExpr (('=' | '!=') RelationalExpr)*
221    fn parse_equality_expr(&mut self) -> Result<Expr, ParseError> {
222        let mut left = self.parse_relational_expr()?;
223        loop {
224            let op = match self.current() {
225                Token::Eq => BinaryOp::Eq,
226                Token::Ne => BinaryOp::Ne,
227                _ => break,
228            };
229            self.advance();
230            let right = self.parse_relational_expr()?;
231            left = Expr::BinaryOp {
232                op,
233                left: Box::new(left),
234                right: Box::new(right),
235            };
236        }
237        Ok(left)
238    }
239
240    /// RelationalExpr ::= AdditiveExpr (('<' | '>' | '<=' | '>=') AdditiveExpr)*
241    fn parse_relational_expr(&mut self) -> Result<Expr, ParseError> {
242        let mut left = self.parse_additive_expr()?;
243        loop {
244            let op = match self.current() {
245                Token::Lt => BinaryOp::Lt,
246                Token::Gt => BinaryOp::Gt,
247                Token::Le => BinaryOp::Le,
248                Token::Ge => BinaryOp::Ge,
249                _ => break,
250            };
251            self.advance();
252            let right = self.parse_additive_expr()?;
253            left = Expr::BinaryOp {
254                op,
255                left: Box::new(left),
256                right: Box::new(right),
257            };
258        }
259        Ok(left)
260    }
261
262    /// AdditiveExpr ::= MultiplicativeExpr (('+' | '-') MultiplicativeExpr)*
263    fn parse_additive_expr(&mut self) -> Result<Expr, ParseError> {
264        let mut left = self.parse_multiplicative_expr()?;
265        loop {
266            let op = match self.current() {
267                Token::Plus => BinaryOp::Add,
268                Token::Minus => BinaryOp::Sub,
269                _ => break,
270            };
271            self.advance();
272            let right = self.parse_multiplicative_expr()?;
273            left = Expr::BinaryOp {
274                op,
275                left: Box::new(left),
276                right: Box::new(right),
277            };
278        }
279        Ok(left)
280    }
281
282    /// MultiplicativeExpr ::= UnaryExpr (('*' | 'div' | 'mod') UnaryExpr)*
283    fn parse_multiplicative_expr(&mut self) -> Result<Expr, ParseError> {
284        let mut left = self.parse_unary_expr()?;
285        loop {
286            let op = match self.current() {
287                // '*' after an expression is multiply, not wildcard
288                Token::Star => BinaryOp::Mul,
289                Token::Div => BinaryOp::Div,
290                Token::Mod => BinaryOp::Mod,
291                _ => break,
292            };
293            self.advance();
294            let right = self.parse_unary_expr()?;
295            left = Expr::BinaryOp {
296                op,
297                left: Box::new(left),
298                right: Box::new(right),
299            };
300        }
301        Ok(left)
302    }
303
304    /// UnaryExpr ::= '-'* UnionExpr
305    fn parse_unary_expr(&mut self) -> Result<Expr, ParseError> {
306        let mut minus_count = 0;
307        while matches!(self.current(), Token::Minus) {
308            self.advance();
309            minus_count += 1;
310        }
311        let mut expr = self.parse_union_expr()?;
312        if minus_count % 2 == 1 {
313            expr = Expr::UnaryMinus(Box::new(expr));
314        }
315        Ok(expr)
316    }
317
318    /// UnionExpr ::= PathExpr ('|' PathExpr)*
319    fn parse_union_expr(&mut self) -> Result<Expr, ParseError> {
320        let mut left = self.parse_path_expr()?;
321        while matches!(self.current(), Token::Pipe) {
322            self.advance();
323            let right = self.parse_path_expr()?;
324            left = Expr::Union(Box::new(left), Box::new(right));
325        }
326        Ok(left)
327    }
328
329    /// PathExpr ::= LocationPath | FilterExpr (('/' | '//') RelativeLocationPath)?
330    fn parse_path_expr(&mut self) -> Result<Expr, ParseError> {
331        // Check if it starts with a location path
332        if self.is_location_path_start() {
333            return self.parse_location_path();
334        }
335
336        // Otherwise it's a FilterExpr (primary with optional predicates and path)
337        let mut expr = self.parse_filter_expr()?;
338
339        // Optional / or // followed by relative location path
340        loop {
341            match self.current() {
342                Token::Slash => {
343                    self.advance();
344                    let step = self.parse_relative_location_path()?;
345                    expr = Expr::RelativePath(Box::new(expr), Box::new(step));
346                }
347                Token::DoubleSlash => {
348                    self.advance();
349                    let step = self.parse_relative_location_path()?;
350                    // // is shorthand for /descendant-or-self::node()/
351                    let descendant = Expr::Step(Step {
352                        axis: Axis::DescendantOrSelf,
353                        node_test: NodeTest::Node,
354                        predicates: vec![],
355                    });
356                    let path = Expr::RelativePath(Box::new(descendant), Box::new(step));
357                    expr = Expr::RelativePath(Box::new(expr), Box::new(path));
358                }
359                _ => break,
360            }
361        }
362
363        Ok(expr)
364    }
365
366    /// Check if the current position starts a location path.
367    fn is_location_path_start(&self) -> bool {
368        match self.current() {
369            Token::Slash | Token::DoubleSlash => true,
370            Token::Dot | Token::DotDot => true,
371            Token::At => true,
372            Token::Star => true,
373            // Name that could be a step (not followed by '(' which means function call)
374            Token::Name(ref n) => {
375                // Check if this is followed by :: (axis), /, //, [, or is a simple step
376                let next = self.peek();
377                if matches!(next, Token::LParen) {
378                    // UPSTREAM-PARITY (xpath.c xmlXPathCompPathExpr): a name
379                    // followed by '(' is only a function call for
380                    // non-node-type names; node/text/comment/
381                    // processing-instruction are node-type tests that BEGIN a
382                    // location path (an expression-initial `node()` was
383                    // previously misparsed as a function call, making
384                    // apply-templates select="node()|@*" fail with
385                    // "Unregistered function: node").
386                    !n.contains(':')
387                        && matches!(
388                            n.as_str(),
389                            "node" | "text" | "comment" | "processing-instruction"
390                        )
391                } else {
392                    true
393                }
394            }
395            // Axis keywords
396            Token::Child
397            | Token::Descendant
398            | Token::DescendantOrSelf
399            | Token::Ancestor
400            | Token::AncestorOrSelf
401            | Token::Attribute
402            | Token::Following
403            | Token::FollowingSibling
404            | Token::Namespace
405            | Token::Parent
406            | Token::Preceding
407            | Token::PrecedingSibling
408            | Token::Self_ => true,
409            _ => false,
410        }
411    }
412
413    /// LocationPath ::= AbsoluteLocationPath | RelativeLocationPath
414    fn parse_location_path(&mut self) -> Result<Expr, ParseError> {
415        match self.current() {
416            Token::Slash => {
417                self.advance();
418                if self.is_location_path_start() {
419                    let path = self.parse_relative_location_path()?;
420                    Ok(Expr::AbsolutePath(Box::new(path)))
421                } else {
422                    // Just "/" - root node
423                    Ok(Expr::Step(Step {
424                        axis: Axis::Self_,
425                        node_test: NodeTest::Node,
426                        predicates: vec![],
427                    }))
428                }
429            }
430            Token::DoubleSlash => {
431                self.advance();
432                let path = self.parse_relative_location_path()?;
433                let descendant = Expr::Step(Step {
434                    axis: Axis::DescendantOrSelf,
435                    node_test: NodeTest::Node,
436                    predicates: vec![],
437                });
438                Ok(Expr::AbsolutePath(Box::new(Expr::RelativePath(
439                    Box::new(descendant),
440                    Box::new(path),
441                ))))
442            }
443            _ => self.parse_relative_location_path(),
444        }
445    }
446
447    /// RelativeLocationPath ::= Step (('/' | '//') Step)*
448    fn parse_relative_location_path(&mut self) -> Result<Expr, ParseError> {
449        let mut expr = self.parse_step()?;
450        loop {
451            match self.current() {
452                Token::Slash => {
453                    self.advance();
454                    let step = self.parse_step()?;
455                    expr = Expr::RelativePath(Box::new(expr), Box::new(step));
456                }
457                Token::DoubleSlash => {
458                    self.advance();
459                    let step = self.parse_step()?;
460                    let descendant = Expr::Step(Step {
461                        axis: Axis::DescendantOrSelf,
462                        node_test: NodeTest::Node,
463                        predicates: vec![],
464                    });
465                    let path = Expr::RelativePath(Box::new(descendant), Box::new(step));
466                    expr = Expr::RelativePath(Box::new(expr), Box::new(path));
467                }
468                _ => break,
469            }
470        }
471        Ok(expr)
472    }
473
474    /// Step ::= AxisSpecifier NodeTest Predicate*
475    ///        | AbbreviatedStep
476    fn parse_step(&mut self) -> Result<Expr, ParseError> {
477        // AbbreviatedStep ::= '.' | '..'
478        match self.current() {
479            Token::Dot => {
480                self.advance();
481                return Ok(Expr::Step(Step {
482                    axis: Axis::Self_,
483                    node_test: NodeTest::Node,
484                    predicates: vec![],
485                }));
486            }
487            Token::DotDot => {
488                self.advance();
489                return Ok(Expr::Step(Step {
490                    axis: Axis::Parent,
491                    node_test: NodeTest::Node,
492                    predicates: vec![],
493                }));
494            }
495            _ => {}
496        }
497
498        // Determine axis
499        let axis = self.parse_axis_specifier();
500
501        // Parse node test
502        let node_test = self.parse_node_test()?;
503
504        // Parse predicates
505        let mut predicates = Vec::new();
506        while matches!(self.current(), Token::LBracket) {
507            self.advance(); // consume '['
508            let pred = self.parse_or_expr()?;
509            self.expect(&Token::RBracket)?;
510            predicates.push(pred);
511        }
512
513        Ok(Expr::Step(Step {
514            axis,
515            node_test,
516            predicates,
517        }))
518    }
519
520    /// AxisSpecifier ::= AxisName '::' | '@'?
521    fn parse_axis_specifier(&mut self) -> Axis {
522        // Check for @ (attribute axis shorthand)
523        if matches!(self.current(), Token::At) {
524            self.advance();
525            return Axis::Attribute;
526        }
527
528        // Check for axis keyword followed by ::
529        // We check if the NEXT token is DoubleColon to decide whether this
530        // is an axis specifier or just a name being used as a node test.
531        let is_axis = match self.current() {
532            Token::Ancestor
533            | Token::AncestorOrSelf
534            | Token::Attribute
535            | Token::Child
536            | Token::Descendant
537            | Token::DescendantOrSelf
538            | Token::Following
539            | Token::FollowingSibling
540            | Token::Namespace
541            | Token::Parent
542            | Token::Preceding
543            | Token::PrecedingSibling
544            | Token::Self_ => matches!(self.peek(), Token::DoubleColon),
545            _ => false,
546        };
547
548        if is_axis {
549            let axis = match self.current() {
550                Token::Ancestor => Axis::Ancestor,
551                Token::AncestorOrSelf => Axis::AncestorOrSelf,
552                Token::Attribute => Axis::Attribute,
553                Token::Child => Axis::Child,
554                Token::Descendant => Axis::Descendant,
555                Token::DescendantOrSelf => Axis::DescendantOrSelf,
556                Token::Following => Axis::Following,
557                Token::FollowingSibling => Axis::FollowingSibling,
558                Token::Namespace => Axis::Namespace,
559                Token::Parent => Axis::Parent,
560                Token::Preceding => Axis::Preceding,
561                Token::PrecedingSibling => Axis::PrecedingSibling,
562                Token::Self_ => Axis::Self_,
563                _ => unreachable!(),
564            };
565            self.advance(); // consume axis keyword
566            self.advance(); // consume ::
567            return axis;
568        }
569
570        // Default axis is "child" for everything except attribute
571        Axis::Child
572    }
573
574    /// Convert a keyword token back to its string name for use as a node test.
575    fn token_to_name(&self, token: &Token) -> Option<String> {
576        match token {
577            Token::Name(ref s) => Some(s.clone()),
578            Token::Div => Some("div".to_string()),
579            Token::Mod => Some("mod".to_string()),
580            Token::And => Some("and".to_string()),
581            Token::Or => Some("or".to_string()),
582            Token::Ancestor => Some("ancestor".to_string()),
583            Token::AncestorOrSelf => Some("ancestor-or-self".to_string()),
584            Token::Attribute => Some("attribute".to_string()),
585            Token::Child => Some("child".to_string()),
586            Token::Descendant => Some("descendant".to_string()),
587            Token::DescendantOrSelf => Some("descendant-or-self".to_string()),
588            Token::Following => Some("following".to_string()),
589            Token::FollowingSibling => Some("following-sibling".to_string()),
590            Token::Namespace => Some("namespace".to_string()),
591            Token::Parent => Some("parent".to_string()),
592            Token::Preceding => Some("preceding".to_string()),
593            Token::PrecedingSibling => Some("preceding-sibling".to_string()),
594            Token::Self_ => Some("self".to_string()),
595            _ => None,
596        }
597    }
598
599    /// NodeTest ::= NameTest | 'comment()' | 'text()' | 'processing-instruction()' | 'node()'
600    /// NameTest ::= '*' | NCName ':' '*' | QName
601    fn parse_node_test(&mut self) -> Result<NodeTest, ParseError> {
602        // Try to get the current token as a potential name
603        let name_opt = self.token_to_name(&self.current());
604
605        match self.current() {
606            Token::Star => {
607                self.advance();
608                Ok(NodeTest::NameTest(NameTest::Any))
609            }
610            _ if name_opt.is_some() => {
611                let name = name_opt.unwrap();
612
613                // Check for function-style node tests: node(), text(), comment(), processing-instruction()
614                if matches!(self.peek(), Token::LParen) {
615                    match name.as_str() {
616                        "node" => {
617                            self.advance();
618                            self.advance();
619                            self.advance(); // name, (, )
620                            Ok(NodeTest::Node)
621                        }
622                        "text" => {
623                            self.advance();
624                            self.advance();
625                            self.advance();
626                            Ok(NodeTest::Text)
627                        }
628                        "comment" => {
629                            self.advance();
630                            self.advance();
631                            self.advance();
632                            Ok(NodeTest::Comment)
633                        }
634                        "processing-instruction" => {
635                            self.advance(); // name
636                            self.advance(); // (
637                                            // Check for optional string argument
638                            let target = if matches!(self.current(), Token::StringLiteral(_)) {
639                                if let Token::StringLiteral(s) = self.current() {
640                                    self.advance();
641                                    Some(s)
642                                } else {
643                                    None
644                                }
645                            } else {
646                                None
647                            };
648                            self.expect(&Token::RParen)?;
649                            Ok(NodeTest::ProcessingInstruction(target))
650                        }
651                        _ => {
652                            // Regular function call, not a node test
653                            self.advance(); // function name
654                            self.advance(); // (
655                            let mut args = Vec::new();
656                            if !matches!(self.current(), Token::RParen) {
657                                args.push(self.parse_or_expr()?);
658                                while matches!(self.current(), Token::Comma) {
659                                    self.advance();
660                                    args.push(self.parse_or_expr()?);
661                                }
662                            }
663                            self.expect(&Token::RParen)?;
664                            // Wrap in a step with a name test
665                            Ok(NodeTest::NameTest(NameTest::LocalName(name)))
666                        }
667                    }
668                } else {
669                    self.advance();
670                    // Check for prefix:*
671                    if let Some(rest) = name.strip_suffix(":*") {
672                        Ok(NodeTest::NsWildcard(rest.to_string()))
673                    } else if let Some((prefix, local)) = name.split_once(':') {
674                        Ok(NodeTest::NameTest(NameTest::QName {
675                            prefix: prefix.to_string(),
676                            local: local.to_string(),
677                        }))
678                    } else {
679                        Ok(NodeTest::NameTest(NameTest::LocalName(name)))
680                    }
681                }
682            }
683            _ => Err(self.error(format!("Expected node test, got {}", self.current()))),
684        }
685    }
686
687    /// FilterExpr ::= PrimaryExpr Predicate*
688    fn parse_filter_expr(&mut self) -> Result<Expr, ParseError> {
689        let primary = self.parse_primary_expr()?;
690
691        // Predicates after primary
692        let mut predicates = Vec::new();
693        while matches!(self.current(), Token::LBracket) {
694            self.advance(); // consume '['
695            let pred = self.parse_or_expr()?;
696            self.expect(&Token::RBracket)?;
697            predicates.push(pred);
698        }
699
700        if predicates.is_empty() {
701            Ok(primary)
702        } else {
703            Ok(Expr::Filter(Box::new(primary), predicates))
704        }
705    }
706
707    /// PrimaryExpr ::= VariableReference | '(' Expr ')' | Literal | Number | FunctionCall
708    fn parse_primary_expr(&mut self) -> Result<Expr, ParseError> {
709        match self.current() {
710            Token::Dollar => {
711                self.advance();
712                if let Token::Name(name) = self.current() {
713                    let name = name.clone();
714                    self.advance();
715                    Ok(Expr::Variable(name))
716                } else {
717                    Err(self.error("Expected variable name after $".to_string()))
718                }
719            }
720            Token::LParen => {
721                self.advance();
722                let expr = self.parse_or_expr()?;
723                self.expect(&Token::RParen)?;
724                Ok(expr)
725            }
726            Token::StringLiteral(ref s) => {
727                let s = s.clone();
728                self.advance();
729                Ok(Expr::StringLiteral(s))
730            }
731            Token::NumberLiteral(n) => {
732                self.advance();
733                Ok(Expr::NumberLiteral(n))
734            }
735            Token::Name(ref name) => {
736                let name = name.clone();
737                if matches!(self.peek(), Token::LParen) {
738                    // Function call
739                    self.advance(); // function name
740                    self.advance(); // (
741                    let mut args = Vec::new();
742                    if !matches!(self.current(), Token::RParen) {
743                        args.push(self.parse_or_expr()?);
744                        while matches!(self.current(), Token::Comma) {
745                            self.advance();
746                            args.push(self.parse_or_expr()?);
747                        }
748                    }
749                    self.expect(&Token::RParen)?;
750                    Ok(Expr::FunctionCall { name, args })
751                } else {
752                    // Standalone name - could be a step or something else
753                    // But at the primary level, this shouldn't happen
754                    Err(self.error(format!("Unexpected name '{}' in primary expression", name)))
755                }
756            }
757            _ => Err(self.error(format!(
758                "Expected primary expression, got {}",
759                self.current()
760            ))),
761        }
762    }
763}
764
765// ═══════════════════════════════════════════════════════════════════════════════
766// Convenience function
767// ═══════════════════════════════════════════════════════════════════════════════
768
769/// Parse an XPath expression string into an AST.
770pub fn parse_xpath(input: &str) -> Result<Expr, ParseError> {
771    let mut lexer = crate::xml::xpath::lexer::Lexer::new(input);
772    let mut tokens = Vec::new();
773    loop {
774        let tok = lexer.next_token();
775        let is_eof = matches!(tok, Token::Eof);
776        tokens.push(tok);
777        if is_eof {
778            break;
779        }
780    }
781    let starts = lexer.token_starts();
782    let mut parser = Parser::new(tokens);
783    parser.parse().map_err(|e| {
784        // UPSTREAM-PARITY (xpath.c xmlXPathErrFmt): the recorded error
785        // position is the BYTE offset into the expression (`int1 =
786        // ctxt->cur - ctxt->base`), not the token index — it drives the
787        // caret of the "XPath error : Invalid expression" diagnostic
788        // (HOSTILE-FAILURE F3).
789        let byte_off = starts
790            .get(e.pos)
791            .copied()
792            .unwrap_or(input.len())
793            .min(input.len());
794        ParseError {
795            message: e.message,
796            pos: byte_off,
797        }
798    })
799}
800
801// ═══════════════════════════════════════════════════════════════════════════════
802// Tests
803// ═══════════════════════════════════════════════════════════════════════════════
804
805#[cfg(test)]
806mod tests {
807    use super::*;
808
809    #[test]
810    fn test_parse_simple_name() {
811        let expr = parse_xpath("para").unwrap();
812        assert!(matches!(expr, Expr::Step(_)));
813    }
814
815    #[test]
816    fn test_parse_absolute_path() {
817        let expr = parse_xpath("/child::para").unwrap();
818        assert!(matches!(expr, Expr::AbsolutePath(_)));
819    }
820
821    #[test]
822    fn test_parse_attribute() {
823        let expr = parse_xpath("@attr").unwrap();
824        assert!(matches!(expr, Expr::Step(_)));
825    }
826
827    #[test]
828    fn test_parse_predicate() {
829        let expr = parse_xpath("para[1]").unwrap();
830        assert!(matches!(expr, Expr::Step(_)));
831    }
832
833    #[test]
834    fn test_parse_function_call() {
835        let expr = parse_xpath("position()").unwrap();
836        assert!(matches!(expr, Expr::FunctionCall { .. }));
837    }
838
839    #[test]
840    fn test_parse_binary_op() {
841        let expr = parse_xpath("a = b").unwrap();
842        assert!(matches!(
843            expr,
844            Expr::BinaryOp {
845                op: BinaryOp::Eq,
846                ..
847            }
848        ));
849    }
850
851    #[test]
852    fn test_parse_union() {
853        let expr = parse_xpath("a | b").unwrap();
854        assert!(matches!(expr, Expr::Union(_, _)));
855    }
856
857    #[test]
858    fn test_parse_variable() {
859        let expr = parse_xpath("$var").unwrap();
860        assert!(matches!(expr, Expr::Variable(_)));
861    }
862
863    #[test]
864    fn test_parse_string_literal() {
865        let expr = parse_xpath("'hello'").unwrap();
866        assert_eq!(expr, Expr::StringLiteral("hello".to_string()));
867    }
868
869    #[test]
870    fn test_parse_number() {
871        let expr = parse_xpath("42").unwrap();
872        assert_eq!(expr, Expr::NumberLiteral(42.0));
873    }
874
875    #[test]
876    fn test_parse_nested_expression() {
877        let expr = parse_xpath("(1 + 2) * 3").unwrap();
878        assert!(matches!(
879            expr,
880            Expr::BinaryOp {
881                op: BinaryOp::Mul,
882                ..
883            }
884        ));
885    }
886
887    #[test]
888    fn test_parse_chained_path() {
889        let expr = parse_xpath("a/b/c").unwrap();
890        assert!(matches!(expr, Expr::RelativePath(_, _)));
891    }
892
893    #[test]
894    fn test_parse_double_slash() {
895        let expr = parse_xpath("//para").unwrap();
896        assert!(matches!(expr, Expr::AbsolutePath(_)));
897    }
898
899    #[test]
900    fn test_parse_dot() {
901        let expr = parse_xpath(".").unwrap();
902        assert!(matches!(expr, Expr::Step(_)));
903    }
904
905    #[test]
906    fn test_parse_dot_dot() {
907        let expr = parse_xpath("..").unwrap();
908        assert!(matches!(expr, Expr::Step(_)));
909    }
910
911    #[test]
912    fn test_parse_unary_minus() {
913        let expr = parse_xpath("-5").unwrap();
914        assert!(matches!(expr, Expr::UnaryMinus(_)));
915    }
916
917    #[test]
918    fn test_parse_double_unary_minus() {
919        let expr = parse_xpath("--5").unwrap();
920        // Should cancel out
921        assert!(!matches!(expr, Expr::UnaryMinus(_)));
922    }
923
924    #[test]
925    fn test_parse_complex_expression() {
926        let expr = parse_xpath("/html/body//div[@class='main']/p[1]").unwrap();
927        assert!(matches!(expr, Expr::AbsolutePath(_)));
928    }
929
930    #[test]
931    fn test_parse_error() {
932        let result = parse_xpath("(");
933        assert!(result.is_err());
934    }
935
936    #[test]
937    fn test_parse_empty() {
938        let result = parse_xpath("");
939        assert!(result.is_err());
940    }
941
942    #[test]
943    fn test_parse_and_or() {
944        let expr = parse_xpath("a = 1 and b = 2 or c = 3").unwrap();
945        assert!(matches!(expr, Expr::BinaryOp { .. }));
946    }
947
948    #[test]
949    fn test_parse_comparison_chain() {
950        let expr = parse_xpath("a < b <= c > d >= e").unwrap();
951        // Should parse as: ((((a < b) <= c) > d) >= e)
952        assert!(matches!(
953            expr,
954            Expr::BinaryOp {
955                op: BinaryOp::Ge,
956                ..
957            }
958        ));
959    }
960
961    #[test]
962    fn test_parse_arithmetic() {
963        let expr = parse_xpath("1 + 2 * 3").unwrap();
964        // 2 * 3 should bind tighter: 1 + (2 * 3)
965        match expr {
966            Expr::BinaryOp {
967                op: BinaryOp::Add,
968                left,
969                right,
970            } => {
971                assert!(matches!(*left, Expr::NumberLiteral(1.0)));
972                assert!(matches!(
973                    *right,
974                    Expr::BinaryOp {
975                        op: BinaryOp::Mul,
976                        ..
977                    }
978                ));
979            }
980            _ => panic!("Expected Add expression"),
981        }
982    }
983
984    #[test]
985    fn test_parse_filter_path() {
986        let expr = parse_xpath("//div/span").unwrap();
987        assert!(matches!(expr, Expr::AbsolutePath(_)));
988    }
989
990    #[test]
991    fn test_parse_node_test_functions() {
992        let expr = parse_xpath("child::node()").unwrap();
993        assert!(matches!(expr, Expr::Step(_)));
994        if let Expr::Step(step) = expr {
995            assert_eq!(step.node_test, NodeTest::Node);
996        }
997    }
998}