Skip to main content

jpx_core/
parser.rs

1//! Module for parsing JMESPath expressions into an AST.
2//!
3//! This JMESPath parser is implemented using a Pratt parser,
4//! or top down operator precedence parser.
5
6use crate::ast::{Ast, Comparator, KeyValuePair};
7use crate::lexer::{Token, TokenTuple, tokenize};
8use crate::{ErrorReason, JmespathError};
9
10/// Result of parsing an expression.
11pub type ParseResult = Result<Ast, JmespathError>;
12
13/// Parses a JMESPath expression into an AST.
14pub fn parse(expr: &str) -> ParseResult {
15    let tokens = tokenize(expr)?;
16    Parser::new(tokens, expr).parse()
17}
18
19/// The maximum binding power for a token that can stop a projection.
20const PROJECTION_STOP: usize = 10;
21
22/// Maximum expression nesting depth accepted by the parser.
23///
24/// Every nested sub-expression enters through [`Parser::nud`] exactly once, so
25/// bounding `nud` recursion here prevents deeply nested input (e.g. `!!!...`,
26/// `(((...)))`, nested filters or function arguments) from overflowing the
27/// stack and aborting the process. Kept in step with the interpreter's
28/// `MAX_EVAL_DEPTH`; far above any realistic expression.
29const MAX_PARSE_DEPTH: usize = 128;
30
31struct Parser<'a> {
32    tokens: Vec<TokenTuple<'a>>,
33    cursor: usize,
34    expr: &'a str,
35    offset: usize,
36    depth: usize,
37}
38
39impl<'a> Parser<'a> {
40    fn new(tokens: Vec<TokenTuple<'a>>, expr: &'a str) -> Parser<'a> {
41        Parser {
42            tokens,
43            cursor: 0,
44            expr,
45            offset: 0,
46            depth: 0,
47        }
48    }
49
50    #[inline]
51    fn parse(&mut self) -> ParseResult {
52        self.expr(0).and_then(|result| match self.peek(0) {
53            &Token::Eof => Ok(result),
54            t => Err(self.err(t, "Did not parse the complete expression", true)),
55        })
56    }
57
58    #[inline]
59    fn advance(&mut self) -> Token<'a> {
60        self.advance_with_pos().1
61    }
62
63    #[inline]
64    fn advance_with_pos(&mut self) -> (usize, Token<'a>) {
65        if self.cursor < self.tokens.len() {
66            let (pos, tok) = self.tokens[self.cursor].clone();
67            self.cursor += 1;
68            self.offset = pos;
69            (pos, tok)
70        } else {
71            (self.offset, Token::Eof)
72        }
73    }
74
75    #[inline]
76    fn peek(&self, lookahead: usize) -> &Token<'a> {
77        let idx = self.cursor + lookahead;
78        if idx < self.tokens.len() {
79            &self.tokens[idx].1
80        } else {
81            &Token::Eof
82        }
83    }
84
85    fn err(&self, current_token: &Token<'_>, error_msg: &str, is_peek: bool) -> JmespathError {
86        let mut actual_pos = self.offset;
87        let mut buff = error_msg.to_string();
88        buff.push_str(&format!(" -- found {current_token:?}"));
89        if is_peek && self.cursor < self.tokens.len() {
90            actual_pos = self.tokens[self.cursor].0;
91        }
92        JmespathError::new(self.expr, actual_pos, ErrorReason::Parse(buff))
93    }
94
95    fn expr(&mut self, rbp: usize) -> ParseResult {
96        let mut left = self.nud();
97        // Bound the left-associative chain length (e.g. `a.a.a...`, `a|a|a...`).
98        // Such chains are built iteratively here without deep recursion, so
99        // `nud`'s depth guard does not catch them, yet they produce a deeply
100        // nested AST that would overflow any recursive AST walker (the
101        // interpreter, `explain`, drop). Rejecting them here keeps every
102        // downstream consumer safe.
103        let mut chain = 0usize;
104        while rbp < self.peek(0).lbp() {
105            chain += 1;
106            if chain > MAX_PARSE_DEPTH {
107                return Err(self.err(
108                    self.peek(0),
109                    &format!("Expression nesting exceeds maximum depth of {MAX_PARSE_DEPTH}"),
110                    true,
111                ));
112            }
113            left = self.led(Box::new(left?));
114        }
115        left
116    }
117
118    fn nud(&mut self) -> ParseResult {
119        self.depth += 1;
120        if self.depth > MAX_PARSE_DEPTH {
121            self.depth -= 1;
122            return Err(self.err(
123                self.peek(0),
124                &format!("Expression nesting exceeds maximum depth of {MAX_PARSE_DEPTH}"),
125                true,
126            ));
127        }
128        let result = self.nud_inner();
129        self.depth -= 1;
130        result
131    }
132
133    fn nud_inner(&mut self) -> ParseResult {
134        let (offset, token) = self.advance_with_pos();
135        match token {
136            Token::At => Ok(Ast::Identity { offset }),
137            #[cfg(feature = "let-expr")]
138            Token::Identifier(value) if *value == *"let" => self.parse_let(offset),
139            Token::Identifier(value) => Ok(Ast::Field {
140                name: value.to_owned(),
141                offset,
142            }),
143            Token::QuotedIdentifier(value) => match self.peek(0) {
144                Token::Lparen => {
145                    let message = "Quoted strings can't be a function name";
146                    Err(self.err(&Token::Lparen, message, true))
147                }
148                _ => Ok(Ast::Field {
149                    name: value,
150                    offset,
151                }),
152            },
153            Token::Star => self.parse_wildcard_values(Box::new(Ast::Identity { offset })),
154            Token::Literal(value) => Ok(Ast::Literal { value, offset }),
155            Token::Lbracket => match self.peek(0) {
156                &Token::Number(_) | &Token::Colon => self.parse_index(),
157                &Token::Star if self.peek(1) == &Token::Rbracket => {
158                    self.advance();
159                    self.parse_wildcard_index(Box::new(Ast::Identity { offset }))
160                }
161                _ => self.parse_multi_list(),
162            },
163            Token::Flatten => self.parse_flatten(Box::new(Ast::Identity { offset })),
164            Token::Lbrace => {
165                let mut pairs = vec![];
166                loop {
167                    pairs.push(self.parse_kvp()?);
168                    match self.advance() {
169                        Token::Rbrace => break,
170                        Token::Comma => continue,
171                        ref t => return Err(self.err(t, "Expected '}' or ','", false)),
172                    }
173                }
174                Ok(Ast::MultiHash {
175                    elements: pairs,
176                    offset,
177                })
178            }
179            t @ Token::Ampersand => {
180                let rhs = self.expr(t.lbp())?;
181                Ok(Ast::Expref {
182                    ast: Box::new(rhs),
183                    offset,
184                })
185            }
186            t @ Token::Not => Ok(Ast::Not {
187                node: Box::new(self.expr(t.lbp())?),
188                offset,
189            }),
190            Token::Filter => self.parse_filter(Box::new(Ast::Identity { offset })),
191            Token::Lparen => {
192                let result = self.expr(0)?;
193                match self.advance() {
194                    Token::Rparen => Ok(result),
195                    ref t => Err(self.err(t, "Expected ')' to close '('", false)),
196                }
197            }
198            #[cfg(feature = "let-expr")]
199            Token::Variable(name) => Ok(Ast::VariableRef {
200                name: name.to_owned(),
201                offset,
202            }),
203            ref t => Err(self.err(t, "Unexpected nud token", false)),
204        }
205    }
206
207    fn led(&mut self, left: Box<Ast>) -> ParseResult {
208        let (offset, token) = self.advance_with_pos();
209        match token {
210            t @ Token::Dot => {
211                if self.peek(0) == &Token::Star {
212                    self.advance();
213                    self.parse_wildcard_values(left)
214                } else {
215                    let rhs = self.parse_dot(t.lbp())?;
216                    Ok(Ast::Subexpr {
217                        offset,
218                        lhs: left,
219                        rhs: Box::new(rhs),
220                    })
221                }
222            }
223            Token::Lbracket => {
224                if match self.peek(0) {
225                    &Token::Number(_) | &Token::Colon => true,
226                    &Token::Star => false,
227                    t => return Err(self.err(t, "Expected number, ':', or '*'", true)),
228                } {
229                    Ok(Ast::Subexpr {
230                        offset,
231                        lhs: left,
232                        rhs: Box::new(self.parse_index()?),
233                    })
234                } else {
235                    self.advance();
236                    self.parse_wildcard_index(left)
237                }
238            }
239            t @ Token::Or => {
240                let rhs = self.expr(t.lbp())?;
241                Ok(Ast::Or {
242                    offset,
243                    lhs: left,
244                    rhs: Box::new(rhs),
245                })
246            }
247            t @ Token::And => {
248                let rhs = self.expr(t.lbp())?;
249                Ok(Ast::And {
250                    offset,
251                    lhs: left,
252                    rhs: Box::new(rhs),
253                })
254            }
255            t @ Token::Pipe => {
256                let rhs = self.expr(t.lbp())?;
257                Ok(Ast::Subexpr {
258                    offset,
259                    lhs: left,
260                    rhs: Box::new(rhs),
261                })
262            }
263            Token::Lparen => match *left {
264                Ast::Field { name: v, .. } => Ok(Ast::Function {
265                    offset,
266                    name: v,
267                    args: self.parse_list(Token::Rparen)?,
268                }),
269                _ => Err(self.err(self.peek(0), "Invalid function name", true)),
270            },
271            Token::Flatten => self.parse_flatten(left),
272            Token::Filter => self.parse_filter(left),
273            Token::Eq => self.parse_comparator(Comparator::Equal, left),
274            Token::Ne => self.parse_comparator(Comparator::NotEqual, left),
275            Token::Gt => self.parse_comparator(Comparator::GreaterThan, left),
276            Token::Gte => self.parse_comparator(Comparator::GreaterThanEqual, left),
277            Token::Lt => self.parse_comparator(Comparator::LessThan, left),
278            Token::Lte => self.parse_comparator(Comparator::LessThanEqual, left),
279            ref t => Err(self.err(t, "Unexpected led token", false)),
280        }
281    }
282
283    #[cfg(feature = "let-expr")]
284    fn parse_let(&mut self, offset: usize) -> ParseResult {
285        let mut bindings = vec![];
286        loop {
287            match self.peek(0) {
288                Token::Variable(_) => {
289                    let var_name = match self.advance() {
290                        Token::Variable(name) => name.to_owned(),
291                        _ => unreachable!(),
292                    };
293                    match self.advance() {
294                        Token::Assign => {}
295                        ref t => {
296                            return Err(self.err(
297                                t,
298                                "Expected '=' after variable in let binding",
299                                false,
300                            ));
301                        }
302                    }
303                    let value = self.parse_let_binding_expr()?;
304                    bindings.push((var_name, value));
305                    match self.peek(0) {
306                        Token::Comma => {
307                            self.advance();
308                        }
309                        Token::Identifier(s) if *s == "in" => {
310                            break;
311                        }
312                        t => {
313                            return Err(self.err(
314                                t,
315                                "Expected ',' or 'in' after let binding",
316                                true,
317                            ));
318                        }
319                    }
320                }
321                t => {
322                    return Err(self.err(t, "Expected variable binding ($name) after 'let'", true));
323                }
324            }
325        }
326        match self.advance() {
327            Token::Identifier(s) if *s == *"in" => {}
328            ref t => {
329                return Err(self.err(t, "Expected 'in' keyword after let bindings", false));
330            }
331        }
332        let body = self.expr(0)?;
333        Ok(Ast::Let {
334            offset,
335            bindings,
336            expr: Box::new(body),
337        })
338    }
339
340    #[cfg(feature = "let-expr")]
341    fn parse_let_binding_expr(&mut self) -> ParseResult {
342        self.parse_let_binding_expr_bp(0)
343    }
344
345    #[cfg(feature = "let-expr")]
346    fn parse_let_binding_expr_bp(&mut self, rbp: usize) -> ParseResult {
347        let mut left = self.nud();
348        let mut chain = 0usize;
349        loop {
350            match self.peek(0) {
351                Token::Comma => break,
352                Token::Identifier(s) if *s == "in" => break,
353                _ => {}
354            }
355            if rbp >= self.peek(0).lbp() {
356                break;
357            }
358            chain += 1;
359            if chain > MAX_PARSE_DEPTH {
360                return Err(self.err(
361                    self.peek(0),
362                    &format!("Expression nesting exceeds maximum depth of {MAX_PARSE_DEPTH}"),
363                    true,
364                ));
365            }
366            left = self.led(Box::new(left?));
367        }
368        left
369    }
370
371    fn parse_kvp(&mut self) -> Result<KeyValuePair, JmespathError> {
372        match self.advance() {
373            Token::Identifier(value) => {
374                if self.peek(0) == &Token::Colon {
375                    self.advance();
376                    Ok(KeyValuePair {
377                        key: value.to_owned(),
378                        value: self.expr(0)?,
379                    })
380                } else {
381                    Err(self.err(self.peek(0), "Expected ':' to follow key", true))
382                }
383            }
384            Token::QuotedIdentifier(value) => {
385                if self.peek(0) == &Token::Colon {
386                    self.advance();
387                    Ok(KeyValuePair {
388                        key: value,
389                        value: self.expr(0)?,
390                    })
391                } else {
392                    Err(self.err(self.peek(0), "Expected ':' to follow key", true))
393                }
394            }
395            ref t => Err(self.err(t, "Expected Field to start key value pair", false)),
396        }
397    }
398
399    fn parse_filter(&mut self, lhs: Box<Ast>) -> ParseResult {
400        let condition_lhs = Box::new(self.expr(0)?);
401        match self.advance() {
402            Token::Rbracket => {
403                let condition_rhs = Box::new(self.projection_rhs(Token::Filter.lbp())?);
404                Ok(Ast::Projection {
405                    offset: self.offset,
406                    lhs,
407                    rhs: Box::new(Ast::Condition {
408                        offset: self.offset,
409                        predicate: condition_lhs,
410                        then: condition_rhs,
411                    }),
412                })
413            }
414            ref t => Err(self.err(t, "Expected ']'", false)),
415        }
416    }
417
418    fn parse_flatten(&mut self, lhs: Box<Ast>) -> ParseResult {
419        let rhs = Box::new(self.projection_rhs(Token::Flatten.lbp())?);
420        Ok(Ast::Projection {
421            offset: self.offset,
422            lhs: Box::new(Ast::Flatten {
423                offset: self.offset,
424                node: lhs,
425            }),
426            rhs,
427        })
428    }
429
430    fn parse_comparator(&mut self, cmp: Comparator, lhs: Box<Ast>) -> ParseResult {
431        let rhs = Box::new(self.expr(Token::Eq.lbp())?);
432        Ok(Ast::Comparison {
433            offset: self.offset,
434            comparator: cmp,
435            lhs,
436            rhs,
437        })
438    }
439
440    fn parse_dot(&mut self, lbp: usize) -> ParseResult {
441        if match self.peek(0) {
442            &Token::Lbracket => true,
443            &Token::Identifier(_)
444            | &Token::QuotedIdentifier(_)
445            | &Token::Star
446            | &Token::Lbrace
447            | &Token::Ampersand => false,
448            t => return Err(self.err(t, "Expected identifier, '*', '{', '[', '&', or '[?'", true)),
449        } {
450            self.advance();
451            self.parse_multi_list()
452        } else {
453            self.expr(lbp)
454        }
455    }
456
457    fn projection_rhs(&mut self, lbp: usize) -> ParseResult {
458        if match self.peek(0) {
459            &Token::Dot => true,
460            &Token::Lbracket | &Token::Filter => false,
461            t if t.lbp() < PROJECTION_STOP => {
462                return Ok(Ast::Identity {
463                    offset: self.offset,
464                });
465            }
466            t => {
467                return Err(self.err(t, "Expected '.', '[', or '[?'", true));
468            }
469        } {
470            self.advance();
471            self.parse_dot(lbp)
472        } else {
473            self.expr(lbp)
474        }
475    }
476
477    fn parse_wildcard_index(&mut self, lhs: Box<Ast>) -> ParseResult {
478        match self.advance() {
479            Token::Rbracket => {
480                let rhs = Box::new(self.projection_rhs(Token::Star.lbp())?);
481                Ok(Ast::Projection {
482                    offset: self.offset,
483                    lhs,
484                    rhs,
485                })
486            }
487            ref t => Err(self.err(t, "Expected ']' for wildcard index", false)),
488        }
489    }
490
491    fn parse_wildcard_values(&mut self, lhs: Box<Ast>) -> ParseResult {
492        let rhs = Box::new(self.projection_rhs(Token::Star.lbp())?);
493        Ok(Ast::Projection {
494            offset: self.offset,
495            lhs: Box::new(Ast::ObjectValues {
496                offset: self.offset,
497                node: lhs,
498            }),
499            rhs,
500        })
501    }
502
503    fn parse_index(&mut self) -> ParseResult {
504        let mut parts = [None, None, None];
505        let mut pos = 0;
506        loop {
507            match self.advance() {
508                Token::Number(value) => {
509                    parts[pos] = Some(value);
510                    match self.peek(0) {
511                        &Token::Colon | &Token::Rbracket => (),
512                        t => return Err(self.err(t, "Expected ':', or ']'", true)),
513                    };
514                }
515                Token::Rbracket => break,
516                Token::Colon if pos >= 2 => {
517                    return Err(self.err(&Token::Colon, "Too many colons in slice expr", false));
518                }
519                Token::Colon => {
520                    pos += 1;
521                    match self.peek(0) {
522                        &Token::Number(_) | &Token::Colon | &Token::Rbracket => continue,
523                        t => return Err(self.err(t, "Expected number, ':', or ']'", true)),
524                    };
525                }
526                ref t => return Err(self.err(t, "Expected number, ':', or ']'", false)),
527            }
528        }
529
530        if pos == 0 {
531            Ok(Ast::Index {
532                offset: self.offset,
533                idx: parts[0].ok_or_else(|| {
534                    JmespathError::new(
535                        self.expr,
536                        self.offset,
537                        ErrorReason::Parse(
538                            "Expected parts[0] to be Some; but found None".to_owned(),
539                        ),
540                    )
541                })?,
542            })
543        } else {
544            Ok(Ast::Projection {
545                offset: self.offset,
546                lhs: Box::new(Ast::Slice {
547                    offset: self.offset,
548                    start: parts[0],
549                    stop: parts[1],
550                    step: parts[2].unwrap_or(1),
551                }),
552                rhs: Box::new(self.projection_rhs(Token::Star.lbp())?),
553            })
554        }
555    }
556
557    fn parse_multi_list(&mut self) -> ParseResult {
558        Ok(Ast::MultiList {
559            offset: self.offset,
560            elements: self.parse_list(Token::Rbracket)?,
561        })
562    }
563
564    fn parse_list(&mut self, closing: Token<'_>) -> Result<Vec<Ast>, JmespathError> {
565        let mut nodes = vec![];
566        while self.peek(0) != &closing {
567            nodes.push(self.expr(0)?);
568            if self.peek(0) == &Token::Comma {
569                self.advance();
570                if self.peek(0) == &closing {
571                    return Err(self.err(self.peek(0), "invalid token after ','", true));
572                }
573            }
574        }
575        self.advance();
576        Ok(nodes)
577    }
578}
579
580#[cfg(test)]
581mod tests {
582    use super::*;
583    use crate::ast::Comparator;
584
585    #[test]
586    fn parse_field() {
587        let ast = parse("foo").unwrap();
588        assert!(matches!(ast, Ast::Field { name, .. } if name == "foo"));
589    }
590
591    #[test]
592    fn parse_identity() {
593        let ast = parse("@").unwrap();
594        assert!(matches!(ast, Ast::Identity { .. }));
595    }
596
597    #[test]
598    fn parse_subexpr() {
599        let ast = parse("foo.bar").unwrap();
600        assert!(matches!(ast, Ast::Subexpr { .. }));
601    }
602
603    #[test]
604    fn parse_deeply_nested_subexpr() {
605        let ast = parse("a.b.c.d.e").unwrap();
606        assert!(matches!(ast, Ast::Subexpr { .. }));
607    }
608
609    #[test]
610    fn parse_index_positive() {
611        let ast = parse("[0]").unwrap();
612        assert!(matches!(ast, Ast::Index { idx: 0, .. }));
613    }
614
615    #[test]
616    fn parse_index_negative() {
617        let ast = parse("[-1]").unwrap();
618        assert!(matches!(ast, Ast::Index { idx: -1, .. }));
619    }
620
621    #[test]
622    fn parse_slice_basic() {
623        let ast = parse("[0:5]").unwrap();
624        match ast {
625            Ast::Projection { lhs, .. } => {
626                assert!(matches!(
627                    *lhs,
628                    Ast::Slice {
629                        start: Some(0),
630                        stop: Some(5),
631                        step: 1,
632                        ..
633                    }
634                ));
635            }
636            _ => panic!("expected Projection with Slice lhs"),
637        }
638    }
639
640    #[test]
641    fn parse_slice_with_step() {
642        let ast = parse("[::2]").unwrap();
643        match ast {
644            Ast::Projection { lhs, .. } => {
645                assert!(matches!(
646                    *lhs,
647                    Ast::Slice {
648                        start: None,
649                        stop: None,
650                        step: 2,
651                        ..
652                    }
653                ));
654            }
655            _ => panic!("expected Projection with Slice lhs"),
656        }
657    }
658
659    #[test]
660    fn parse_slice_negative_step() {
661        let ast = parse("[::-1]").unwrap();
662        match ast {
663            Ast::Projection { lhs, .. } => {
664                assert!(matches!(*lhs, Ast::Slice { step: -1, .. }));
665            }
666            _ => panic!("expected Projection with Slice lhs"),
667        }
668    }
669
670    #[test]
671    fn parse_wildcard_values() {
672        let ast = parse("*").unwrap();
673        assert!(matches!(ast, Ast::Projection { .. }));
674    }
675
676    #[test]
677    fn parse_wildcard_index() {
678        let ast = parse("[*]").unwrap();
679        assert!(matches!(ast, Ast::Projection { .. }));
680    }
681
682    #[test]
683    fn parse_flatten() {
684        let ast = parse("[]").unwrap();
685        match ast {
686            Ast::Projection { lhs, .. } => {
687                assert!(matches!(*lhs, Ast::Flatten { .. }));
688            }
689            _ => panic!("expected Projection with Flatten"),
690        }
691    }
692
693    #[test]
694    fn parse_filter() {
695        let ast = parse("[?a > `1`]").unwrap();
696        assert!(matches!(ast, Ast::Projection { .. }));
697    }
698
699    #[test]
700    fn parse_or() {
701        let ast = parse("a || b").unwrap();
702        assert!(matches!(ast, Ast::Or { .. }));
703    }
704
705    #[test]
706    fn parse_and() {
707        let ast = parse("a && b").unwrap();
708        assert!(matches!(ast, Ast::And { .. }));
709    }
710
711    #[test]
712    fn parse_not() {
713        let ast = parse("!a").unwrap();
714        assert!(matches!(ast, Ast::Not { .. }));
715    }
716
717    #[test]
718    fn parse_pipe() {
719        let ast = parse("a | b").unwrap();
720        assert!(matches!(ast, Ast::Subexpr { .. }));
721    }
722
723    #[test]
724    fn parse_function_call() {
725        let ast = parse("length(@)").unwrap();
726        match ast {
727            Ast::Function { name, args, .. } => {
728                assert_eq!(name, "length");
729                assert_eq!(args.len(), 1);
730            }
731            _ => panic!("expected Function"),
732        }
733    }
734
735    #[test]
736    fn parse_multi_list() {
737        let ast = parse("[a, b, c]").unwrap();
738        match ast {
739            Ast::MultiList { elements, .. } => {
740                assert_eq!(elements.len(), 3);
741            }
742            _ => panic!("expected MultiList"),
743        }
744    }
745
746    #[test]
747    fn parse_multi_hash() {
748        let ast = parse("{a: b, c: d}").unwrap();
749        match ast {
750            Ast::MultiHash { elements, .. } => {
751                assert_eq!(elements.len(), 2);
752            }
753            _ => panic!("expected MultiHash"),
754        }
755    }
756
757    #[test]
758    fn parse_literal_string() {
759        let ast = parse("`\"hello\"`").unwrap();
760        assert!(matches!(ast, Ast::Literal { .. }));
761    }
762
763    #[test]
764    fn parse_literal_number() {
765        let ast = parse("`42`").unwrap();
766        assert!(matches!(ast, Ast::Literal { .. }));
767    }
768
769    #[test]
770    fn parse_literal_null() {
771        let ast = parse("`null`").unwrap();
772        assert!(matches!(ast, Ast::Literal { .. }));
773    }
774
775    #[test]
776    fn parse_raw_string() {
777        let ast = parse("'hello'").unwrap();
778        match ast {
779            Ast::Literal { value, .. } => {
780                assert_eq!(value, serde_json::json!("hello"));
781            }
782            _ => panic!("expected Literal from raw string"),
783        }
784    }
785
786    #[test]
787    fn parse_expref() {
788        let ast = parse("&foo").unwrap();
789        assert!(matches!(ast, Ast::Expref { .. }));
790    }
791
792    #[test]
793    fn parse_all_comparators() {
794        for (expr, cmp) in [
795            ("a == b", Comparator::Equal),
796            ("a != b", Comparator::NotEqual),
797            ("a > b", Comparator::GreaterThan),
798            ("a >= b", Comparator::GreaterThanEqual),
799            ("a < b", Comparator::LessThan),
800            ("a <= b", Comparator::LessThanEqual),
801        ] {
802            let ast = parse(expr).unwrap();
803            match ast {
804                Ast::Comparison { comparator, .. } => assert_eq!(comparator, cmp, "for {expr}"),
805                _ => panic!("expected Comparison for {expr}"),
806            }
807        }
808    }
809
810    #[test]
811    fn parse_quoted_identifier() {
812        let ast = parse("\"foo bar\"").unwrap();
813        match ast {
814            Ast::Field { name, .. } => assert_eq!(name, "foo bar"),
815            _ => panic!("expected Field from quoted identifier"),
816        }
817    }
818
819    #[test]
820    fn parse_parenthesized_expression() {
821        let ast = parse("(a)").unwrap();
822        assert!(matches!(ast, Ast::Field { .. }));
823    }
824
825    #[test]
826    fn error_empty_expression() {
827        let result = parse("");
828        assert!(result.is_err());
829    }
830
831    #[test]
832    fn error_unclosed_bracket() {
833        let result = parse("[0");
834        assert!(result.is_err());
835    }
836
837    #[test]
838    fn error_trailing_garbage() {
839        let result = parse("foo bar");
840        assert!(result.is_err());
841    }
842
843    #[test]
844    fn error_quoted_string_as_function() {
845        let result = parse("\"foo\"()");
846        assert!(result.is_err());
847    }
848
849    #[test]
850    fn error_trailing_comma_in_list() {
851        let result = parse("[a, b,]");
852        assert!(result.is_err());
853    }
854
855    #[test]
856    fn error_on_excessive_not_nesting() {
857        // Deeply nested prefix operators (recursive descent) must return a
858        // parse error, not overflow the stack.
859        let expr = format!("{}a", "!".repeat(300));
860        let result = parse(&expr);
861        assert!(result.is_err(), "deep nesting should error, not abort");
862        let msg = format!("{}", result.unwrap_err());
863        assert!(msg.contains("nesting"), "unexpected message: {msg}");
864    }
865
866    #[test]
867    fn error_on_excessive_paren_nesting() {
868        let expr = format!("{}a{}", "(".repeat(500), ")".repeat(500));
869        assert!(parse(&expr).is_err());
870    }
871
872    #[test]
873    fn error_on_excessive_subexpr_chain() {
874        // A long left-associative chain is rejected at parse time so it can
875        // never build an AST deep enough to overflow a downstream walker
876        // (interpreter, explain, drop).
877        let expr = format!("a{}", ".a".repeat(300));
878        let result = parse(&expr);
879        assert!(
880            result.is_err(),
881            "deep chain should error, not build a deep AST"
882        );
883        let msg = format!("{}", result.unwrap_err());
884        assert!(msg.contains("nesting"), "unexpected message: {msg}");
885    }
886
887    #[test]
888    fn parse_nesting_within_limit_ok() {
889        assert!(parse(&format!("{}a", "!".repeat(100))).is_ok());
890        assert!(parse(&format!("a{}", ".a".repeat(100))).is_ok());
891    }
892
893    #[cfg(feature = "let-expr")]
894    #[test]
895    fn parse_let_expression() {
896        let ast = parse("let $x = `1` in $x").unwrap();
897        assert!(matches!(ast, Ast::Let { .. }));
898    }
899
900    #[cfg(feature = "let-expr")]
901    #[test]
902    fn parse_variable_ref() {
903        // This will parse fine, but searching will fail (no scope)
904        let ast = parse("$x").unwrap();
905        assert!(matches!(ast, Ast::VariableRef { .. }));
906    }
907
908    #[cfg(feature = "let-expr")]
909    #[test]
910    fn error_let_missing_in() {
911        let result = parse("let $x = `1` $x");
912        assert!(result.is_err());
913    }
914}