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