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(_) => {
375                // Check if this is followed by :: (axis), /, //, [, or is a simple step
376                let next = self.peek();
377                !matches!(next, Token::LParen)
378            }
379            // Axis keywords
380            Token::Child
381            | Token::Descendant
382            | Token::DescendantOrSelf
383            | Token::Ancestor
384            | Token::AncestorOrSelf
385            | Token::Attribute
386            | Token::Following
387            | Token::FollowingSibling
388            | Token::Namespace
389            | Token::Parent
390            | Token::Preceding
391            | Token::PrecedingSibling
392            | Token::Self_ => true,
393            _ => false,
394        }
395    }
396
397    /// LocationPath ::= AbsoluteLocationPath | RelativeLocationPath
398    fn parse_location_path(&mut self) -> Result<Expr, ParseError> {
399        match self.current() {
400            Token::Slash => {
401                self.advance();
402                if self.is_location_path_start() {
403                    let path = self.parse_relative_location_path()?;
404                    Ok(Expr::AbsolutePath(Box::new(path)))
405                } else {
406                    // Just "/" - root node
407                    Ok(Expr::Step(Step {
408                        axis: Axis::Self_,
409                        node_test: NodeTest::Node,
410                        predicates: vec![],
411                    }))
412                }
413            }
414            Token::DoubleSlash => {
415                self.advance();
416                let path = self.parse_relative_location_path()?;
417                let descendant = Expr::Step(Step {
418                    axis: Axis::DescendantOrSelf,
419                    node_test: NodeTest::Node,
420                    predicates: vec![],
421                });
422                Ok(Expr::AbsolutePath(Box::new(Expr::RelativePath(
423                    Box::new(descendant),
424                    Box::new(path),
425                ))))
426            }
427            _ => self.parse_relative_location_path(),
428        }
429    }
430
431    /// RelativeLocationPath ::= Step (('/' | '//') Step)*
432    fn parse_relative_location_path(&mut self) -> Result<Expr, ParseError> {
433        let mut expr = self.parse_step()?;
434        loop {
435            match self.current() {
436                Token::Slash => {
437                    self.advance();
438                    let step = self.parse_step()?;
439                    expr = Expr::RelativePath(Box::new(expr), Box::new(step));
440                }
441                Token::DoubleSlash => {
442                    self.advance();
443                    let step = self.parse_step()?;
444                    let descendant = Expr::Step(Step {
445                        axis: Axis::DescendantOrSelf,
446                        node_test: NodeTest::Node,
447                        predicates: vec![],
448                    });
449                    let path = Expr::RelativePath(Box::new(descendant), Box::new(step));
450                    expr = Expr::RelativePath(Box::new(expr), Box::new(path));
451                }
452                _ => break,
453            }
454        }
455        Ok(expr)
456    }
457
458    /// Step ::= AxisSpecifier NodeTest Predicate*
459    ///        | AbbreviatedStep
460    fn parse_step(&mut self) -> Result<Expr, ParseError> {
461        // AbbreviatedStep ::= '.' | '..'
462        match self.current() {
463            Token::Dot => {
464                self.advance();
465                return Ok(Expr::Step(Step {
466                    axis: Axis::Self_,
467                    node_test: NodeTest::Node,
468                    predicates: vec![],
469                }));
470            }
471            Token::DotDot => {
472                self.advance();
473                return Ok(Expr::Step(Step {
474                    axis: Axis::Parent,
475                    node_test: NodeTest::Node,
476                    predicates: vec![],
477                }));
478            }
479            _ => {}
480        }
481
482        // Determine axis
483        let axis = self.parse_axis_specifier();
484
485        // Parse node test
486        let node_test = self.parse_node_test()?;
487
488        // Parse predicates
489        let mut predicates = Vec::new();
490        while matches!(self.current(), Token::LBracket) {
491            self.advance(); // consume '['
492            let pred = self.parse_or_expr()?;
493            self.expect(&Token::RBracket)?;
494            predicates.push(pred);
495        }
496
497        Ok(Expr::Step(Step {
498            axis,
499            node_test,
500            predicates,
501        }))
502    }
503
504    /// AxisSpecifier ::= AxisName '::' | '@'?
505    fn parse_axis_specifier(&mut self) -> Axis {
506        // Check for @ (attribute axis shorthand)
507        if matches!(self.current(), Token::At) {
508            self.advance();
509            return Axis::Attribute;
510        }
511
512        // Check for axis keyword followed by ::
513        // We check if the NEXT token is DoubleColon to decide whether this
514        // is an axis specifier or just a name being used as a node test.
515        let is_axis = match self.current() {
516            Token::Ancestor
517            | Token::AncestorOrSelf
518            | Token::Attribute
519            | Token::Child
520            | Token::Descendant
521            | Token::DescendantOrSelf
522            | Token::Following
523            | Token::FollowingSibling
524            | Token::Namespace
525            | Token::Parent
526            | Token::Preceding
527            | Token::PrecedingSibling
528            | Token::Self_ => matches!(self.peek(), Token::DoubleColon),
529            _ => false,
530        };
531
532        if is_axis {
533            let axis = match self.current() {
534                Token::Ancestor => Axis::Ancestor,
535                Token::AncestorOrSelf => Axis::AncestorOrSelf,
536                Token::Attribute => Axis::Attribute,
537                Token::Child => Axis::Child,
538                Token::Descendant => Axis::Descendant,
539                Token::DescendantOrSelf => Axis::DescendantOrSelf,
540                Token::Following => Axis::Following,
541                Token::FollowingSibling => Axis::FollowingSibling,
542                Token::Namespace => Axis::Namespace,
543                Token::Parent => Axis::Parent,
544                Token::Preceding => Axis::Preceding,
545                Token::PrecedingSibling => Axis::PrecedingSibling,
546                Token::Self_ => Axis::Self_,
547                _ => unreachable!(),
548            };
549            self.advance(); // consume axis keyword
550            self.advance(); // consume ::
551            return axis;
552        }
553
554        // Default axis is "child" for everything except attribute
555        Axis::Child
556    }
557
558    /// Convert a keyword token back to its string name for use as a node test.
559    fn token_to_name(&self, token: &Token) -> Option<String> {
560        match token {
561            Token::Name(ref s) => Some(s.clone()),
562            Token::Div => Some("div".to_string()),
563            Token::Mod => Some("mod".to_string()),
564            Token::And => Some("and".to_string()),
565            Token::Or => Some("or".to_string()),
566            Token::Ancestor => Some("ancestor".to_string()),
567            Token::AncestorOrSelf => Some("ancestor-or-self".to_string()),
568            Token::Attribute => Some("attribute".to_string()),
569            Token::Child => Some("child".to_string()),
570            Token::Descendant => Some("descendant".to_string()),
571            Token::DescendantOrSelf => Some("descendant-or-self".to_string()),
572            Token::Following => Some("following".to_string()),
573            Token::FollowingSibling => Some("following-sibling".to_string()),
574            Token::Namespace => Some("namespace".to_string()),
575            Token::Parent => Some("parent".to_string()),
576            Token::Preceding => Some("preceding".to_string()),
577            Token::PrecedingSibling => Some("preceding-sibling".to_string()),
578            Token::Self_ => Some("self".to_string()),
579            _ => None,
580        }
581    }
582
583    /// NodeTest ::= NameTest | 'comment()' | 'text()' | 'processing-instruction()' | 'node()'
584    /// NameTest ::= '*' | NCName ':' '*' | QName
585    fn parse_node_test(&mut self) -> Result<NodeTest, ParseError> {
586        // Try to get the current token as a potential name
587        let name_opt = self.token_to_name(&self.current());
588
589        match self.current() {
590            Token::Star => {
591                self.advance();
592                Ok(NodeTest::NameTest(NameTest::Any))
593            }
594            _ if name_opt.is_some() => {
595                let name = name_opt.unwrap();
596
597                // Check for function-style node tests: node(), text(), comment(), processing-instruction()
598                if matches!(self.peek(), Token::LParen) {
599                    match name.as_str() {
600                        "node" => {
601                            self.advance();
602                            self.advance();
603                            self.advance(); // name, (, )
604                            Ok(NodeTest::Node)
605                        }
606                        "text" => {
607                            self.advance();
608                            self.advance();
609                            self.advance();
610                            Ok(NodeTest::Text)
611                        }
612                        "comment" => {
613                            self.advance();
614                            self.advance();
615                            self.advance();
616                            Ok(NodeTest::Comment)
617                        }
618                        "processing-instruction" => {
619                            self.advance(); // name
620                            self.advance(); // (
621                                            // Check for optional string argument
622                            let target = if matches!(self.current(), Token::StringLiteral(_)) {
623                                if let Token::StringLiteral(s) = self.current() {
624                                    self.advance();
625                                    Some(s)
626                                } else {
627                                    None
628                                }
629                            } else {
630                                None
631                            };
632                            self.expect(&Token::RParen)?;
633                            Ok(NodeTest::ProcessingInstruction(target))
634                        }
635                        _ => {
636                            // Regular function call, not a node test
637                            self.advance(); // function name
638                            self.advance(); // (
639                            let mut args = Vec::new();
640                            if !matches!(self.current(), Token::RParen) {
641                                args.push(self.parse_or_expr()?);
642                                while matches!(self.current(), Token::Comma) {
643                                    self.advance();
644                                    args.push(self.parse_or_expr()?);
645                                }
646                            }
647                            self.expect(&Token::RParen)?;
648                            // Wrap in a step with a name test
649                            Ok(NodeTest::NameTest(NameTest::LocalName(name)))
650                        }
651                    }
652                } else {
653                    self.advance();
654                    // Check for prefix:*
655                    if let Some(rest) = name.strip_suffix(":*") {
656                        Ok(NodeTest::NsWildcard(rest.to_string()))
657                    } else if let Some((prefix, local)) = name.split_once(':') {
658                        Ok(NodeTest::NameTest(NameTest::QName {
659                            prefix: prefix.to_string(),
660                            local: local.to_string(),
661                        }))
662                    } else {
663                        Ok(NodeTest::NameTest(NameTest::LocalName(name)))
664                    }
665                }
666            }
667            _ => Err(self.error(format!("Expected node test, got {}", self.current()))),
668        }
669    }
670
671    /// FilterExpr ::= PrimaryExpr Predicate*
672    fn parse_filter_expr(&mut self) -> Result<Expr, ParseError> {
673        let primary = self.parse_primary_expr()?;
674
675        // Predicates after primary
676        let mut predicates = Vec::new();
677        while matches!(self.current(), Token::LBracket) {
678            self.advance(); // consume '['
679            let pred = self.parse_or_expr()?;
680            self.expect(&Token::RBracket)?;
681            predicates.push(pred);
682        }
683
684        if predicates.is_empty() {
685            Ok(primary)
686        } else {
687            Ok(Expr::Filter(Box::new(primary), predicates))
688        }
689    }
690
691    /// PrimaryExpr ::= VariableReference | '(' Expr ')' | Literal | Number | FunctionCall
692    fn parse_primary_expr(&mut self) -> Result<Expr, ParseError> {
693        match self.current() {
694            Token::Dollar => {
695                self.advance();
696                if let Token::Name(name) = self.current() {
697                    let name = name.clone();
698                    self.advance();
699                    Ok(Expr::Variable(name))
700                } else {
701                    Err(self.error("Expected variable name after $".to_string()))
702                }
703            }
704            Token::LParen => {
705                self.advance();
706                let expr = self.parse_or_expr()?;
707                self.expect(&Token::RParen)?;
708                Ok(expr)
709            }
710            Token::StringLiteral(ref s) => {
711                let s = s.clone();
712                self.advance();
713                Ok(Expr::StringLiteral(s))
714            }
715            Token::NumberLiteral(n) => {
716                self.advance();
717                Ok(Expr::NumberLiteral(n))
718            }
719            Token::Name(ref name) => {
720                let name = name.clone();
721                if matches!(self.peek(), Token::LParen) {
722                    // Function call
723                    self.advance(); // function name
724                    self.advance(); // (
725                    let mut args = Vec::new();
726                    if !matches!(self.current(), Token::RParen) {
727                        args.push(self.parse_or_expr()?);
728                        while matches!(self.current(), Token::Comma) {
729                            self.advance();
730                            args.push(self.parse_or_expr()?);
731                        }
732                    }
733                    self.expect(&Token::RParen)?;
734                    Ok(Expr::FunctionCall { name, args })
735                } else {
736                    // Standalone name - could be a step or something else
737                    // But at the primary level, this shouldn't happen
738                    Err(self.error(format!("Unexpected name '{}' in primary expression", name)))
739                }
740            }
741            _ => Err(self.error(format!(
742                "Expected primary expression, got {}",
743                self.current()
744            ))),
745        }
746    }
747}
748
749// ═══════════════════════════════════════════════════════════════════════════════
750// Convenience function
751// ═══════════════════════════════════════════════════════════════════════════════
752
753/// Parse an XPath expression string into an AST.
754pub fn parse_xpath(input: &str) -> Result<Expr, ParseError> {
755    let mut lexer = crate::xml::xpath::lexer::Lexer::new(input);
756    let mut tokens = Vec::new();
757    loop {
758        let tok = lexer.next_token();
759        let is_eof = matches!(tok, Token::Eof);
760        tokens.push(tok);
761        if is_eof {
762            break;
763        }
764    }
765    let starts = lexer.token_starts();
766    let mut parser = Parser::new(tokens);
767    parser.parse().map_err(|e| {
768        // UPSTREAM-PARITY (xpath.c xmlXPathErrFmt): the recorded error
769        // position is the BYTE offset into the expression (`int1 =
770        // ctxt->cur - ctxt->base`), not the token index — it drives the
771        // caret of the "XPath error : Invalid expression" diagnostic
772        // (HOSTILE-FAILURE F3).
773        let byte_off = starts
774            .get(e.pos)
775            .copied()
776            .unwrap_or(input.len())
777            .min(input.len());
778        ParseError {
779            message: e.message,
780            pos: byte_off,
781        }
782    })
783}
784
785// ═══════════════════════════════════════════════════════════════════════════════
786// Tests
787// ═══════════════════════════════════════════════════════════════════════════════
788
789#[cfg(test)]
790mod tests {
791    use super::*;
792
793    #[test]
794    fn test_parse_simple_name() {
795        let expr = parse_xpath("para").unwrap();
796        assert!(matches!(expr, Expr::Step(_)));
797    }
798
799    #[test]
800    fn test_parse_absolute_path() {
801        let expr = parse_xpath("/child::para").unwrap();
802        assert!(matches!(expr, Expr::AbsolutePath(_)));
803    }
804
805    #[test]
806    fn test_parse_attribute() {
807        let expr = parse_xpath("@attr").unwrap();
808        assert!(matches!(expr, Expr::Step(_)));
809    }
810
811    #[test]
812    fn test_parse_predicate() {
813        let expr = parse_xpath("para[1]").unwrap();
814        assert!(matches!(expr, Expr::Step(_)));
815    }
816
817    #[test]
818    fn test_parse_function_call() {
819        let expr = parse_xpath("position()").unwrap();
820        assert!(matches!(expr, Expr::FunctionCall { .. }));
821    }
822
823    #[test]
824    fn test_parse_binary_op() {
825        let expr = parse_xpath("a = b").unwrap();
826        assert!(matches!(
827            expr,
828            Expr::BinaryOp {
829                op: BinaryOp::Eq,
830                ..
831            }
832        ));
833    }
834
835    #[test]
836    fn test_parse_union() {
837        let expr = parse_xpath("a | b").unwrap();
838        assert!(matches!(expr, Expr::Union(_, _)));
839    }
840
841    #[test]
842    fn test_parse_variable() {
843        let expr = parse_xpath("$var").unwrap();
844        assert!(matches!(expr, Expr::Variable(_)));
845    }
846
847    #[test]
848    fn test_parse_string_literal() {
849        let expr = parse_xpath("'hello'").unwrap();
850        assert_eq!(expr, Expr::StringLiteral("hello".to_string()));
851    }
852
853    #[test]
854    fn test_parse_number() {
855        let expr = parse_xpath("42").unwrap();
856        assert_eq!(expr, Expr::NumberLiteral(42.0));
857    }
858
859    #[test]
860    fn test_parse_nested_expression() {
861        let expr = parse_xpath("(1 + 2) * 3").unwrap();
862        assert!(matches!(
863            expr,
864            Expr::BinaryOp {
865                op: BinaryOp::Mul,
866                ..
867            }
868        ));
869    }
870
871    #[test]
872    fn test_parse_chained_path() {
873        let expr = parse_xpath("a/b/c").unwrap();
874        assert!(matches!(expr, Expr::RelativePath(_, _)));
875    }
876
877    #[test]
878    fn test_parse_double_slash() {
879        let expr = parse_xpath("//para").unwrap();
880        assert!(matches!(expr, Expr::AbsolutePath(_)));
881    }
882
883    #[test]
884    fn test_parse_dot() {
885        let expr = parse_xpath(".").unwrap();
886        assert!(matches!(expr, Expr::Step(_)));
887    }
888
889    #[test]
890    fn test_parse_dot_dot() {
891        let expr = parse_xpath("..").unwrap();
892        assert!(matches!(expr, Expr::Step(_)));
893    }
894
895    #[test]
896    fn test_parse_unary_minus() {
897        let expr = parse_xpath("-5").unwrap();
898        assert!(matches!(expr, Expr::UnaryMinus(_)));
899    }
900
901    #[test]
902    fn test_parse_double_unary_minus() {
903        let expr = parse_xpath("--5").unwrap();
904        // Should cancel out
905        assert!(!matches!(expr, Expr::UnaryMinus(_)));
906    }
907
908    #[test]
909    fn test_parse_complex_expression() {
910        let expr = parse_xpath("/html/body//div[@class='main']/p[1]").unwrap();
911        assert!(matches!(expr, Expr::AbsolutePath(_)));
912    }
913
914    #[test]
915    fn test_parse_error() {
916        let result = parse_xpath("(");
917        assert!(result.is_err());
918    }
919
920    #[test]
921    fn test_parse_empty() {
922        let result = parse_xpath("");
923        assert!(result.is_err());
924    }
925
926    #[test]
927    fn test_parse_and_or() {
928        let expr = parse_xpath("a = 1 and b = 2 or c = 3").unwrap();
929        assert!(matches!(expr, Expr::BinaryOp { .. }));
930    }
931
932    #[test]
933    fn test_parse_comparison_chain() {
934        let expr = parse_xpath("a < b <= c > d >= e").unwrap();
935        // Should parse as: ((((a < b) <= c) > d) >= e)
936        assert!(matches!(
937            expr,
938            Expr::BinaryOp {
939                op: BinaryOp::Ge,
940                ..
941            }
942        ));
943    }
944
945    #[test]
946    fn test_parse_arithmetic() {
947        let expr = parse_xpath("1 + 2 * 3").unwrap();
948        // 2 * 3 should bind tighter: 1 + (2 * 3)
949        match expr {
950            Expr::BinaryOp {
951                op: BinaryOp::Add,
952                left,
953                right,
954            } => {
955                assert!(matches!(*left, Expr::NumberLiteral(1.0)));
956                assert!(matches!(
957                    *right,
958                    Expr::BinaryOp {
959                        op: BinaryOp::Mul,
960                        ..
961                    }
962                ));
963            }
964            _ => panic!("Expected Add expression"),
965        }
966    }
967
968    #[test]
969    fn test_parse_filter_path() {
970        let expr = parse_xpath("//div/span").unwrap();
971        assert!(matches!(expr, Expr::AbsolutePath(_)));
972    }
973
974    #[test]
975    fn test_parse_node_test_functions() {
976        let expr = parse_xpath("child::node()").unwrap();
977        assert!(matches!(expr, Expr::Step(_)));
978        if let Expr::Step(step) = expr {
979            assert_eq!(step.node_test, NodeTest::Node);
980        }
981    }
982}