Skip to main content

xpath_eval/
parser.rs

1//! Recursive-descent parser over the XPath 1.0 operator-precedence chain
2//! ([14],[18],[21]-[27]: Or → And → Equality → Relational → Additive →
3//! Multiplicative → Unary → Union → Path → Filter → Primary), plus
4//! `LocationPath`/`Step`/`Predicate` parsing ([1]-[13]).
5//!
6//! Function names are accepted purely syntactically (name + argument
7//! list) — they are not checked against the core function library here;
8//! that is Phase 04 (evaluation).
9
10use crate::ast::{
11    AdditiveOp, Axis, EqualityOp, Expr, FilterExpr, FunctionCall, LocationPath, MultiplicativeOp,
12    NodeTest, PathExpr, PrimaryExpr, QName, RelationalOp, Step,
13};
14use crate::lexer::{Lexer, Token};
15
16/// A structured parse error: a byte offset into the input expression plus a
17/// human-readable description (not a bare string error).
18#[derive(Debug, Clone, PartialEq)]
19pub struct ParseError {
20    pub position: usize,
21    pub message: String,
22}
23
24impl ParseError {
25    pub(crate) fn new(position: usize, message: impl Into<String>) -> Self {
26        ParseError {
27            position,
28            message: message.into(),
29        }
30    }
31}
32
33impl std::fmt::Display for ParseError {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        write!(f, "parse error at byte {}: {}", self.position, self.message)
36    }
37}
38
39impl std::error::Error for ParseError {}
40
41const NODE_TYPES: [&str; 4] = ["comment", "text", "processing-instruction", "node"];
42
43fn desc_or_self_step() -> Step {
44    Step {
45        axis: Axis::DescendantOrSelf,
46        node_test: NodeTest::Node,
47        predicates: Vec::new(),
48    }
49}
50
51/// Parses an XPath 1.0 expression string into a structured [`Expr`] AST.
52pub fn parse(expr: &str) -> Result<Expr, ParseError> {
53    let tokens = Lexer::tokenize(expr)?;
54    if tokens.is_empty() {
55        return Err(ParseError::new(0, "empty expression"));
56    }
57    let end = expr.len();
58    let mut parser = Parser {
59        tokens,
60        pos: 0,
61        end,
62    };
63    let result = parser.parse_expr()?;
64    if parser.pos != parser.tokens.len() {
65        let p = parser.current_pos();
66        return Err(ParseError::new(p, "unexpected trailing input"));
67    }
68    Ok(result)
69}
70
71struct Parser {
72    tokens: Vec<(Token, usize)>,
73    pos: usize,
74    end: usize,
75}
76
77impl Parser {
78    fn peek(&self) -> Option<&Token> {
79        self.tokens.get(self.pos).map(|(t, _)| t)
80    }
81
82    fn peek_at(&self, offset: usize) -> Option<&Token> {
83        self.tokens.get(self.pos + offset).map(|(t, _)| t)
84    }
85
86    fn current_pos(&self) -> usize {
87        self.tokens
88            .get(self.pos)
89            .map(|(_, p)| *p)
90            .unwrap_or(self.end)
91    }
92
93    fn advance(&mut self) -> Token {
94        let (t, _) = self.tokens[self.pos].clone();
95        self.pos += 1;
96        t
97    }
98
99    fn expect(&mut self, tok: Token, what: &str) -> Result<(), ParseError> {
100        match self.peek() {
101            Some(t) if *t == tok => {
102                self.pos += 1;
103                Ok(())
104            }
105            Some(_) => Err(ParseError::new(
106                self.current_pos(),
107                format!("expected {what}"),
108            )),
109            None => Err(self.eof_error(what)),
110        }
111    }
112
113    fn eof_error(&self, what: &str) -> ParseError {
114        ParseError::new(
115            self.end,
116            format!("unexpected end of input, expected {what}"),
117        )
118    }
119
120    // ---- Operator-precedence chain ---------------------------------
121
122    fn parse_expr(&mut self) -> Result<Expr, ParseError> {
123        self.parse_or()
124    }
125
126    fn parse_or(&mut self) -> Result<Expr, ParseError> {
127        let mut lhs = self.parse_and()?;
128        while matches!(self.peek(), Some(Token::Or)) {
129            self.advance();
130            let rhs = self.parse_and()?;
131            lhs = Expr::Or(Box::new(lhs), Box::new(rhs));
132        }
133        Ok(lhs)
134    }
135
136    fn parse_and(&mut self) -> Result<Expr, ParseError> {
137        let mut lhs = self.parse_equality()?;
138        while matches!(self.peek(), Some(Token::And)) {
139            self.advance();
140            let rhs = self.parse_equality()?;
141            lhs = Expr::And(Box::new(lhs), Box::new(rhs));
142        }
143        Ok(lhs)
144    }
145
146    fn parse_equality(&mut self) -> Result<Expr, ParseError> {
147        let mut lhs = self.parse_relational()?;
148        loop {
149            let op = match self.peek() {
150                Some(Token::Eq) => EqualityOp::Eq,
151                Some(Token::Ne) => EqualityOp::Ne,
152                _ => break,
153            };
154            self.advance();
155            let rhs = self.parse_relational()?;
156            lhs = Expr::Equality(Box::new(lhs), op, Box::new(rhs));
157        }
158        Ok(lhs)
159    }
160
161    fn parse_relational(&mut self) -> Result<Expr, ParseError> {
162        let mut lhs = self.parse_additive()?;
163        loop {
164            let op = match self.peek() {
165                Some(Token::Lt) => RelationalOp::Lt,
166                Some(Token::Gt) => RelationalOp::Gt,
167                Some(Token::Le) => RelationalOp::Le,
168                Some(Token::Ge) => RelationalOp::Ge,
169                _ => break,
170            };
171            self.advance();
172            let rhs = self.parse_additive()?;
173            lhs = Expr::Relational(Box::new(lhs), op, Box::new(rhs));
174        }
175        Ok(lhs)
176    }
177
178    fn parse_additive(&mut self) -> Result<Expr, ParseError> {
179        let mut lhs = self.parse_multiplicative()?;
180        loop {
181            let op = match self.peek() {
182                Some(Token::Plus) => AdditiveOp::Add,
183                Some(Token::Minus) => AdditiveOp::Sub,
184                _ => break,
185            };
186            self.advance();
187            let rhs = self.parse_multiplicative()?;
188            lhs = Expr::Additive(Box::new(lhs), op, Box::new(rhs));
189        }
190        Ok(lhs)
191    }
192
193    fn parse_multiplicative(&mut self) -> Result<Expr, ParseError> {
194        let mut lhs = self.parse_unary()?;
195        loop {
196            let op = match self.peek() {
197                Some(Token::Star) => MultiplicativeOp::Mul,
198                Some(Token::Div) => MultiplicativeOp::Div,
199                Some(Token::Mod) => MultiplicativeOp::Mod,
200                _ => break,
201            };
202            self.advance();
203            let rhs = self.parse_unary()?;
204            lhs = Expr::Multiplicative(Box::new(lhs), op, Box::new(rhs));
205        }
206        Ok(lhs)
207    }
208
209    fn parse_unary(&mut self) -> Result<Expr, ParseError> {
210        if matches!(self.peek(), Some(Token::Minus)) {
211            self.advance();
212            let operand = self.parse_unary()?;
213            Ok(Expr::Negate(Box::new(operand)))
214        } else {
215            self.parse_union()
216        }
217    }
218
219    fn parse_union(&mut self) -> Result<Expr, ParseError> {
220        let mut lhs = self.parse_path_expr()?;
221        while matches!(self.peek(), Some(Token::Pipe)) {
222            self.advance();
223            let rhs = self.parse_path_expr()?;
224            lhs = Expr::Union(Box::new(lhs), Box::new(rhs));
225        }
226        Ok(lhs)
227    }
228
229    // ---- PathExpr / LocationPath / Step / Predicate -----------------
230
231    fn peek_is_node_type_call(&self) -> bool {
232        matches!(self.peek(), Some(Token::Name { prefix: None, local }) if NODE_TYPES.contains(&local.as_str()))
233            && matches!(self.peek_at(1), Some(Token::LParen))
234    }
235
236    fn peek_axis(&self) -> Option<Axis> {
237        if let Some(Token::Name {
238            prefix: None,
239            local,
240        }) = self.peek()
241            && matches!(self.peek_at(1), Some(Token::ColonColon))
242        {
243            return Axis::from_name(local);
244        }
245        None
246    }
247
248    fn starts_location_path(&self) -> bool {
249        match self.peek() {
250            Some(Token::Slash) | Some(Token::SlashSlash) => true,
251            Some(Token::Dot)
252            | Some(Token::DotDot)
253            | Some(Token::At)
254            | Some(Token::Wildcard)
255            | Some(Token::NsWildcard(_)) => true,
256            Some(Token::Name { .. }) => {
257                if self.peek_is_node_type_call() {
258                    true
259                } else {
260                    !matches!(self.peek_at(1), Some(Token::LParen))
261                }
262            }
263            _ => false,
264        }
265    }
266
267    fn can_start_step(&self) -> bool {
268        matches!(
269            self.peek(),
270            Some(Token::Dot)
271                | Some(Token::DotDot)
272                | Some(Token::At)
273                | Some(Token::Wildcard)
274                | Some(Token::NsWildcard(_))
275                | Some(Token::Name { .. })
276        )
277    }
278
279    fn parse_path_expr(&mut self) -> Result<Expr, ParseError> {
280        if self.starts_location_path() {
281            let loc = self.parse_location_path()?;
282            Ok(Expr::Path(PathExpr::Location(loc)))
283        } else {
284            let filter = self.parse_filter_expr()?;
285            if matches!(self.peek(), Some(Token::Slash)) {
286                self.advance();
287                let steps = self.parse_step_sequence()?;
288                Ok(Expr::Path(PathExpr::FilterLocation(
289                    filter,
290                    LocationPath {
291                        is_absolute: false,
292                        steps,
293                    },
294                )))
295            } else if matches!(self.peek(), Some(Token::SlashSlash)) {
296                self.advance();
297                let mut steps = vec![desc_or_self_step()];
298                steps.extend(self.parse_step_sequence()?);
299                Ok(Expr::Path(PathExpr::FilterLocation(
300                    filter,
301                    LocationPath {
302                        is_absolute: false,
303                        steps,
304                    },
305                )))
306            } else {
307                Ok(Expr::Path(PathExpr::Filter(filter)))
308            }
309        }
310    }
311
312    fn parse_location_path(&mut self) -> Result<LocationPath, ParseError> {
313        if matches!(self.peek(), Some(Token::Slash)) {
314            self.advance();
315            if self.can_start_step() {
316                let steps = self.parse_step_sequence()?;
317                Ok(LocationPath {
318                    is_absolute: true,
319                    steps,
320                })
321            } else {
322                Ok(LocationPath {
323                    is_absolute: true,
324                    steps: Vec::new(),
325                })
326            }
327        } else if matches!(self.peek(), Some(Token::SlashSlash)) {
328            self.advance();
329            let mut steps = vec![desc_or_self_step()];
330            steps.extend(self.parse_step_sequence()?);
331            Ok(LocationPath {
332                is_absolute: true,
333                steps,
334            })
335        } else {
336            let steps = self.parse_step_sequence()?;
337            Ok(LocationPath {
338                is_absolute: false,
339                steps,
340            })
341        }
342    }
343
344    fn parse_step_sequence(&mut self) -> Result<Vec<Step>, ParseError> {
345        let mut steps = vec![self.parse_step()?];
346        loop {
347            if matches!(self.peek(), Some(Token::Slash)) {
348                self.advance();
349                steps.push(self.parse_step()?);
350            } else if matches!(self.peek(), Some(Token::SlashSlash)) {
351                self.advance();
352                steps.push(desc_or_self_step());
353                steps.push(self.parse_step()?);
354            } else {
355                break;
356            }
357        }
358        Ok(steps)
359    }
360
361    fn parse_step(&mut self) -> Result<Step, ParseError> {
362        match self.peek() {
363            Some(Token::Dot) => {
364                self.advance();
365                return Ok(Step {
366                    axis: Axis::SelfAxis,
367                    node_test: NodeTest::Node,
368                    predicates: Vec::new(),
369                });
370            }
371            Some(Token::DotDot) => {
372                self.advance();
373                return Ok(Step {
374                    axis: Axis::Parent,
375                    node_test: NodeTest::Node,
376                    predicates: Vec::new(),
377                });
378            }
379            _ => {}
380        }
381        let axis = if matches!(self.peek(), Some(Token::At)) {
382            self.advance();
383            Axis::Attribute
384        } else if let Some(axis) = self.peek_axis() {
385            self.advance(); // AxisName
386            self.advance(); // '::'
387            axis
388        } else {
389            Axis::Child
390        };
391        let node_test = self.parse_node_test()?;
392        let mut predicates = Vec::new();
393        while matches!(self.peek(), Some(Token::LBracket)) {
394            self.advance();
395            let e = self.parse_expr()?;
396            self.expect(Token::RBracket, "']'")?;
397            predicates.push(e);
398        }
399        Ok(Step {
400            axis,
401            node_test,
402            predicates,
403        })
404    }
405
406    fn parse_node_test(&mut self) -> Result<NodeTest, ParseError> {
407        match self.peek().cloned() {
408            Some(Token::Wildcard) => {
409                self.advance();
410                Ok(NodeTest::AnyName)
411            }
412            Some(Token::NsWildcard(prefix)) => {
413                self.advance();
414                Ok(NodeTest::NamespaceWildcard(prefix))
415            }
416            Some(Token::Name { prefix, local }) => {
417                if matches!(self.peek_at(1), Some(Token::LParen)) {
418                    if prefix.is_none() && NODE_TYPES.contains(&local.as_str()) {
419                        self.advance(); // name
420                        self.advance(); // '('
421                        let result = match local.as_str() {
422                            "node" => {
423                                self.expect(Token::RParen, "')'")?;
424                                NodeTest::Node
425                            }
426                            "text" => {
427                                self.expect(Token::RParen, "')'")?;
428                                NodeTest::Text
429                            }
430                            "comment" => {
431                                self.expect(Token::RParen, "')'")?;
432                                NodeTest::Comment
433                            }
434                            "processing-instruction" => {
435                                if let Some(Token::Literal(lit)) = self.peek().cloned() {
436                                    self.advance();
437                                    self.expect(Token::RParen, "')'")?;
438                                    NodeTest::ProcessingInstruction(Some(lit))
439                                } else {
440                                    self.expect(Token::RParen, "')'")?;
441                                    NodeTest::ProcessingInstruction(None)
442                                }
443                            }
444                            _ => unreachable!("filtered by NODE_TYPES.contains above"),
445                        };
446                        Ok(result)
447                    } else {
448                        Err(ParseError::new(
449                            self.current_pos(),
450                            "function call is not a valid node test",
451                        ))
452                    }
453                } else {
454                    self.advance();
455                    Ok(NodeTest::QName(QName { prefix, local }))
456                }
457            }
458            Some(_) => Err(ParseError::new(self.current_pos(), "expected a node test")),
459            None => Err(self.eof_error("a node test")),
460        }
461    }
462
463    // ---- FilterExpr / PrimaryExpr -----------------------------------
464
465    fn parse_filter_expr(&mut self) -> Result<FilterExpr, ParseError> {
466        let primary = self.parse_primary_expr()?;
467        let mut predicates = Vec::new();
468        while matches!(self.peek(), Some(Token::LBracket)) {
469            self.advance();
470            let e = self.parse_expr()?;
471            self.expect(Token::RBracket, "']'")?;
472            predicates.push(e);
473        }
474        Ok(FilterExpr {
475            primary,
476            predicates,
477        })
478    }
479
480    fn parse_primary_expr(&mut self) -> Result<PrimaryExpr, ParseError> {
481        match self.peek().cloned() {
482            Some(Token::Variable(qname)) => {
483                self.advance();
484                Ok(PrimaryExpr::Variable(qname))
485            }
486            Some(Token::LParen) => {
487                self.advance();
488                let e = self.parse_expr()?;
489                self.expect(Token::RParen, "')'")?;
490                Ok(PrimaryExpr::Parenthesized(Box::new(e)))
491            }
492            Some(Token::Literal(s)) => {
493                self.advance();
494                Ok(PrimaryExpr::Literal(s))
495            }
496            Some(Token::Number(n)) => {
497                self.advance();
498                Ok(PrimaryExpr::Number(n))
499            }
500            Some(Token::Name { prefix, local })
501                if matches!(self.peek_at(1), Some(Token::LParen)) =>
502            {
503                self.advance(); // name
504                self.advance(); // '('
505                let args = self.parse_argument_list()?;
506                self.expect(Token::RParen, "')'")?;
507                Ok(PrimaryExpr::Function(FunctionCall {
508                    name: QName { prefix, local },
509                    args,
510                }))
511            }
512            Some(_) => Err(ParseError::new(
513                self.current_pos(),
514                "expected an expression",
515            )),
516            None => Err(self.eof_error("an expression")),
517        }
518    }
519
520    fn parse_argument_list(&mut self) -> Result<Vec<Expr>, ParseError> {
521        let mut args = Vec::new();
522        if matches!(self.peek(), Some(Token::RParen)) {
523            return Ok(args);
524        }
525        args.push(self.parse_expr()?);
526        while matches!(self.peek(), Some(Token::Comma)) {
527            self.advance();
528            args.push(self.parse_expr()?);
529        }
530        Ok(args)
531    }
532}
533
534#[cfg(test)]
535mod tests {
536    use super::*;
537    use crate::ast::*;
538
539    fn qn(local: &str) -> QName {
540        QName {
541            prefix: None,
542            local: local.to_string(),
543        }
544    }
545
546    fn qn_ns(prefix: &str, local: &str) -> QName {
547        QName {
548            prefix: Some(prefix.to_string()),
549            local: local.to_string(),
550        }
551    }
552
553    fn name_test(local: &str) -> NodeTest {
554        NodeTest::QName(qn(local))
555    }
556
557    fn step(axis: Axis, node_test: NodeTest) -> Step {
558        Step {
559            axis,
560            node_test,
561            predicates: Vec::new(),
562        }
563    }
564
565    fn rel_path(steps: Vec<Step>) -> Expr {
566        Expr::Path(PathExpr::Location(LocationPath {
567            is_absolute: false,
568            steps,
569        }))
570    }
571
572    fn abs_path(steps: Vec<Step>) -> Expr {
573        Expr::Path(PathExpr::Location(LocationPath {
574            is_absolute: true,
575            steps,
576        }))
577    }
578
579    // ---- Abbreviated vs. full axis syntax ----------------------------
580
581    #[test]
582    fn abbreviated_child_axis_matches_full_child_axis() {
583        let abbrev = parse("para").unwrap();
584        let full = parse("child::para").unwrap();
585        let expected = rel_path(vec![step(Axis::Child, name_test("para"))]);
586        assert_eq!(abbrev, expected);
587        assert_eq!(full, expected);
588    }
589
590    #[test]
591    fn abbreviated_attribute_axis_matches_full_attribute_axis() {
592        let abbrev = parse("@name").unwrap();
593        let full = parse("attribute::name").unwrap();
594        let expected = rel_path(vec![step(Axis::Attribute, name_test("name"))]);
595        assert_eq!(abbrev, expected);
596        assert_eq!(full, expected);
597    }
598
599    #[test]
600    fn dot_desugars_to_self_axis_node_test() {
601        assert_eq!(
602            parse(".").unwrap(),
603            rel_path(vec![step(Axis::SelfAxis, NodeTest::Node)])
604        );
605    }
606
607    #[test]
608    fn dot_dot_desugars_to_parent_axis_node_test() {
609        assert_eq!(
610            parse("..").unwrap(),
611            rel_path(vec![step(Axis::Parent, NodeTest::Node)])
612        );
613    }
614
615    #[test]
616    fn double_slash_desugars_to_descendant_or_self_node_step() {
617        assert_eq!(
618            parse("//para").unwrap(),
619            abs_path(vec![
620                step(Axis::DescendantOrSelf, NodeTest::Node),
621                step(Axis::Child, name_test("para")),
622            ])
623        );
624    }
625
626    #[test]
627    fn double_slash_mid_path_inserts_descendant_or_self_step() {
628        assert_eq!(
629            parse("a//b").unwrap(),
630            rel_path(vec![
631                step(Axis::Child, name_test("a")),
632                step(Axis::DescendantOrSelf, NodeTest::Node),
633                step(Axis::Child, name_test("b")),
634            ])
635        );
636    }
637
638    #[test]
639    fn spec_example_absolute_path_with_predicates() {
640        // /doc/chapter[1]/para[last()] — checks AST shape, `last()` is
641        // parsed as a syntactic function call, not evaluated.
642        let last_call = Expr::Path(PathExpr::Filter(FilterExpr {
643            primary: PrimaryExpr::Function(FunctionCall {
644                name: qn("last"),
645                args: vec![],
646            }),
647            predicates: vec![],
648        }));
649        let one = Expr::Path(PathExpr::Filter(FilterExpr {
650            primary: PrimaryExpr::Number(1.0),
651            predicates: vec![],
652        }));
653        assert_eq!(
654            parse("/doc/chapter[1]/para[last()]").unwrap(),
655            abs_path(vec![
656                step(Axis::Child, name_test("doc")),
657                Step {
658                    axis: Axis::Child,
659                    node_test: name_test("chapter"),
660                    predicates: vec![one],
661                },
662                Step {
663                    axis: Axis::Child,
664                    node_test: name_test("para"),
665                    predicates: vec![last_call],
666                },
667            ])
668        );
669    }
670
671    // ---- All 13 axes --------------------------------------------------
672
673    #[test]
674    fn all_thirteen_axes_parse_to_their_enum_variant() {
675        let cases = [
676            ("ancestor::a", Axis::Ancestor),
677            ("ancestor-or-self::a", Axis::AncestorOrSelf),
678            ("attribute::a", Axis::Attribute),
679            ("child::a", Axis::Child),
680            ("descendant::a", Axis::Descendant),
681            ("descendant-or-self::a", Axis::DescendantOrSelf),
682            ("following::a", Axis::Following),
683            ("following-sibling::a", Axis::FollowingSibling),
684            ("namespace::a", Axis::Namespace),
685            ("parent::a", Axis::Parent),
686            ("preceding::a", Axis::Preceding),
687            ("preceding-sibling::a", Axis::PrecedingSibling),
688            ("self::a", Axis::SelfAxis),
689        ];
690        for (src, axis) in cases {
691            assert_eq!(
692                parse(src).unwrap(),
693                rel_path(vec![step(axis, name_test("a"))]),
694                "axis expression {src:?}"
695            );
696        }
697    }
698
699    // ---- NodeType tests -------------------------------------------------
700
701    #[test]
702    fn node_type_tests() {
703        assert_eq!(
704            parse("text()").unwrap(),
705            rel_path(vec![step(Axis::Child, NodeTest::Text)])
706        );
707        assert_eq!(
708            parse("comment()").unwrap(),
709            rel_path(vec![step(Axis::Child, NodeTest::Comment)])
710        );
711        assert_eq!(
712            parse("node()").unwrap(),
713            rel_path(vec![step(Axis::Child, NodeTest::Node)])
714        );
715        assert_eq!(
716            parse("processing-instruction()").unwrap(),
717            rel_path(vec![step(
718                Axis::Child,
719                NodeTest::ProcessingInstruction(None)
720            )])
721        );
722        assert_eq!(
723            parse("processing-instruction('foo')").unwrap(),
724            rel_path(vec![step(
725                Axis::Child,
726                NodeTest::ProcessingInstruction(Some("foo".to_string()))
727            )])
728        );
729    }
730
731    // ---- Predicates -----------------------------------------------------
732
733    #[test]
734    fn chained_predicates() {
735        let a = Step {
736            axis: Axis::Child,
737            node_test: name_test("a"),
738            predicates: vec![
739                rel_path(vec![step(Axis::Child, name_test("b"))]),
740                rel_path(vec![step(Axis::Child, name_test("c"))]),
741            ],
742        };
743        assert_eq!(parse("a[b][c]").unwrap(), rel_path(vec![a]));
744    }
745
746    #[test]
747    fn nested_predicate() {
748        // a[b[c]]
749        let inner_b = Step {
750            axis: Axis::Child,
751            node_test: name_test("b"),
752            predicates: vec![rel_path(vec![step(Axis::Child, name_test("c"))])],
753        };
754        let a = Step {
755            axis: Axis::Child,
756            node_test: name_test("a"),
757            predicates: vec![rel_path(vec![inner_b])],
758        };
759        assert_eq!(parse("a[b[c]]").unwrap(), rel_path(vec![a]));
760    }
761
762    // ---- Operator precedence / associativity -----------------------------
763
764    #[test]
765    fn multiplicative_binds_tighter_than_additive() {
766        // 1 + 2 * 3
767        let one = Expr::Path(PathExpr::Filter(FilterExpr {
768            primary: PrimaryExpr::Number(1.0),
769            predicates: vec![],
770        }));
771        let two = Expr::Path(PathExpr::Filter(FilterExpr {
772            primary: PrimaryExpr::Number(2.0),
773            predicates: vec![],
774        }));
775        let three = Expr::Path(PathExpr::Filter(FilterExpr {
776            primary: PrimaryExpr::Number(3.0),
777            predicates: vec![],
778        }));
779        let expected = Expr::Additive(
780            Box::new(one),
781            AdditiveOp::Add,
782            Box::new(Expr::Multiplicative(
783                Box::new(two),
784                MultiplicativeOp::Mul,
785                Box::new(three),
786            )),
787        );
788        assert_eq!(parse("1 + 2 * 3").unwrap(), expected);
789    }
790
791    #[test]
792    fn parens_override_precedence() {
793        // (1 + 2) * 3
794        let one = Expr::Path(PathExpr::Filter(FilterExpr {
795            primary: PrimaryExpr::Number(1.0),
796            predicates: vec![],
797        }));
798        let two = Expr::Path(PathExpr::Filter(FilterExpr {
799            primary: PrimaryExpr::Number(2.0),
800            predicates: vec![],
801        }));
802        let three = Expr::Path(PathExpr::Filter(FilterExpr {
803            primary: PrimaryExpr::Number(3.0),
804            predicates: vec![],
805        }));
806        let sum = Expr::Additive(Box::new(one), AdditiveOp::Add, Box::new(two));
807        let grouped = Expr::Path(PathExpr::Filter(FilterExpr {
808            primary: PrimaryExpr::Parenthesized(Box::new(sum)),
809            predicates: vec![],
810        }));
811        let expected =
812            Expr::Multiplicative(Box::new(grouped), MultiplicativeOp::Mul, Box::new(three));
813        assert_eq!(parse("(1 + 2) * 3").unwrap(), expected);
814    }
815
816    #[test]
817    fn subtraction_is_left_associative() {
818        // 1 - 2 - 3 == (1 - 2) - 3
819        fn num(n: f64) -> Expr {
820            Expr::Path(PathExpr::Filter(FilterExpr {
821                primary: PrimaryExpr::Number(n),
822                predicates: vec![],
823            }))
824        }
825        let expected = Expr::Additive(
826            Box::new(Expr::Additive(
827                Box::new(num(1.0)),
828                AdditiveOp::Sub,
829                Box::new(num(2.0)),
830            )),
831            AdditiveOp::Sub,
832            Box::new(num(3.0)),
833        );
834        assert_eq!(parse("1 - 2 - 3").unwrap(), expected);
835    }
836
837    #[test]
838    fn unary_minus() {
839        // -1 + 2
840        fn num(n: f64) -> Expr {
841            Expr::Path(PathExpr::Filter(FilterExpr {
842                primary: PrimaryExpr::Number(n),
843                predicates: vec![],
844            }))
845        }
846        let expected = Expr::Additive(
847            Box::new(Expr::Negate(Box::new(num(1.0)))),
848            AdditiveOp::Add,
849            Box::new(num(2.0)),
850        );
851        assert_eq!(parse("-1 + 2").unwrap(), expected);
852    }
853
854    // ---- div/mod/and/or as operators AND as valid NCNames -----------------
855
856    #[test]
857    fn div_mod_and_or_as_operators() {
858        fn num(n: f64) -> Expr {
859            Expr::Path(PathExpr::Filter(FilterExpr {
860                primary: PrimaryExpr::Number(n),
861                predicates: vec![],
862            }))
863        }
864        assert_eq!(
865            parse("1 div 2").unwrap(),
866            Expr::Multiplicative(
867                Box::new(num(1.0)),
868                MultiplicativeOp::Div,
869                Box::new(num(2.0))
870            )
871        );
872        assert_eq!(
873            parse("1 mod 2").unwrap(),
874            Expr::Multiplicative(
875                Box::new(num(1.0)),
876                MultiplicativeOp::Mod,
877                Box::new(num(2.0))
878            )
879        );
880        assert_eq!(
881            parse("a and b").unwrap(),
882            Expr::And(
883                Box::new(rel_path(vec![step(Axis::Child, name_test("a"))])),
884                Box::new(rel_path(vec![step(Axis::Child, name_test("b"))])),
885            )
886        );
887        assert_eq!(
888            parse("a or b").unwrap(),
889            Expr::Or(
890                Box::new(rel_path(vec![step(Axis::Child, name_test("a"))])),
891                Box::new(rel_path(vec![step(Axis::Child, name_test("b"))])),
892            )
893        );
894    }
895
896    #[test]
897    fn div_as_ncname_in_non_operator_position() {
898        // child::div — after '::', "div" must be a NameTest, not an operator.
899        assert_eq!(
900            parse("child::div").unwrap(),
901            rel_path(vec![step(Axis::Child, name_test("div"))])
902        );
903        // //div — after '//', "div" must be a NameTest, not an operator.
904        assert_eq!(
905            parse("//div").unwrap(),
906            abs_path(vec![
907                step(Axis::DescendantOrSelf, NodeTest::Node),
908                step(Axis::Child, name_test("div")),
909            ])
910        );
911    }
912
913    // ---- Union --------------------------------------------------------
914
915    #[test]
916    fn union_of_three_paths_is_left_associative() {
917        let a = rel_path(vec![step(Axis::Child, name_test("a"))]);
918        let b = rel_path(vec![step(Axis::Child, name_test("b"))]);
919        let c = rel_path(vec![step(Axis::Child, name_test("c"))]);
920        let expected = Expr::Union(Box::new(Expr::Union(Box::new(a), Box::new(b))), Box::new(c));
921        assert_eq!(parse("a | b | c").unwrap(), expected);
922    }
923
924    // ---- Variable references -------------------------------------------
925
926    #[test]
927    fn variable_references() {
928        assert_eq!(
929            parse("$foo").unwrap(),
930            Expr::Path(PathExpr::Filter(FilterExpr {
931                primary: PrimaryExpr::Variable(qn("foo")),
932                predicates: vec![],
933            }))
934        );
935        assert_eq!(
936            parse("$ns:foo").unwrap(),
937            Expr::Path(PathExpr::Filter(FilterExpr {
938                primary: PrimaryExpr::Variable(qn_ns("ns", "foo")),
939                predicates: vec![],
940            }))
941        );
942    }
943
944    // ---- Literals -------------------------------------------------------
945
946    #[test]
947    fn literals_both_quote_styles() {
948        assert_eq!(
949            parse("'hello'").unwrap(),
950            Expr::Path(PathExpr::Filter(FilterExpr {
951                primary: PrimaryExpr::Literal("hello".to_string()),
952                predicates: vec![],
953            }))
954        );
955        assert_eq!(
956            parse("\"hello\"").unwrap(),
957            Expr::Path(PathExpr::Filter(FilterExpr {
958                primary: PrimaryExpr::Literal("hello".to_string()),
959                predicates: vec![],
960            }))
961        );
962    }
963
964    // ---- Numbers --------------------------------------------------------
965
966    #[test]
967    #[allow(clippy::approx_constant)] // 3.14 is a plain test literal, not an approximation of pi
968    fn numbers() {
969        fn number(n: f64) -> Expr {
970            Expr::Path(PathExpr::Filter(FilterExpr {
971                primary: PrimaryExpr::Number(n),
972                predicates: vec![],
973            }))
974        }
975        assert_eq!(parse("3").unwrap(), number(3.0));
976        assert_eq!(parse("3.14").unwrap(), number(3.14));
977        assert_eq!(parse(".5").unwrap(), number(0.5));
978    }
979
980    // ---- Namespace-prefixed name tests -----------------------------------
981
982    #[test]
983    fn namespace_prefixed_name_tests() {
984        assert_eq!(
985            parse("svg:rect").unwrap(),
986            rel_path(vec![step(
987                Axis::Child,
988                NodeTest::QName(qn_ns("svg", "rect"))
989            )])
990        );
991        assert_eq!(
992            parse("*").unwrap(),
993            rel_path(vec![step(Axis::Child, NodeTest::AnyName)])
994        );
995        assert_eq!(
996            parse("svg:*").unwrap(),
997            rel_path(vec![step(
998                Axis::Child,
999                NodeTest::NamespaceWildcard("svg".to_string())
1000            )])
1001        );
1002    }
1003
1004    // ---- Function calls ---------------------------------------------------
1005
1006    #[test]
1007    fn function_call_with_multiple_arguments() {
1008        let call = parse("concat(a, b, c)").unwrap();
1009        let arg = |n: &str| rel_path(vec![step(Axis::Child, name_test(n))]);
1010        assert_eq!(
1011            call,
1012            Expr::Path(PathExpr::Filter(FilterExpr {
1013                primary: PrimaryExpr::Function(FunctionCall {
1014                    name: qn("concat"),
1015                    args: vec![arg("a"), arg("b"), arg("c")],
1016                }),
1017                predicates: vec![],
1018            }))
1019        );
1020    }
1021
1022    #[test]
1023    fn function_call_with_zero_arguments() {
1024        assert_eq!(
1025            parse("true()").unwrap(),
1026            Expr::Path(PathExpr::Filter(FilterExpr {
1027                primary: PrimaryExpr::Function(FunctionCall {
1028                    name: qn("true"),
1029                    args: vec![],
1030                }),
1031                predicates: vec![],
1032            }))
1033        );
1034    }
1035
1036    // ---- Error cases ------------------------------------------------------
1037
1038    #[test]
1039    fn error_unexpected_end_of_input() {
1040        let err = parse("1 +").unwrap_err();
1041        assert_eq!(err.position, 3);
1042    }
1043
1044    #[test]
1045    fn error_unbalanced_parens() {
1046        let err = parse("(1 + 2").unwrap_err();
1047        assert_eq!(err.position, 6);
1048
1049        let err2 = parse("1 + 2)").unwrap_err();
1050        assert_eq!(err2.position, 5);
1051    }
1052
1053    #[test]
1054    fn error_empty_expression() {
1055        let err = parse("").unwrap_err();
1056        assert_eq!(err.position, 0);
1057        assert_eq!(err.message, "empty expression");
1058    }
1059}