Skip to main content

jsonata_core/
parser.rs

1// JSONata expression parser
2// Mirrors parser.js from the reference implementation
3
4#![allow(clippy::approx_constant)]
5
6use crate::ast::{AstNode, BinaryOp, PathStep, Stage, UnaryOp};
7use thiserror::Error;
8
9/// Parser errors
10#[derive(Error, Debug)]
11pub enum ParserError {
12    #[error("Unexpected token: {0}")]
13    UnexpectedToken(String),
14
15    #[error("Unexpected end of expression")]
16    UnexpectedEnd,
17
18    #[error("Invalid syntax: {0}")]
19    InvalidSyntax(String),
20
21    #[error("Invalid number: {0}")]
22    InvalidNumber(String),
23
24    #[error("Unclosed string literal")]
25    UnclosedString,
26
27    #[error("Invalid escape sequence: {0}")]
28    InvalidEscape(String),
29
30    #[error("Unclosed comment")]
31    UnclosedComment,
32
33    #[error("Unclosed backtick name")]
34    UnclosedBacktick,
35
36    #[error("Expected {expected}, found {found}")]
37    Expected { expected: String, found: String },
38
39    /// A JSONata-spec-coded parse error (S0214-S0217 for the %/@ operators).
40    /// Code is at the start of the message (matching the DateTimeError::Coded
41    /// convention from the datetime picture-string engine) so
42    /// test_reference_suite.py's extract_error_code() finds it.
43    #[error("{code}: {message}")]
44    Coded { code: &'static str, message: String },
45}
46
47impl ParserError {
48    /// The full display-ready message: `Coded` variants (e.g. S0214) are
49    /// already exactly "code: message" via `Display`, so they pass
50    /// through unchanged; every other variant gets a "Parse error: "
51    /// prefix added. Used by both the Python bindings (`src/lib.rs`) and
52    /// the `jsonata` CLI.
53    pub fn display_message(&self) -> String {
54        let msg = self.to_string();
55        if matches!(self, ParserError::Coded { .. }) {
56            msg
57        } else {
58            format!("Parse error: {}", msg)
59        }
60    }
61}
62
63#[cfg(test)]
64mod parser_error_display_message_tests {
65    use super::ParserError;
66
67    #[test]
68    fn coded_error_passes_through_unchanged() {
69        let e = ParserError::Coded {
70            code: "S0214",
71            message: "The % operator is invalid outside a path".to_string(),
72        };
73        assert_eq!(
74            e.display_message(),
75            "S0214: The % operator is invalid outside a path"
76        );
77    }
78
79    #[test]
80    fn uncoded_error_gets_parse_error_prefix() {
81        let e = ParserError::UnexpectedToken("foo".to_string());
82        assert_eq!(e.display_message(), "Parse error: Unexpected token: foo");
83    }
84}
85
86/// Token types for the lexer
87#[derive(Debug, Clone, PartialEq)]
88pub enum Token {
89    // Literals
90    String(String),
91    Number(f64),
92    True,
93    False,
94    Null,
95    Undefined,                                // The `undefined` keyword
96    Regex { pattern: String, flags: String }, // /pattern/flags
97
98    // Identifiers and operators
99    Identifier(String),
100    Variable(String),
101    ParentVariable(String), // $$ variables
102    Function,               // function keyword
103
104    // Operators
105    Plus,
106    Minus,
107    Star,
108    StarStar, // **
109    Slash,
110    Percent,
111    Equal,
112    NotEqual,
113    LessThan,
114    LessThanOrEqual,
115    GreaterThan,
116    GreaterThanOrEqual,
117    And,
118    Or,
119    In,
120    Ampersand,
121    Dot,
122    DotDot,
123    Question,
124    QuestionQuestion, // ??
125    QuestionColon,    // ?:
126    Colon,
127    ColonEqual, // :=
128    TildeArrow, // ~>
129
130    // Delimiters
131    LeftParen,
132    RightParen,
133    LeftBracket,
134    RightBracket,
135    LeftBrace,
136    RightBrace,
137    Comma,
138    Semicolon,
139    Caret, // ^ sort operator
140    Pipe,  // | transform operator
141
142    // Special
143    Hash, // # index binding operator
144    At,   // @ focus binding operator
145    Eof,
146}
147
148/// Lexer for tokenizing JSONata expressions
149pub struct Lexer {
150    input: Vec<char>,
151    position: usize,
152    last_token: Option<Token>,
153}
154
155impl Lexer {
156    pub fn new(input: String) -> Self {
157        Lexer {
158            input: input.chars().collect(),
159            position: 0,
160            last_token: None,
161        }
162    }
163
164    fn current(&self) -> Option<char> {
165        self.input.get(self.position).copied()
166    }
167
168    fn peek(&self, offset: usize) -> Option<char> {
169        self.input.get(self.position + offset).copied()
170    }
171
172    fn advance(&mut self) {
173        if self.position < self.input.len() {
174            self.position += 1;
175        }
176    }
177
178    fn skip_whitespace(&mut self) {
179        while self.current().is_some_and(|ch| ch.is_whitespace()) {
180            self.advance();
181        }
182    }
183
184    fn skip_comment(&mut self) -> Result<(), ParserError> {
185        // We're at '/', check if next is '*'
186        if self.peek(1) == Some('*') {
187            self.advance(); // skip '/'
188            self.advance(); // skip '*'
189
190            // Find closing */
191            loop {
192                match self.current() {
193                    None => return Err(ParserError::UnclosedComment),
194                    Some('*') if self.peek(1) == Some('/') => {
195                        self.advance(); // skip '*'
196                        self.advance(); // skip '/'
197                        break;
198                    }
199                    Some(_) => self.advance(),
200                }
201            }
202        }
203        Ok(())
204    }
205
206    fn read_string(&mut self, quote_char: char) -> Result<String, ParserError> {
207        let mut result = String::new();
208        self.advance(); // skip opening quote
209
210        loop {
211            match self.current() {
212                None => return Err(ParserError::UnclosedString),
213                Some(ch) if ch == quote_char => {
214                    self.advance(); // skip closing quote
215                    return Ok(result);
216                }
217                Some('\\') => {
218                    self.advance();
219                    match self.current() {
220                        None => return Err(ParserError::UnclosedString),
221                        Some('"') => result.push('"'),
222                        Some('\\') => result.push('\\'),
223                        Some('/') => result.push('/'),
224                        Some('b') => result.push('\u{0008}'),
225                        Some('f') => result.push('\u{000C}'),
226                        Some('n') => result.push('\n'),
227                        Some('r') => result.push('\r'),
228                        Some('t') => result.push('\t'),
229                        Some('u') => {
230                            // Unicode escape sequence \uXXXX
231                            self.advance();
232                            let mut hex = String::new();
233                            for _ in 0..4 {
234                                match self.current() {
235                                    Some(h) if h.is_ascii_hexdigit() => {
236                                        hex.push(h);
237                                        self.advance();
238                                    }
239                                    _ => {
240                                        return Err(ParserError::InvalidEscape(format!(
241                                            "\\u{}",
242                                            hex
243                                        )))
244                                    }
245                                }
246                            }
247                            let code = u32::from_str_radix(&hex, 16).unwrap();
248                            if (0xD800..=0xDBFF).contains(&code) {
249                                // High surrogate - expect \uXXXX low surrogate to follow
250                                if self.current() == Some('\\') {
251                                    self.advance();
252                                    if self.current() == Some('u') {
253                                        self.advance();
254                                        let mut low_hex = String::new();
255                                        for _ in 0..4 {
256                                            match self.current() {
257                                                Some(h) if h.is_ascii_hexdigit() => {
258                                                    low_hex.push(h);
259                                                    self.advance();
260                                                }
261                                                _ => {
262                                                    return Err(ParserError::InvalidEscape(
263                                                        format!("\\u{}", low_hex),
264                                                    ))
265                                                }
266                                            }
267                                        }
268                                        let low = u32::from_str_radix(&low_hex, 16).unwrap();
269                                        if (0xDC00..=0xDFFF).contains(&low) {
270                                            let cp =
271                                                0x10000 + (code - 0xD800) * 0x400 + (low - 0xDC00);
272                                            if let Some(ch) = char::from_u32(cp) {
273                                                result.push(ch);
274                                            } else {
275                                                return Err(ParserError::InvalidEscape(format!(
276                                                    "\\u{}\\u{}",
277                                                    hex, low_hex
278                                                )));
279                                            }
280                                        } else {
281                                            return Err(ParserError::InvalidEscape(format!(
282                                                "\\u{}\\u{}",
283                                                hex, low_hex
284                                            )));
285                                        }
286                                    } else {
287                                        return Err(ParserError::InvalidEscape(format!(
288                                            "\\u{}",
289                                            hex
290                                        )));
291                                    }
292                                } else {
293                                    return Err(ParserError::InvalidEscape(format!("\\u{}", hex)));
294                                }
295                            } else if let Some(ch) = char::from_u32(code) {
296                                result.push(ch);
297                            } else {
298                                return Err(ParserError::InvalidEscape(format!("\\u{}", hex)));
299                            }
300                            continue; // Don't advance again
301                        }
302                        Some(ch) => return Err(ParserError::InvalidEscape(format!("\\{}", ch))),
303                    }
304                    self.advance();
305                }
306                Some(ch) => {
307                    result.push(ch);
308                    self.advance();
309                }
310            }
311        }
312    }
313
314    fn read_number(&mut self) -> Result<f64, ParserError> {
315        let start = self.position;
316
317        // Integer part (no minus sign - negation is handled as unary operator)
318        if self.current() == Some('0') {
319            self.advance();
320        } else if self.current().is_some_and(|c| c.is_ascii_digit()) {
321            while self.current().is_some_and(|c| c.is_ascii_digit()) {
322                self.advance();
323            }
324        } else {
325            return Err(ParserError::InvalidNumber("Expected digit".to_string()));
326        }
327
328        // Fractional part
329        if self.current() == Some('.') && self.peek(1) != Some('.') {
330            // Only consume '.' if next char is not '.', to avoid consuming '..' range operator
331            self.advance();
332            if !self.current().is_some_and(|c| c.is_ascii_digit()) {
333                return Err(ParserError::InvalidNumber(
334                    "Expected digit after decimal point".to_string(),
335                ));
336            }
337            while self.current().is_some_and(|c| c.is_ascii_digit()) {
338                self.advance();
339            }
340        }
341
342        // Exponent part
343        if matches!(self.current(), Some('e') | Some('E')) {
344            self.advance();
345            if matches!(self.current(), Some('+') | Some('-')) {
346                self.advance();
347            }
348            if !self.current().is_some_and(|c| c.is_ascii_digit()) {
349                return Err(ParserError::InvalidNumber(
350                    "Expected digit in exponent".to_string(),
351                ));
352            }
353            while self.current().is_some_and(|c| c.is_ascii_digit()) {
354                self.advance();
355            }
356        }
357
358        let num_str: String = self.input[start..self.position].iter().collect();
359        let num: f64 = num_str
360            .parse()
361            .map_err(|_| ParserError::InvalidNumber(num_str.clone()))?;
362
363        // Check for overflow to infinity
364        if num.is_infinite() {
365            return Err(ParserError::InvalidNumber(format!(
366                "S0102: Number out of range: {}",
367                num_str
368            )));
369        }
370
371        Ok(num)
372    }
373
374    fn read_identifier(&mut self) -> String {
375        let start = self.position;
376
377        while let Some(ch) = self.current() {
378            // Continue if alphanumeric or underscore
379            if ch.is_alphanumeric() || ch == '_' {
380                self.advance();
381            } else {
382                break;
383            }
384        }
385
386        self.input[start..self.position].iter().collect()
387    }
388
389    fn read_backtick_name(&mut self) -> Result<String, ParserError> {
390        self.advance(); // skip opening backtick
391        let start = self.position;
392
393        while let Some(ch) = self.current() {
394            if ch == '`' {
395                let name: String = self.input[start..self.position].iter().collect();
396                self.advance(); // skip closing backtick
397                return Ok(name);
398            }
399            self.advance();
400        }
401
402        Err(ParserError::UnclosedBacktick)
403    }
404
405    fn read_regex(&mut self) -> Result<Token, ParserError> {
406        self.advance(); // skip opening /
407
408        // Read pattern
409        let mut pattern = String::new();
410        let mut escaped = false;
411
412        loop {
413            match self.current() {
414                None => return Err(ParserError::UnclosedString),
415                Some('/') if !escaped => {
416                    self.advance(); // skip closing /
417                    break;
418                }
419                Some('\\') if !escaped => {
420                    escaped = true;
421                    pattern.push('\\');
422                    self.advance();
423                }
424                Some(ch) => {
425                    escaped = false;
426                    pattern.push(ch);
427                    self.advance();
428                }
429            }
430        }
431
432        // Read flags (optional)
433        let mut flags = String::new();
434        while let Some(ch) = self.current() {
435            if ch.is_alphabetic() {
436                flags.push(ch);
437                self.advance();
438            } else {
439                break;
440            }
441        }
442
443        Ok(Token::Regex { pattern, flags })
444    }
445
446    fn emit_token(&mut self, token: Token) -> Result<Token, ParserError> {
447        self.last_token = Some(token.clone());
448        Ok(token)
449    }
450
451    pub fn next_token(&mut self) -> Result<Token, ParserError> {
452        loop {
453            self.skip_whitespace();
454
455            match self.current() {
456                None => return Ok(Token::Eof),
457
458                // Comments
459                Some('/') if self.peek(1) == Some('*') => {
460                    self.skip_comment()?;
461                    continue; // Skip whitespace again after comment
462                }
463
464                // String literals
465                Some('"') => {
466                    let s = self.read_string('"')?;
467                    return self.emit_token(Token::String(s));
468                }
469                Some('\'') => {
470                    let s = self.read_string('\'')?;
471                    return self.emit_token(Token::String(s));
472                }
473
474                // Backtick names
475                Some('`') => {
476                    let name = self.read_backtick_name()?;
477                    return self.emit_token(Token::Identifier(name));
478                }
479
480                // Numbers (positive only - negation handled as unary operator)
481                Some(ch) if ch.is_ascii_digit() => {
482                    let num = self.read_number()?;
483                    return self.emit_token(Token::Number(num));
484                }
485
486                // Variables (start with $)
487                Some('$') if self.peek(1) == Some('$') => {
488                    // $$ - parent variable
489                    self.advance(); // skip first $
490                    self.advance(); // skip second $
491                    let name = self.read_identifier();
492                    return self.emit_token(Token::ParentVariable(name));
493                }
494                Some('$') => {
495                    self.advance();
496                    let name = self.read_identifier();
497                    return self.emit_token(Token::Variable(name));
498                }
499
500                // Two-character operators
501                Some('.') if self.peek(1) == Some('.') => {
502                    self.advance();
503                    self.advance();
504                    return Ok(Token::DotDot);
505                }
506                Some(':') if self.peek(1) == Some('=') => {
507                    self.advance();
508                    self.advance();
509                    return Ok(Token::ColonEqual);
510                }
511                Some('!') if self.peek(1) == Some('=') => {
512                    self.advance();
513                    self.advance();
514                    return Ok(Token::NotEqual);
515                }
516                Some('>') if self.peek(1) == Some('=') => {
517                    self.advance();
518                    self.advance();
519                    return Ok(Token::GreaterThanOrEqual);
520                }
521                Some('<') if self.peek(1) == Some('=') => {
522                    self.advance();
523                    self.advance();
524                    return Ok(Token::LessThanOrEqual);
525                }
526                Some('~') if self.peek(1) == Some('>') => {
527                    self.advance();
528                    self.advance();
529                    return self.emit_token(Token::TildeArrow);
530                }
531
532                // Single-character operators and delimiters
533                Some('(') => {
534                    self.advance();
535                    return Ok(Token::LeftParen);
536                }
537                Some(')') => {
538                    self.advance();
539                    return self.emit_token(Token::RightParen);
540                }
541                Some('[') => {
542                    self.advance();
543                    return Ok(Token::LeftBracket);
544                }
545                Some(']') => {
546                    self.advance();
547                    return self.emit_token(Token::RightBracket);
548                }
549                Some('{') => {
550                    self.advance();
551                    return Ok(Token::LeftBrace);
552                }
553                Some('}') => {
554                    self.advance();
555                    return self.emit_token(Token::RightBrace);
556                }
557                Some(',') => {
558                    self.advance();
559                    return self.emit_token(Token::Comma);
560                }
561                Some(';') => {
562                    self.advance();
563                    return self.emit_token(Token::Semicolon);
564                }
565                Some(':') => {
566                    self.advance();
567                    return Ok(Token::Colon);
568                }
569                Some('?') if self.peek(1) == Some('?') => {
570                    self.advance();
571                    self.advance();
572                    return Ok(Token::QuestionQuestion);
573                }
574                Some('?') if self.peek(1) == Some(':') => {
575                    self.advance();
576                    self.advance();
577                    return Ok(Token::QuestionColon);
578                }
579                Some('?') => {
580                    self.advance();
581                    return Ok(Token::Question);
582                }
583                Some('λ') => {
584                    // Lambda symbol (alternative to "function" keyword)
585                    self.advance();
586                    return Ok(Token::Function);
587                }
588                Some('.') => {
589                    self.advance();
590                    return Ok(Token::Dot);
591                }
592                Some('+') => {
593                    self.advance();
594                    return Ok(Token::Plus);
595                }
596                Some('-') => {
597                    self.advance();
598                    return Ok(Token::Minus);
599                }
600                Some('*') if self.peek(1) == Some('*') => {
601                    self.advance();
602                    self.advance();
603                    return Ok(Token::StarStar);
604                }
605                Some('*') => {
606                    self.advance();
607                    return Ok(Token::Star);
608                }
609                Some('/') => {
610                    // Determine if this is a regex literal or division operator
611                    // Regex literals can appear after:
612                    // - Start of expression (last_token is None)
613                    // - Operators: (, [, {, ,, ;, :, =, !=, <, >, <=, >=, +, -, *, %, &, |, ~, !, ?
614                    // - Keywords: and, or, in, function
615                    // Division operator appears after:
616                    // - Values: ), ], }, identifiers, variables, numbers, strings, etc.
617
618                    let is_regex = match &self.last_token {
619                        None => true, // Start of expression
620                        Some(Token::LeftParen)
621                        | Some(Token::LeftBracket)
622                        | Some(Token::LeftBrace) => true,
623                        Some(Token::Comma) | Some(Token::Semicolon) | Some(Token::Colon) => true,
624                        Some(Token::Equal) | Some(Token::NotEqual) => true,
625                        Some(Token::LessThan) | Some(Token::LessThanOrEqual) => true,
626                        Some(Token::GreaterThan) | Some(Token::GreaterThanOrEqual) => true,
627                        Some(Token::Plus) | Some(Token::Minus) | Some(Token::Star)
628                        | Some(Token::Percent) => true,
629                        Some(Token::Ampersand)
630                        | Some(Token::Question)
631                        | Some(Token::TildeArrow) => true,
632                        Some(Token::ColonEqual)
633                        | Some(Token::QuestionQuestion)
634                        | Some(Token::QuestionColon) => true,
635                        Some(Token::And) | Some(Token::Or) | Some(Token::In) => true,
636                        Some(Token::Function) => true,
637                        Some(Token::Identifier(s)) if s == "and" || s == "or" || s == "in" => true,
638                        _ => false, // After values, treat as division
639                    };
640
641                    if is_regex {
642                        let tok = self.read_regex()?;
643                        return self.emit_token(tok);
644                    } else {
645                        self.advance();
646                        return self.emit_token(Token::Slash);
647                    }
648                }
649                Some('%') => {
650                    self.advance();
651                    return Ok(Token::Percent);
652                }
653                Some('^') => {
654                    self.advance();
655                    return Ok(Token::Caret);
656                }
657                Some('#') => {
658                    self.advance();
659                    return Ok(Token::Hash);
660                }
661                Some('@') => {
662                    self.advance();
663                    return Ok(Token::At);
664                }
665                Some('=') => {
666                    self.advance();
667                    return Ok(Token::Equal);
668                }
669                Some('<') => {
670                    self.advance();
671                    return Ok(Token::LessThan);
672                }
673                Some('>') => {
674                    self.advance();
675                    return Ok(Token::GreaterThan);
676                }
677                Some('&') => {
678                    self.advance();
679                    return Ok(Token::Ampersand);
680                }
681                Some('|') => {
682                    self.advance();
683                    return Ok(Token::Pipe);
684                }
685
686                // Identifiers and keywords
687                Some(ch) if ch.is_alphabetic() || ch == '_' => {
688                    let ident = self.read_identifier();
689                    let tok = match ident.as_str() {
690                        "true" => Token::True,
691                        "false" => Token::False,
692                        "null" => Token::Null,
693                        "undefined" => Token::Undefined,
694                        "function" => Token::Function,
695                        // "and", "or", "in" are now contextual keywords (handled in parser)
696                        _ => Token::Identifier(ident),
697                    };
698                    return self.emit_token(tok);
699                }
700
701                Some(ch) => {
702                    return Err(ParserError::UnexpectedToken(ch.to_string()));
703                }
704            }
705        }
706    }
707}
708
709// Same constants `evaluate_internal` (src/evaluator.rs) and
710// ast_transform.rs's `resolve_ancestry` use for their analogous
711// native-stack safety nets -- see those for the full rationale. Kept as
712// separate constants (rather than reused from those modules) since this
713// module has no dependency on them and the guards are conceptually
714// independent safety nets.
715const PARSER_RED_ZONE: usize = 128 * 1024;
716const PARSER_GROW_STACK_SIZE: usize = 8 * 1024 * 1024;
717
718// Same ceiling as ast_transform.rs's MAX_TRANSFORM_DEPTH (1000), chosen for
719// consistency: a tree that passes THIS guard (enforced earlier, at parse
720// construction time) should also comfortably pass ast_transform's guard
721// afterward, which stays in place as harmless defense-in-depth. Empirically
722// verified (this task, scratch test, 1MB-stack thread, release build) that
723// n=999 succeeds and n=1001 fails gracefully with U1002 for BOTH the
724// deeply-parenthesized case (real recursive descent per level, the more
725// expensive of the two shapes since `stacker::maybe_grow`'s growth still
726// costs real stack per `parse_expression` call) and the flat-arithmetic
727// loop-driven case -- so 1000 has essentially zero headroom by construction
728// (that's the point: it's an exact ceiling match with ast_transform, not an
729// independently-tuned one) but is not itself unsafe at that ceiling.
730const MAX_PARSE_DEPTH: usize = 1000;
731
732/// Parser for JSONata expressions using Pratt parsing
733pub struct Parser {
734    lexer: Lexer,
735    current_token: Token,
736    /// Current expression-nesting depth, shared by two structurally
737    /// different growth patterns (see `parse_expression`/
738    /// `parse_expression_impl`): recursive descent (parens, unary
739    /// operands, array/object elements, function args, blocks -- depth
740    /// grows by 1 per actual recursive call to `parse_expression` and
741    /// shrinks back on return) and loop-driven left-nesting (flat
742    /// `1+1+1+...` chains -- a SINGLE call's `loop { .. }` reassigns `lhs`
743    /// to a deeper node every iteration without a new recursive call).
744    /// Both must be bounded by this one counter; see `MAX_PARSE_DEPTH`.
745    depth: usize,
746}
747
748impl Parser {
749    pub fn new(input: String) -> Result<Self, ParserError> {
750        let mut lexer = Lexer::new(input);
751        let current_token = lexer.next_token()?;
752        Ok(Parser {
753            lexer,
754            current_token,
755            depth: 0,
756        })
757    }
758
759    fn advance(&mut self) -> Result<(), ParserError> {
760        self.current_token = self.lexer.next_token()?;
761        Ok(())
762    }
763
764    fn expect(&mut self, expected: Token) -> Result<(), ParserError> {
765        if std::mem::discriminant(&self.current_token) == std::mem::discriminant(&expected) {
766            self.advance()?;
767            Ok(())
768        } else {
769            Err(ParserError::Expected {
770                expected: format!("{:?}", expected),
771                found: format!("{:?}", self.current_token),
772            })
773        }
774    }
775
776    /// Get the binding power (precedence) for the current token
777    fn binding_power(&self, token: &Token) -> Option<(u8, u8)> {
778        // Returns (left_bp, right_bp) for left and right binding power
779        // Higher numbers = higher precedence
780        match token {
781            // Contextual keywords (treated as operators when in infix position)
782            Token::Identifier(name) if name == "or" => Some((25, 26)),
783            Token::Identifier(name) if name == "and" => Some((30, 31)),
784            Token::Identifier(name) if name == "in" => Some((40, 41)),
785            // Regular operators
786            Token::Or => Some((25, 26)),
787            Token::And => Some((30, 31)),
788            Token::Equal
789            | Token::NotEqual
790            | Token::LessThan
791            | Token::LessThanOrEqual
792            | Token::GreaterThan
793            | Token::GreaterThanOrEqual
794            | Token::In => Some((40, 41)),
795            Token::Ampersand => Some((50, 51)),
796            Token::Plus | Token::Minus => Some((50, 51)),
797            Token::Star | Token::Slash | Token::Percent => Some((60, 61)),
798            Token::Dot => Some((75, 85)), // Right bp is higher to prevent consuming postfix operators
799            Token::LeftBracket => Some((80, 81)),
800            Token::LeftParen => Some((80, 81)),
801            Token::LeftBrace => Some((80, 81)), // Object constructor as postfix
802            Token::Caret => Some((80, 81)),     // Sort operator as postfix
803            Token::Hash => Some((80, 81)),      // Index binding operator as postfix
804            Token::At => Some((80, 81)),        // Focus binding operator as postfix
805            Token::Question => Some((20, 21)),
806            Token::QuestionQuestion => Some((15, 16)), // Coalescing operator
807            Token::QuestionColon => Some((15, 16)),    // Default operator
808            Token::DotDot => Some((20, 21)),
809            Token::ColonEqual => Some((10, 9)), // Right associative
810            Token::TildeArrow => Some((70, 71)), // Chain/pipe operator
811            _ => None,
812        }
813    }
814
815    /// Parse a function signature: <param-types:return-type>
816    fn parse_signature(&mut self) -> Result<String, ParserError> {
817        // Expect <
818        if self.current_token != Token::LessThan {
819            return Err(ParserError::Expected {
820                expected: "<".to_string(),
821                found: format!("{:?}", self.current_token),
822            });
823        }
824
825        // Build signature string by collecting characters until we find >
826        let mut signature = String::from("<");
827        self.advance()?; // skip <
828
829        // Collect all characters until we find the closing >
830        // This is a bit tricky because we need to handle nested <> for array types like a<s>
831        let mut depth = 1;
832
833        while depth > 0 && self.current_token != Token::Eof {
834            match &self.current_token {
835                Token::LessThan => {
836                    signature.push('<');
837                    depth += 1;
838                    self.advance()?;
839                }
840                Token::GreaterThan => {
841                    depth -= 1;
842                    if depth > 0 {
843                        signature.push('>');
844                    }
845                    self.advance()?;
846                }
847                Token::Minus => {
848                    signature.push('-');
849                    self.advance()?;
850                }
851                Token::Plus => {
852                    signature.push('+');
853                    self.advance()?;
854                }
855                Token::Colon => {
856                    signature.push(':');
857                    self.advance()?;
858                }
859                Token::Question => {
860                    signature.push('?');
861                    self.advance()?;
862                }
863                Token::QuestionColon => {
864                    // ?:  gets tokenized as QuestionColon in signatures like s?:s
865                    signature.push('?');
866                    signature.push(':');
867                    self.advance()?;
868                }
869                Token::Identifier(s) => {
870                    signature.push_str(s);
871                    self.advance()?;
872                }
873                Token::LeftParen | Token::RightParen => {
874                    // Handle parentheses for union types like (ns)
875                    let c = if self.current_token == Token::LeftParen {
876                        '('
877                    } else {
878                        ')'
879                    };
880                    signature.push(c);
881                    self.advance()?;
882                }
883                _ => {
884                    return Err(ParserError::UnexpectedToken(format!(
885                        "Unexpected token in signature: {:?}",
886                        self.current_token
887                    )));
888                }
889            }
890        }
891
892        signature.push('>');
893        Ok(signature)
894    }
895
896    /// Parse a primary expression (literals, identifiers, variables, grouping)
897    fn parse_primary(&mut self) -> Result<AstNode, ParserError> {
898        match &self.current_token {
899            Token::String(s) => {
900                let value = s.clone();
901                self.advance()?;
902                Ok(AstNode::String(value))
903            }
904            Token::Number(n) => {
905                let value = *n;
906                self.advance()?;
907                Ok(AstNode::Number(value))
908            }
909            Token::True => {
910                self.advance()?;
911                Ok(AstNode::Boolean(true))
912            }
913            Token::False => {
914                self.advance()?;
915                Ok(AstNode::Boolean(false))
916            }
917            Token::Null => {
918                self.advance()?;
919                Ok(AstNode::Null)
920            }
921            Token::Undefined => {
922                self.advance()?;
923                Ok(AstNode::Undefined)
924            }
925            Token::Regex { pattern, flags } => {
926                let pat = pattern.clone();
927                let flg = flags.clone();
928                self.advance()?;
929                Ok(AstNode::Regex {
930                    pattern: pat,
931                    flags: flg,
932                })
933            }
934            Token::Identifier(name) => {
935                let name = name.clone();
936                self.advance()?;
937                Ok(AstNode::Path {
938                    steps: vec![PathStep::new(AstNode::Name(name))],
939                })
940            }
941            Token::Variable(name) => {
942                let name = name.clone();
943                self.advance()?;
944                Ok(AstNode::Variable(name))
945            }
946            Token::ParentVariable(name) => {
947                let name = name.clone();
948                self.advance()?;
949                Ok(AstNode::ParentVariable(name))
950            }
951            Token::LeftParen => {
952                self.advance()?; // skip '('
953
954                // Check for empty parentheses () which means undefined
955                if self.current_token == Token::RightParen {
956                    self.advance()?;
957                    return Ok(AstNode::Undefined);
958                }
959
960                // Parse block expressions (separated by semicolons)
961                // NOTE: Parentheses ALWAYS create a block in JSONata, even with a single expression.
962                // This is important for variable scoping - ( $x := value ) creates a new scope.
963                let mut expressions = vec![self.parse_expression(0)?];
964
965                while self.current_token == Token::Semicolon {
966                    self.advance()?;
967                    if self.current_token == Token::RightParen {
968                        break;
969                    }
970                    expressions.push(self.parse_expression(0)?);
971                }
972
973                self.expect(Token::RightParen)?;
974
975                // Always create a block, matching JavaScript implementation
976                Ok(AstNode::Block(expressions))
977            }
978            Token::LeftBracket => {
979                self.advance()?; // skip '['
980
981                let mut elements = Vec::new();
982
983                if self.current_token != Token::RightBracket {
984                    loop {
985                        let element = self.parse_expression(0)?;
986                        elements.push(element);
987
988                        if self.current_token != Token::Comma {
989                            break;
990                        }
991                        self.advance()?;
992                    }
993                }
994
995                self.expect(Token::RightBracket)?;
996                Ok(AstNode::Array(elements))
997            }
998            Token::LeftBrace => {
999                self.advance()?; // skip '{'
1000
1001                let mut pairs = Vec::new();
1002
1003                if self.current_token != Token::RightBrace {
1004                    loop {
1005                        let key = self.parse_expression(0)?;
1006                        self.expect(Token::Colon)?;
1007                        let value = self.parse_expression(0)?;
1008                        pairs.push((key, value));
1009
1010                        if self.current_token != Token::Comma {
1011                            break;
1012                        }
1013                        self.advance()?;
1014                    }
1015                }
1016
1017                self.expect(Token::RightBrace)?;
1018                Ok(AstNode::Object(pairs))
1019            }
1020            Token::Pipe => {
1021                // Transform operator: |location|update[,delete]|
1022                self.advance()?; // skip first '|'
1023
1024                // Parse location expression
1025                let location = self.parse_expression(0)?;
1026
1027                // Expect second '|'
1028                self.expect(Token::Pipe)?;
1029
1030                // Parse update expression (object constructor)
1031                let update = self.parse_expression(0)?;
1032
1033                // Check for optional delete part
1034                let delete = if self.current_token == Token::Comma {
1035                    self.advance()?; // skip comma
1036                    Some(Box::new(self.parse_expression(0)?))
1037                } else {
1038                    None
1039                };
1040
1041                // Expect final '|'
1042                self.expect(Token::Pipe)?;
1043
1044                Ok(AstNode::Transform {
1045                    location: Box::new(location),
1046                    update: Box::new(update),
1047                    delete,
1048                })
1049            }
1050            Token::Minus => {
1051                self.advance()?;
1052                let operand = self.parse_expression(70)?; // High precedence for unary
1053                Ok(AstNode::Unary {
1054                    op: UnaryOp::Negate,
1055                    operand: Box::new(operand),
1056                })
1057            }
1058            Token::Star => {
1059                // Wildcard operator in primary position
1060                self.advance()?;
1061                Ok(AstNode::Wildcard)
1062            }
1063            Token::StarStar => {
1064                // Descendant operator in primary position
1065                self.advance()?;
1066                Ok(AstNode::Descendant)
1067            }
1068            Token::Percent => {
1069                // Parent operator in primary position. Label is resolved by
1070                // ast_transform -- this empty string is never observed by
1071                // the evaluator (ast_transform fills every AstNode::Parent
1072                // ("") with a real label or errors S0217).
1073                self.advance()?;
1074                Ok(AstNode::Parent(String::new()))
1075            }
1076            Token::Function => {
1077                // Parse lambda: function($param1, $param2, ...) { body }
1078                self.advance()?; // skip 'function'
1079                self.expect(Token::LeftParen)?;
1080
1081                // Parse parameters
1082                let mut params = Vec::new();
1083                if self.current_token != Token::RightParen {
1084                    loop {
1085                        match &self.current_token {
1086                            Token::Variable(name) => {
1087                                params.push(name.clone());
1088                                self.advance()?;
1089                            }
1090                            _ => {
1091                                return Err(ParserError::Expected {
1092                                    expected: "parameter name".to_string(),
1093                                    found: format!("{:?}", self.current_token),
1094                                })
1095                            }
1096                        }
1097
1098                        if self.current_token != Token::Comma {
1099                            break;
1100                        }
1101                        self.advance()?; // skip comma
1102                    }
1103                }
1104
1105                self.expect(Token::RightParen)?;
1106
1107                // Check for optional signature: <type-type:returntype>
1108                let signature = if self.current_token == Token::LessThan {
1109                    Some(self.parse_signature()?)
1110                } else {
1111                    None
1112                };
1113
1114                self.expect(Token::LeftBrace)?;
1115
1116                // Parse body
1117                let body = self.parse_expression(0)?;
1118
1119                self.expect(Token::RightBrace)?;
1120
1121                // Apply tail call optimization to the body
1122                let (optimized_body, is_thunk) = Self::tail_call_optimize(body);
1123
1124                Ok(AstNode::Lambda {
1125                    params,
1126                    body: Box::new(optimized_body),
1127                    signature,
1128                    thunk: is_thunk,
1129                })
1130            }
1131            _ => Err(ParserError::UnexpectedToken(format!(
1132                "{:?}",
1133                self.current_token
1134            ))),
1135        }
1136    }
1137
1138    /// Bumps the shared nesting-depth counter and errors out gracefully
1139    /// (`U1002`) once `MAX_PARSE_DEPTH` is exceeded, instead of letting
1140    /// either growth pattern (recursive descent or loop-driven
1141    /// left-nesting -- see `Parser::depth`'s doc comment) overflow the
1142    /// native stack. New error code `U1002` (no jsonata-js equivalent):
1143    /// reuses the same code `ast_transform.rs`'s `resolve_ancestry` guard
1144    /// introduced for the same conceptual guard, enforced earlier in the
1145    /// pipeline (at raw-parse construction time, before ast_transform ever
1146    /// runs).
1147    fn bump_parse_depth(&mut self) -> Result<(), ParserError> {
1148        self.depth += 1;
1149        if self.depth > MAX_PARSE_DEPTH {
1150            return Err(ParserError::Coded {
1151                code: "U1002",
1152                message: format!(
1153                    "Stack overflow - maximum expression nesting depth ({}) exceeded while parsing expression",
1154                    MAX_PARSE_DEPTH
1155                ),
1156            });
1157        }
1158        Ok(())
1159    }
1160
1161    /// Parse an expression with Pratt parsing.
1162    ///
1163    /// Thin wrapper around `parse_expression_impl` that owns the
1164    /// entry-depth push/pop (matching `evaluate_internal`'s
1165    /// `stacker::maybe_grow` pattern in src/evaluator.rs): every actual
1166    /// recursive call to `parse_expression` (parens, unary operands,
1167    /// array/object elements, function args, blocks, ...) bumps `self.depth`
1168    /// on entry and restores it to the pre-call value on return, so depth
1169    /// accumulated while parsing one subtree never leaks into an unrelated
1170    /// sibling subtree parsed afterward. Loop-driven left-nesting (the flat
1171    /// `1+1+1+...` case, where `parse_expression_impl`'s own `loop { .. }`
1172    /// reassigns `lhs` to a deeper node every iteration WITHOUT a new
1173    /// recursive call) is bounded separately, by additional `bump_parse_depth`
1174    /// calls at each `lhs = ..` reassignment site inside that loop.
1175    fn parse_expression(&mut self, min_bp: u8) -> Result<AstNode, ParserError> {
1176        let entry_depth = self.depth;
1177        self.bump_parse_depth()?;
1178
1179        let result = stacker::maybe_grow(PARSER_RED_ZONE, PARSER_GROW_STACK_SIZE, || {
1180            self.parse_expression_impl(min_bp)
1181        });
1182
1183        self.depth = entry_depth;
1184        result
1185    }
1186
1187    fn parse_expression_impl(&mut self, min_bp: u8) -> Result<AstNode, ParserError> {
1188        let mut lhs = self.parse_primary()?;
1189
1190        loop {
1191            // Check for end of expression
1192            if matches!(
1193                self.current_token,
1194                Token::Eof
1195                    | Token::RightParen
1196                    | Token::RightBracket
1197                    | Token::RightBrace
1198                    | Token::Comma
1199                    | Token::Semicolon
1200                    | Token::Colon
1201            ) {
1202                break;
1203            }
1204
1205            // Get binding power for current operator
1206            let (left_bp, right_bp) = match self.binding_power(&self.current_token) {
1207                Some(bp) => bp,
1208                None => break,
1209            };
1210
1211            if left_bp < min_bp {
1212                break;
1213            }
1214
1215            // Handle infix operators
1216            match &self.current_token {
1217                Token::Dot => {
1218                    self.advance()?;
1219
1220                    // Check for .[expr] syntax (array grouping)
1221                    if self.current_token == Token::LeftBracket {
1222                        self.advance()?;
1223
1224                        // Parse the array elements
1225                        let mut elements = Vec::new();
1226                        if self.current_token != Token::RightBracket {
1227                            loop {
1228                                elements.push(self.parse_expression(0)?);
1229                                if self.current_token != Token::Comma {
1230                                    break;
1231                                }
1232                                self.advance()?;
1233                            }
1234                        }
1235
1236                        self.expect(Token::RightBracket)?;
1237
1238                        // Create ArrayGroup node as a path step
1239                        let mut steps = match lhs {
1240                            AstNode::Path { steps } => steps,
1241                            _ => vec![PathStep::new(lhs)],
1242                        };
1243
1244                        steps.push(PathStep::new(AstNode::ArrayGroup(elements)));
1245                        self.bump_parse_depth()?;
1246                        lhs = AstNode::Path { steps };
1247                    } else if self.current_token == Token::LeftParen {
1248                        // Check for .(expr) syntax (function application)
1249                        self.advance()?;
1250
1251                        // Empty block `.()`: a parenthesised step with no
1252                        // expression (e.g. `Account.Order.().%`). jsonata-js
1253                        // treats `()` as an empty block that evaluates to
1254                        // undefined; keep it as a `FunctionApplication` of an
1255                        // empty `Block` so the ancestry pass can walk past it.
1256                        if self.current_token == Token::RightParen {
1257                            self.advance()?;
1258                            let mut steps = match lhs {
1259                                AstNode::Path { steps } => steps,
1260                                _ => vec![PathStep::new(lhs)],
1261                            };
1262                            steps.push(PathStep::new(AstNode::FunctionApplication(Box::new(
1263                                AstNode::Block(Vec::new()),
1264                            ))));
1265                            self.bump_parse_depth()?;
1266                            lhs = AstNode::Path { steps };
1267                            continue;
1268                        }
1269
1270                        // Parse the expression(s) to apply - may be block with semicolons
1271                        let mut expressions = vec![self.parse_expression(0)?];
1272
1273                        while self.current_token == Token::Semicolon {
1274                            self.advance()?;
1275                            if self.current_token == Token::RightParen {
1276                                break;
1277                            }
1278                            expressions.push(self.parse_expression(0)?);
1279                        }
1280
1281                        self.expect(Token::RightParen)?;
1282
1283                        // Wrap in Block if multiple expressions, otherwise use single expression
1284                        let expr = if expressions.len() == 1 {
1285                            expressions.into_iter().next().unwrap()
1286                        } else {
1287                            AstNode::Block(expressions)
1288                        };
1289
1290                        // Create FunctionApplication node as a path step
1291                        let mut steps = match lhs {
1292                            AstNode::Path { steps } => steps,
1293                            _ => vec![PathStep::new(lhs)],
1294                        };
1295
1296                        steps.push(PathStep::new(AstNode::FunctionApplication(Box::new(expr))));
1297                        self.bump_parse_depth()?;
1298                        lhs = AstNode::Path { steps };
1299                    } else {
1300                        // Normal dot path
1301                        let rhs = self.parse_expression(right_bp)?;
1302
1303                        // Flatten path expressions
1304                        let mut steps = match lhs {
1305                            AstNode::Path { steps } => steps,
1306                            // Convert string literals to field names when used as first step in path
1307                            // e.g., "foo".bar should behave like foo.bar
1308                            AstNode::String(field_name) => {
1309                                vec![PathStep::new(AstNode::Name(field_name))]
1310                            }
1311                            _ => vec![PathStep::new(lhs)],
1312                        };
1313
1314                        // S0213: The literal value cannot be used as a step within a path expression
1315                        // Numbers, booleans (true/false), and null cannot be path steps
1316                        match &rhs {
1317                            AstNode::Number(n) => {
1318                                return Err(ParserError::InvalidSyntax(
1319                                    format!("S0213: The literal value {} cannot be used as a step within a path expression", n),
1320                                ));
1321                            }
1322                            AstNode::Boolean(b) => {
1323                                return Err(ParserError::InvalidSyntax(
1324                                    format!("S0213: The literal value {} cannot be used as a step within a path expression", b),
1325                                ));
1326                            }
1327                            AstNode::Null => {
1328                                return Err(ParserError::InvalidSyntax(
1329                                    "S0213: The literal value null cannot be used as a step within a path expression".to_string(),
1330                                ));
1331                            }
1332                            _ => {}
1333                        }
1334
1335                        match rhs {
1336                            AstNode::Path {
1337                                steps: mut rhs_steps,
1338                            } => {
1339                                steps.append(&mut rhs_steps);
1340                            }
1341                            // Convert string literals to field names when they appear after a dot
1342                            // e.g., $."Field.Name" should access a property named "Field.Name"
1343                            AstNode::String(field_name) => {
1344                                steps.push(PathStep::new(AstNode::Name(field_name)));
1345                            }
1346                            _ => steps.push(PathStep::new(rhs)),
1347                        }
1348
1349                        // Check for following predicates and attach as stages to the last step
1350                        // This implements JSONata semantics where foo.bar[0] has [0] apply during extraction
1351                        while self.current_token == Token::LeftBracket {
1352                            self.advance()?;
1353
1354                            let predicate_expr = if self.current_token == Token::RightBracket {
1355                                // Empty brackets []
1356                                Box::new(AstNode::Boolean(true))
1357                            } else {
1358                                // Normal predicate expression
1359                                let pred = self.parse_expression(0)?;
1360                                Box::new(pred)
1361                            };
1362
1363                            self.expect(Token::RightBracket)?;
1364
1365                            // Attach predicate as stage to the last step
1366                            if let Some(last_step) = steps.last_mut() {
1367                                last_step.stages.push(Stage::Filter(predicate_expr));
1368                            }
1369                        }
1370
1371                        self.bump_parse_depth()?;
1372                        lhs = AstNode::Path { steps };
1373                    }
1374                }
1375                Token::LeftBracket => {
1376                    // S0209: A predicate cannot follow a grouping expression in a step
1377                    // Check if lhs is an ObjectTransform (grouping expression)
1378                    if matches!(lhs, AstNode::ObjectTransform { .. }) {
1379                        return Err(ParserError::InvalidSyntax(
1380                            "S0209: A predicate cannot follow a grouping expression in a step"
1381                                .to_string(),
1382                        ));
1383                    }
1384
1385                    self.advance()?;
1386
1387                    // Predicates in postfix position are always separate steps
1388                    // Predicates as stages are only attached during DOT operator parsing
1389                    if self.current_token == Token::RightBracket {
1390                        // Empty brackets []
1391                        self.advance()?;
1392
1393                        let mut steps = match lhs {
1394                            AstNode::Path { steps } => steps,
1395                            _ => vec![PathStep::new(lhs)],
1396                        };
1397
1398                        steps.push(PathStep::new(AstNode::Predicate(Box::new(
1399                            AstNode::Boolean(true),
1400                        ))));
1401                        self.bump_parse_depth()?;
1402                        lhs = AstNode::Path { steps };
1403                    } else {
1404                        // Normal predicate
1405                        let predicate = self.parse_expression(0)?;
1406                        self.expect(Token::RightBracket)?;
1407
1408                        let mut steps = match lhs {
1409                            AstNode::Path { steps } => steps,
1410                            _ => vec![PathStep::new(lhs)],
1411                        };
1412
1413                        steps.push(PathStep::new(AstNode::Predicate(Box::new(predicate))));
1414                        self.bump_parse_depth()?;
1415                        lhs = AstNode::Path { steps };
1416                    }
1417                }
1418                Token::LeftParen => {
1419                    self.advance()?;
1420
1421                    let mut args = Vec::new();
1422
1423                    if self.current_token != Token::RightParen {
1424                        loop {
1425                            // Check for ? placeholder (partial application)
1426                            if self.current_token == Token::Question {
1427                                args.push(AstNode::Placeholder);
1428                                self.advance()?;
1429                            } else {
1430                                args.push(self.parse_expression(0)?);
1431                            }
1432
1433                            if self.current_token != Token::Comma {
1434                                break;
1435                            }
1436                            self.advance()?;
1437                        }
1438                    }
1439
1440                    self.expect(Token::RightParen)?;
1441
1442                    // Check if lhs is a lambda or callable expression
1443                    match lhs {
1444                        // Direct invocations: lambda(args), block(args), chained calls, function result calls
1445                        AstNode::Lambda { .. }
1446                        | AstNode::Block(_)
1447                        | AstNode::Call { .. }
1448                        | AstNode::Function { .. } => {
1449                            self.bump_parse_depth()?;
1450                            lhs = AstNode::Call {
1451                                procedure: Box::new(lhs),
1452                                args,
1453                            };
1454                        }
1455                        ref other_lhs => {
1456                            // Extract function name from lhs
1457                            match other_lhs {
1458                                // Handle bare function names: uppercase()
1459                                AstNode::Path { steps } if steps.len() == 1 => {
1460                                    let name = match &steps[0].node {
1461                                        AstNode::Name(s) => s.clone(),
1462                                        _ => {
1463                                            return Err(ParserError::InvalidSyntax(
1464                                                "Invalid function name".to_string(),
1465                                            ))
1466                                        }
1467                                    };
1468                                    self.bump_parse_depth()?;
1469                                    lhs = AstNode::Function {
1470                                        name,
1471                                        args,
1472                                        is_builtin: false,
1473                                    };
1474                                }
1475                                // Handle path ending with $function: foo.bar.$lowercase(args)
1476                                AstNode::Path { steps } if steps.len() > 1 => {
1477                                    let last_step = &steps[steps.len() - 1].node;
1478
1479                                    // Check if last step is a Variable (function reference)
1480                                    if let AstNode::Variable(func_name) = last_step {
1481                                        // Extract all but the last step as the path context
1482                                        let mut context_steps = steps.clone();
1483                                        context_steps.pop();
1484
1485                                        // Create function call
1486                                        let func_call = AstNode::Function {
1487                                            name: func_name.clone(),
1488                                            args: args.clone(),
1489                                            is_builtin: true, // Variable means $ prefix
1490                                        };
1491
1492                                        // Append function application to the path
1493                                        context_steps.push(PathStep::new(
1494                                            AstNode::FunctionApplication(Box::new(func_call)),
1495                                        ));
1496
1497                                        self.bump_parse_depth()?;
1498                                        lhs = AstNode::Path {
1499                                            steps: context_steps,
1500                                        };
1501                                    }
1502                                    // Check if last step is a Lambda (inline function in path)
1503                                    else if let AstNode::Lambda {
1504                                        params,
1505                                        body,
1506                                        signature,
1507                                        thunk,
1508                                    } = last_step
1509                                    {
1510                                        // Extract all but the last step as the path context
1511                                        let mut context_steps = steps.clone();
1512                                        context_steps.pop();
1513
1514                                        // In path context, determine if we need to prepend $
1515                                        // - If fewer args than params, prepend $ (context value) as first arg
1516                                        // - If args == params, use args as-is
1517                                        let full_args = if args.len() < params.len() {
1518                                            let mut new_args =
1519                                                vec![AstNode::Variable("$".to_string())];
1520                                            new_args.extend(args.clone());
1521                                            new_args
1522                                        } else {
1523                                            args.clone()
1524                                        };
1525
1526                                        // Create a lambda invocation block
1527                                        // ($__path_lambda := lambda; $__path_lambda(args...))
1528                                        let lambda_invocation = AstNode::Block(vec![
1529                                            AstNode::Binary {
1530                                                op: crate::ast::BinaryOp::ColonEqual,
1531                                                lhs: Box::new(AstNode::Variable(
1532                                                    "__path_lambda__".to_string(),
1533                                                )),
1534                                                rhs: Box::new(AstNode::Lambda {
1535                                                    params: params.clone(),
1536                                                    body: body.clone(),
1537                                                    signature: signature.clone(),
1538                                                    thunk: *thunk,
1539                                                }),
1540                                            },
1541                                            AstNode::Function {
1542                                                name: "__path_lambda__".to_string(),
1543                                                args: full_args,
1544                                                is_builtin: true,
1545                                            },
1546                                        ]);
1547
1548                                        // Append as function application to the path
1549                                        context_steps.push(PathStep::new(
1550                                            AstNode::FunctionApplication(Box::new(
1551                                                lambda_invocation,
1552                                            )),
1553                                        ));
1554
1555                                        self.bump_parse_depth()?;
1556                                        lhs = AstNode::Path {
1557                                            steps: context_steps,
1558                                        };
1559                                    } else {
1560                                        return Err(ParserError::InvalidSyntax(
1561                                            "Invalid function call".to_string(),
1562                                        ));
1563                                    }
1564                                }
1565                                // Handle $-prefixed function names: $uppercase()
1566                                AstNode::Variable(name) => {
1567                                    self.bump_parse_depth()?;
1568                                    lhs = AstNode::Function {
1569                                        name: name.clone(),
1570                                        args,
1571                                        is_builtin: true,
1572                                    };
1573                                }
1574                                _ => {
1575                                    return Err(ParserError::InvalidSyntax(
1576                                        "Invalid function call".to_string(),
1577                                    ))
1578                                }
1579                            };
1580                        }
1581                    }
1582                }
1583                Token::Question => {
1584                    self.advance()?;
1585                    let then_branch = self.parse_expression(0)?;
1586
1587                    let else_branch = if self.current_token == Token::Colon {
1588                        self.advance()?;
1589                        // Use 0 for right-associativity: a ? b : c ? d : e parses as a ? b : (c ? d : e)
1590                        Some(Box::new(self.parse_expression(0)?))
1591                    } else {
1592                        None
1593                    };
1594
1595                    self.bump_parse_depth()?;
1596                    lhs = AstNode::Conditional {
1597                        condition: Box::new(lhs),
1598                        then_branch: Box::new(then_branch),
1599                        else_branch,
1600                    };
1601                }
1602                Token::LeftBrace => {
1603                    // S0210: Each step can only have one grouping expression
1604                    // Check if lhs is already an ObjectTransform
1605                    if matches!(lhs, AstNode::ObjectTransform { .. }) {
1606                        return Err(ParserError::InvalidSyntax(
1607                            "S0210: Each step can only have one grouping expression".to_string(),
1608                        ));
1609                    }
1610
1611                    // Object constructor as postfix: expr{key: value}
1612                    self.advance()?; // skip '{'
1613
1614                    let mut pairs = Vec::new();
1615
1616                    if self.current_token != Token::RightBrace {
1617                        loop {
1618                            // Parse key expression - parse_expression handles identifiers correctly
1619                            let key = self.parse_expression(0)?;
1620
1621                            self.expect(Token::Colon)?;
1622
1623                            // Parse value expression - can be any expression including paths
1624                            let value = self.parse_expression(0)?;
1625
1626                            pairs.push((key, value));
1627
1628                            if self.current_token != Token::Comma {
1629                                break;
1630                            }
1631                            self.advance()?; // skip comma
1632                        }
1633                    }
1634
1635                    self.expect(Token::RightBrace)?;
1636
1637                    // Object constructor with input: lhs{k:v} means transform lhs using the object pattern
1638                    self.bump_parse_depth()?;
1639                    lhs = AstNode::ObjectTransform {
1640                        input: Box::new(lhs),
1641                        pattern: pairs,
1642                    };
1643                }
1644                Token::Hash => {
1645                    // Index binding operator: #$var
1646                    // Binds the current array index to the specified variable
1647                    self.advance()?; // skip '#'
1648
1649                    // Expect a variable name. A bare `#` without a `$var` (e.g.
1650                    // `Account.Order@$o#i.Product`) is S0214, mirroring jsonata-js's
1651                    // inline check for `@`/`#` (parser.js ~L834-847).
1652                    let var_name = match &self.current_token {
1653                        Token::Variable(name) => name.clone(),
1654                        _ => {
1655                            return Err(ParserError::Coded {
1656                                code: "S0214",
1657                                message: "Expected a variable reference after #".to_string(),
1658                            });
1659                        }
1660                    };
1661                    self.advance()?; // skip variable
1662
1663                    // Produces a generic Binary(IndexBind) marker -- ast_transform
1664                    // resolves this into a PathStep.index_var flag, mirroring how
1665                    // @$var/FocusBind is represented (see Token::At below). Using
1666                    // the same generic Binary shape (rather than a dedicated
1667                    // AstNode::IndexBind variant) lets that variant be retired
1668                    // from ast.rs entirely.
1669                    self.bump_parse_depth()?;
1670                    lhs = AstNode::Binary {
1671                        op: BinaryOp::IndexBind,
1672                        lhs: Box::new(lhs),
1673                        rhs: Box::new(AstNode::Variable(var_name)),
1674                    };
1675                }
1676                Token::At => {
1677                    // Focus binding operator: @$var
1678                    // Produces a generic Binary(FocusBind) marker -- ast_transform
1679                    // resolves this into a PathStep.focus flag, matching
1680                    // jsonata-js's parser.js:834-847 (which does the same S0214
1681                    // check inline, deferring all other semantics to processAST).
1682                    self.advance()?; // skip '@'
1683
1684                    let var_name = match &self.current_token {
1685                        Token::Variable(name) => name.clone(),
1686                        _ => {
1687                            return Err(ParserError::Coded {
1688                                code: "S0214",
1689                                message: "Expected a variable reference after @".to_string(),
1690                            });
1691                        }
1692                    };
1693                    self.advance()?; // skip variable
1694
1695                    self.bump_parse_depth()?;
1696                    lhs = AstNode::Binary {
1697                        op: BinaryOp::FocusBind,
1698                        lhs: Box::new(lhs),
1699                        rhs: Box::new(AstNode::Variable(var_name)),
1700                    };
1701                }
1702                Token::Caret => {
1703                    // Sort operator: ^(expr) or ^(<expr) or ^(>expr)
1704                    self.advance()?; // skip '^'
1705                    self.expect(Token::LeftParen)?;
1706
1707                    let mut terms = Vec::new();
1708
1709                    loop {
1710                        // Check for optional sort direction prefix
1711                        let ascending = match &self.current_token {
1712                            Token::LessThan => {
1713                                self.advance()?;
1714                                true
1715                            }
1716                            Token::GreaterThan => {
1717                                self.advance()?;
1718                                false
1719                            }
1720                            _ => true, // Default to ascending
1721                        };
1722
1723                        // Parse the sort expression
1724                        let expr = self.parse_expression(0)?;
1725                        terms.push((expr, ascending));
1726
1727                        // Check for more sort terms
1728                        if self.current_token != Token::Comma {
1729                            break;
1730                        }
1731                        self.advance()?; // skip comma
1732                    }
1733
1734                    self.expect(Token::RightParen)?;
1735
1736                    self.bump_parse_depth()?;
1737                    lhs = AstNode::Sort {
1738                        input: Box::new(lhs),
1739                        terms,
1740                    };
1741                }
1742                _ => {
1743                    // Binary operators
1744                    let op = match &self.current_token {
1745                        // Contextual keyword operators
1746                        Token::Identifier(name) if name == "and" => BinaryOp::And,
1747                        Token::Identifier(name) if name == "or" => BinaryOp::Or,
1748                        Token::Identifier(name) if name == "in" => BinaryOp::In,
1749                        // Regular operators
1750                        Token::Plus => BinaryOp::Add,
1751                        Token::Minus => BinaryOp::Subtract,
1752                        Token::Star => BinaryOp::Multiply,
1753                        Token::Slash => BinaryOp::Divide,
1754                        Token::Percent => BinaryOp::Modulo,
1755                        Token::Equal => BinaryOp::Equal,
1756                        Token::NotEqual => BinaryOp::NotEqual,
1757                        Token::LessThan => BinaryOp::LessThan,
1758                        Token::LessThanOrEqual => BinaryOp::LessThanOrEqual,
1759                        Token::GreaterThan => BinaryOp::GreaterThan,
1760                        Token::GreaterThanOrEqual => BinaryOp::GreaterThanOrEqual,
1761                        Token::And => BinaryOp::And,
1762                        Token::Or => BinaryOp::Or,
1763                        Token::In => BinaryOp::In,
1764                        Token::Ampersand => BinaryOp::Concatenate,
1765                        Token::DotDot => BinaryOp::Range,
1766                        Token::ColonEqual => BinaryOp::ColonEqual,
1767                        Token::QuestionQuestion => BinaryOp::Coalesce,
1768                        Token::QuestionColon => BinaryOp::Default,
1769                        Token::TildeArrow => BinaryOp::ChainPipe,
1770                        _ => {
1771                            return Err(ParserError::UnexpectedToken(format!(
1772                                "{:?}",
1773                                self.current_token
1774                            )))
1775                        }
1776                    };
1777
1778                    self.advance()?;
1779                    let rhs = self.parse_expression(right_bp)?;
1780
1781                    self.bump_parse_depth()?;
1782                    lhs = AstNode::Binary {
1783                        op,
1784                        lhs: Box::new(lhs),
1785                        rhs: Box::new(rhs),
1786                    };
1787                }
1788            }
1789        }
1790
1791        Ok(lhs)
1792    }
1793
1794    pub fn parse(&mut self) -> Result<AstNode, ParserError> {
1795        let ast = self.parse_expression(0)?;
1796
1797        if self.current_token != Token::Eof {
1798            return Err(ParserError::Expected {
1799                expected: "end of expression".to_string(),
1800                found: format!("{:?}", self.current_token),
1801            });
1802        }
1803
1804        Ok(ast)
1805    }
1806
1807    /// Analyze an expression for tail call optimization
1808    /// Returns (optimized_expr, is_thunk) where:
1809    /// - optimized_expr is the expression (unchanged)
1810    /// - is_thunk is true if the expression's tail position is a function call
1811    ///
1812    /// A tail position is where a function call's result is directly returned:
1813    /// - The body itself if it's a function call
1814    /// - Both branches of a conditional at tail position
1815    /// - The last expression of a block at tail position
1816    fn tail_call_optimize(expr: AstNode) -> (AstNode, bool) {
1817        let is_thunk = Self::is_tail_call(&expr);
1818        (expr, is_thunk)
1819    }
1820
1821    /// Check if an expression is in tail call position
1822    /// Returns true if the expression is a function call (or contains function calls in all tail positions)
1823    fn is_tail_call(expr: &AstNode) -> bool {
1824        match expr {
1825            // Direct function calls are tail calls
1826            AstNode::Function { .. } => true,
1827            AstNode::Call { .. } => true,
1828
1829            // Conditional: both branches must be tail calls (or at least one if only one branch)
1830            AstNode::Conditional {
1831                then_branch,
1832                else_branch,
1833                ..
1834            } => {
1835                let then_is_tail = Self::is_tail_call(then_branch);
1836                let else_is_tail = else_branch.as_ref().is_some_and(|e| Self::is_tail_call(e));
1837                // At least one branch should be a tail call for TCO to be useful
1838                then_is_tail || else_is_tail
1839            }
1840
1841            // Block: last expression is tail position
1842            AstNode::Block(exprs) => exprs.last().is_some_and(Self::is_tail_call),
1843
1844            // Variable binding with result: the result expression is tail position
1845            AstNode::Binary {
1846                op: BinaryOp::ColonEqual,
1847                rhs,
1848                ..
1849            } => {
1850                // The rhs (or next expression) could be tail position
1851                // But typically := is used for assignment within blocks
1852                // Check if rhs is a block (common pattern)
1853                Self::is_tail_call(rhs)
1854            }
1855
1856            // Anything else is not a tail call
1857            _ => false,
1858        }
1859    }
1860}
1861
1862/// Parse a JSONata expression string into an AST
1863///
1864/// This is the main entry point for parsing. Runs the post-parse
1865/// ast_transform pass (ancestor-slot resolution, @/#/% unification)
1866/// unconditionally, matching jsonata-js's processAST always running
1867/// immediately after the raw Pratt parse.
1868pub fn parse(expression: &str) -> Result<AstNode, ParserError> {
1869    let mut parser = Parser::new(expression.to_string())?;
1870    let raw_ast = parser.parse()?;
1871    crate::ast_transform::resolve_ancestry(raw_ast).map_err(|e| match e {
1872        crate::ast_transform::AstTransformError::Coded { code, message } => {
1873            ParserError::Coded { code, message }
1874        }
1875    })
1876}
1877
1878#[cfg(test)]
1879mod tests {
1880    use super::*;
1881
1882    // Lexer tests
1883    #[test]
1884    fn test_lexer_numbers() {
1885        let mut lexer = Lexer::new("42 3.14 -10 2.5e10 1E-5".to_string());
1886
1887        assert_eq!(lexer.next_token().unwrap(), Token::Number(42.0));
1888        assert_eq!(lexer.next_token().unwrap(), Token::Number(3.14));
1889        // Negation is handled as a unary operator, not part of the number literal
1890        assert_eq!(lexer.next_token().unwrap(), Token::Minus);
1891        assert_eq!(lexer.next_token().unwrap(), Token::Number(10.0));
1892        assert_eq!(lexer.next_token().unwrap(), Token::Number(2.5e10));
1893        assert_eq!(lexer.next_token().unwrap(), Token::Number(1e-5));
1894        assert_eq!(lexer.next_token().unwrap(), Token::Eof);
1895    }
1896
1897    #[test]
1898    fn test_lexer_strings() {
1899        let mut lexer = Lexer::new(r#""hello" 'world' "with\nnewline""#.to_string());
1900
1901        assert_eq!(
1902            lexer.next_token().unwrap(),
1903            Token::String("hello".to_string())
1904        );
1905        assert_eq!(
1906            lexer.next_token().unwrap(),
1907            Token::String("world".to_string())
1908        );
1909        assert_eq!(
1910            lexer.next_token().unwrap(),
1911            Token::String("with\nnewline".to_string())
1912        );
1913        assert_eq!(lexer.next_token().unwrap(), Token::Eof);
1914    }
1915
1916    #[test]
1917    fn test_lexer_string_escapes() {
1918        let mut lexer = Lexer::new(r#""a\"b\\c\/d""#.to_string());
1919        assert_eq!(
1920            lexer.next_token().unwrap(),
1921            Token::String("a\"b\\c/d".to_string())
1922        );
1923    }
1924
1925    #[test]
1926    fn test_lexer_keywords() {
1927        let mut lexer = Lexer::new("true false null and or in".to_string());
1928
1929        assert_eq!(lexer.next_token().unwrap(), Token::True);
1930        assert_eq!(lexer.next_token().unwrap(), Token::False);
1931        assert_eq!(lexer.next_token().unwrap(), Token::Null);
1932        // "and", "or", "in" are now contextual keywords - lexer emits them as identifiers
1933        assert_eq!(
1934            lexer.next_token().unwrap(),
1935            Token::Identifier("and".to_string())
1936        );
1937        assert_eq!(
1938            lexer.next_token().unwrap(),
1939            Token::Identifier("or".to_string())
1940        );
1941        assert_eq!(
1942            lexer.next_token().unwrap(),
1943            Token::Identifier("in".to_string())
1944        );
1945        assert_eq!(lexer.next_token().unwrap(), Token::Eof);
1946    }
1947
1948    #[test]
1949    fn test_lexer_identifiers() {
1950        let mut lexer = Lexer::new("foo bar_baz test123".to_string());
1951
1952        assert_eq!(
1953            lexer.next_token().unwrap(),
1954            Token::Identifier("foo".to_string())
1955        );
1956        assert_eq!(
1957            lexer.next_token().unwrap(),
1958            Token::Identifier("bar_baz".to_string())
1959        );
1960        assert_eq!(
1961            lexer.next_token().unwrap(),
1962            Token::Identifier("test123".to_string())
1963        );
1964        assert_eq!(lexer.next_token().unwrap(), Token::Eof);
1965    }
1966
1967    #[test]
1968    fn test_lexer_variables() {
1969        let mut lexer = Lexer::new("$var $foo_bar".to_string());
1970
1971        assert_eq!(
1972            lexer.next_token().unwrap(),
1973            Token::Variable("var".to_string())
1974        );
1975        assert_eq!(
1976            lexer.next_token().unwrap(),
1977            Token::Variable("foo_bar".to_string())
1978        );
1979        assert_eq!(lexer.next_token().unwrap(), Token::Eof);
1980    }
1981
1982    #[test]
1983    fn test_lexer_operators() {
1984        // Test non-slash operators first
1985        let mut lexer = Lexer::new("+ - * % = != < <= > >= & . .. := ".to_string());
1986
1987        assert_eq!(lexer.next_token().unwrap(), Token::Plus);
1988        assert_eq!(lexer.next_token().unwrap(), Token::Minus);
1989        assert_eq!(lexer.next_token().unwrap(), Token::Star);
1990        assert_eq!(lexer.next_token().unwrap(), Token::Percent);
1991        assert_eq!(lexer.next_token().unwrap(), Token::Equal);
1992        assert_eq!(lexer.next_token().unwrap(), Token::NotEqual);
1993        assert_eq!(lexer.next_token().unwrap(), Token::LessThan);
1994        assert_eq!(lexer.next_token().unwrap(), Token::LessThanOrEqual);
1995        assert_eq!(lexer.next_token().unwrap(), Token::GreaterThan);
1996        assert_eq!(lexer.next_token().unwrap(), Token::GreaterThanOrEqual);
1997        assert_eq!(lexer.next_token().unwrap(), Token::Ampersand);
1998        assert_eq!(lexer.next_token().unwrap(), Token::Dot);
1999        assert_eq!(lexer.next_token().unwrap(), Token::DotDot);
2000        assert_eq!(lexer.next_token().unwrap(), Token::ColonEqual);
2001        assert_eq!(lexer.next_token().unwrap(), Token::Eof);
2002
2003        // Slash is context-dependent: after a value token it's division, otherwise regex.
2004        // Test slash after a number (value context) to get Token::Slash.
2005        let mut lexer2 = Lexer::new("42 / 2".to_string());
2006        assert_eq!(lexer2.next_token().unwrap(), Token::Number(42.0));
2007        assert_eq!(lexer2.next_token().unwrap(), Token::Slash);
2008        assert_eq!(lexer2.next_token().unwrap(), Token::Number(2.0));
2009        assert_eq!(lexer2.next_token().unwrap(), Token::Eof);
2010    }
2011
2012    #[test]
2013    fn test_lexer_delimiters() {
2014        let mut lexer = Lexer::new("()[]{},:;?".to_string());
2015
2016        assert_eq!(lexer.next_token().unwrap(), Token::LeftParen);
2017        assert_eq!(lexer.next_token().unwrap(), Token::RightParen);
2018        assert_eq!(lexer.next_token().unwrap(), Token::LeftBracket);
2019        assert_eq!(lexer.next_token().unwrap(), Token::RightBracket);
2020        assert_eq!(lexer.next_token().unwrap(), Token::LeftBrace);
2021        assert_eq!(lexer.next_token().unwrap(), Token::RightBrace);
2022        assert_eq!(lexer.next_token().unwrap(), Token::Comma);
2023        assert_eq!(lexer.next_token().unwrap(), Token::Colon);
2024        assert_eq!(lexer.next_token().unwrap(), Token::Semicolon);
2025        assert_eq!(lexer.next_token().unwrap(), Token::Question);
2026        assert_eq!(lexer.next_token().unwrap(), Token::Eof);
2027    }
2028
2029    #[test]
2030    fn test_empty_brackets() {
2031        let mut parser = Parser::new("foo[]".to_string()).unwrap();
2032        let ast = parser.parse().unwrap();
2033
2034        // Should create a Path with two steps: Name("foo") and Predicate(Boolean(true))
2035        if let AstNode::Path { steps } = ast {
2036            assert_eq!(steps.len(), 2);
2037            assert!(matches!(steps[0].node, AstNode::Name(ref s) if s == "foo"));
2038            if let AstNode::Predicate(pred) = &steps[1].node {
2039                assert!(matches!(**pred, AstNode::Boolean(true)));
2040            } else {
2041                panic!("Expected Predicate as second step, got {:?}", steps[1].node);
2042            }
2043        } else {
2044            panic!("Expected Path, got {:?}", ast);
2045        }
2046    }
2047
2048    #[test]
2049    fn test_lexer_comments() {
2050        let mut lexer = Lexer::new("foo /* comment */ bar".to_string());
2051
2052        assert_eq!(
2053            lexer.next_token().unwrap(),
2054            Token::Identifier("foo".to_string())
2055        );
2056        assert_eq!(
2057            lexer.next_token().unwrap(),
2058            Token::Identifier("bar".to_string())
2059        );
2060        assert_eq!(lexer.next_token().unwrap(), Token::Eof);
2061    }
2062
2063    #[test]
2064    fn test_lexer_backtick_names() {
2065        let mut lexer = Lexer::new("`field name` `with-dash`".to_string());
2066
2067        assert_eq!(
2068            lexer.next_token().unwrap(),
2069            Token::Identifier("field name".to_string())
2070        );
2071        assert_eq!(
2072            lexer.next_token().unwrap(),
2073            Token::Identifier("with-dash".to_string())
2074        );
2075        assert_eq!(lexer.next_token().unwrap(), Token::Eof);
2076    }
2077
2078    // Parser tests
2079    #[test]
2080    fn test_parse_number() {
2081        let ast = parse("42").unwrap();
2082        assert_eq!(ast, AstNode::Number(42.0));
2083    }
2084
2085    #[test]
2086    fn test_parse_string() {
2087        let ast = parse(r#""hello""#).unwrap();
2088        assert_eq!(ast, AstNode::String("hello".to_string()));
2089    }
2090
2091    #[test]
2092    fn test_parse_boolean() {
2093        let ast = parse("true").unwrap();
2094        assert_eq!(ast, AstNode::Boolean(true));
2095
2096        let ast = parse("false").unwrap();
2097        assert_eq!(ast, AstNode::Boolean(false));
2098    }
2099
2100    #[test]
2101    fn test_parse_null() {
2102        let ast = parse("null").unwrap();
2103        assert_eq!(ast, AstNode::Null);
2104    }
2105
2106    #[test]
2107    fn test_parse_variable() {
2108        let ast = parse("$var").unwrap();
2109        assert_eq!(ast, AstNode::Variable("var".to_string()));
2110    }
2111
2112    #[test]
2113    fn test_parse_identifier() {
2114        let ast = parse("foo").unwrap();
2115        assert_eq!(
2116            ast,
2117            AstNode::Path {
2118                steps: vec![PathStep::new(AstNode::Name("foo".to_string()))]
2119            }
2120        );
2121    }
2122
2123    #[test]
2124    fn test_parse_addition() {
2125        let ast = parse("1 + 2").unwrap();
2126        match ast {
2127            AstNode::Binary { op, lhs, rhs } => {
2128                assert_eq!(op, BinaryOp::Add);
2129                assert_eq!(*lhs, AstNode::Number(1.0));
2130                assert_eq!(*rhs, AstNode::Number(2.0));
2131            }
2132            _ => panic!("Expected Binary node"),
2133        }
2134    }
2135
2136    #[test]
2137    fn test_parse_precedence() {
2138        // 1 + 2 * 3 should parse as 1 + (2 * 3)
2139        let ast = parse("1 + 2 * 3").unwrap();
2140        match ast {
2141            AstNode::Binary {
2142                op: BinaryOp::Add,
2143                lhs,
2144                rhs,
2145            } => {
2146                assert_eq!(*lhs, AstNode::Number(1.0));
2147                match *rhs {
2148                    AstNode::Binary {
2149                        op: BinaryOp::Multiply,
2150                        lhs,
2151                        rhs,
2152                    } => {
2153                        assert_eq!(*lhs, AstNode::Number(2.0));
2154                        assert_eq!(*rhs, AstNode::Number(3.0));
2155                    }
2156                    _ => panic!("Expected Binary node for multiplication"),
2157                }
2158            }
2159            _ => panic!("Expected Binary node for addition"),
2160        }
2161    }
2162
2163    #[test]
2164    fn test_parse_parentheses() {
2165        // (1 + 2) * 3 should parse as Block([1 + 2]) * 3
2166        // Parenthesized expressions always create a Block in JSONata
2167        let ast = parse("(1 + 2) * 3").unwrap();
2168        match ast {
2169            AstNode::Binary {
2170                op: BinaryOp::Multiply,
2171                lhs,
2172                rhs,
2173            } => {
2174                match *lhs {
2175                    AstNode::Block(ref exprs) => {
2176                        assert_eq!(exprs.len(), 1);
2177                        match &exprs[0] {
2178                            AstNode::Binary {
2179                                op: BinaryOp::Add,
2180                                lhs,
2181                                rhs,
2182                            } => {
2183                                assert_eq!(**lhs, AstNode::Number(1.0));
2184                                assert_eq!(**rhs, AstNode::Number(2.0));
2185                            }
2186                            _ => panic!("Expected Binary node for addition inside block"),
2187                        }
2188                    }
2189                    _ => panic!(
2190                        "Expected Block node for parenthesized expression, got {:?}",
2191                        lhs
2192                    ),
2193                }
2194                assert_eq!(*rhs, AstNode::Number(3.0));
2195            }
2196            _ => panic!("Expected Binary node for multiplication"),
2197        }
2198    }
2199
2200    #[test]
2201    fn test_parse_array() {
2202        let ast = parse("[1, 2, 3]").unwrap();
2203        match ast {
2204            AstNode::Array(elements) => {
2205                assert_eq!(elements.len(), 3);
2206                assert_eq!(elements[0], AstNode::Number(1.0));
2207                assert_eq!(elements[1], AstNode::Number(2.0));
2208                assert_eq!(elements[2], AstNode::Number(3.0));
2209            }
2210            _ => panic!("Expected Array node"),
2211        }
2212    }
2213
2214    #[test]
2215    fn test_parse_object() {
2216        let ast = parse(r#"{"a": 1, "b": 2}"#).unwrap();
2217        match ast {
2218            AstNode::Object(pairs) => {
2219                assert_eq!(pairs.len(), 2);
2220                assert_eq!(pairs[0].0, AstNode::String("a".to_string()));
2221                assert_eq!(pairs[0].1, AstNode::Number(1.0));
2222                assert_eq!(pairs[1].0, AstNode::String("b".to_string()));
2223                assert_eq!(pairs[1].1, AstNode::Number(2.0));
2224            }
2225            _ => panic!("Expected Object node"),
2226        }
2227    }
2228
2229    #[test]
2230    fn test_parse_path() {
2231        let ast = parse("foo.bar").unwrap();
2232        match ast {
2233            AstNode::Path { steps } => {
2234                assert_eq!(steps.len(), 2);
2235                assert_eq!(steps[0].node, AstNode::Name("foo".to_string()));
2236                assert_eq!(steps[1].node, AstNode::Name("bar".to_string()));
2237            }
2238            _ => panic!("Expected Path node"),
2239        }
2240    }
2241
2242    #[test]
2243    fn test_parse_function_call() {
2244        let ast = parse("sum(1, 2, 3)").unwrap();
2245        match ast {
2246            AstNode::Function {
2247                name,
2248                args,
2249                is_builtin,
2250            } => {
2251                assert_eq!(name, "sum");
2252                assert_eq!(args.len(), 3);
2253                assert_eq!(args[0], AstNode::Number(1.0));
2254                assert_eq!(args[1], AstNode::Number(2.0));
2255                assert_eq!(args[2], AstNode::Number(3.0));
2256                assert!(!is_builtin); // Bare function call (no $ prefix)
2257            }
2258            _ => panic!("Expected Function node"),
2259        }
2260    }
2261
2262    #[test]
2263    fn test_parse_conditional() {
2264        let ast = parse("x > 0 ? 1 : -1").unwrap();
2265        match ast {
2266            AstNode::Conditional {
2267                condition,
2268                then_branch,
2269                else_branch,
2270            } => {
2271                assert!(matches!(*condition, AstNode::Binary { .. }));
2272                assert_eq!(*then_branch, AstNode::Number(1.0));
2273                // Negative numbers are parsed as Unary { Negate, Number(1.0) }
2274                assert_eq!(
2275                    else_branch,
2276                    Some(Box::new(AstNode::Unary {
2277                        op: UnaryOp::Negate,
2278                        operand: Box::new(AstNode::Number(1.0)),
2279                    }))
2280                );
2281            }
2282            _ => panic!("Expected Conditional node"),
2283        }
2284    }
2285
2286    #[test]
2287    fn test_parse_comparison() {
2288        let ast = parse("x < 10").unwrap();
2289        match ast {
2290            AstNode::Binary { op, .. } => {
2291                assert_eq!(op, BinaryOp::LessThan);
2292            }
2293            _ => panic!("Expected Binary node"),
2294        }
2295    }
2296
2297    #[test]
2298    fn test_parse_logical_and() {
2299        let ast = parse("true and false").unwrap();
2300        match ast {
2301            AstNode::Binary { op, lhs, rhs } => {
2302                assert_eq!(op, BinaryOp::And);
2303                assert_eq!(*lhs, AstNode::Boolean(true));
2304                assert_eq!(*rhs, AstNode::Boolean(false));
2305            }
2306            _ => panic!("Expected Binary node"),
2307        }
2308    }
2309
2310    #[test]
2311    fn test_parse_string_concatenation() {
2312        let ast = parse(r#""hello" & " " & "world""#).unwrap();
2313        match ast {
2314            AstNode::Binary { op, .. } => {
2315                assert_eq!(op, BinaryOp::Concatenate);
2316            }
2317            _ => panic!("Expected Binary node"),
2318        }
2319    }
2320
2321    #[test]
2322    fn test_parse_unary_minus() {
2323        // Negative numbers are parsed as Unary { Negate, Number(5.0) }
2324        let ast = parse("-5").unwrap();
2325        assert_eq!(
2326            ast,
2327            AstNode::Unary {
2328                op: UnaryOp::Negate,
2329                operand: Box::new(AstNode::Number(5.0)),
2330            }
2331        );
2332    }
2333
2334    #[test]
2335    fn test_parse_block() {
2336        let ast = parse("(1; 2; 3)").unwrap();
2337        match ast {
2338            AstNode::Block(expressions) => {
2339                assert_eq!(expressions.len(), 3);
2340                assert_eq!(expressions[0], AstNode::Number(1.0));
2341                assert_eq!(expressions[1], AstNode::Number(2.0));
2342                assert_eq!(expressions[2], AstNode::Number(3.0));
2343            }
2344            _ => panic!("Expected Block node"),
2345        }
2346    }
2347
2348    #[test]
2349    fn test_parse_complex_expression() {
2350        // Test a more complex expression
2351        let ast = parse("(a + b) * c.d").unwrap();
2352        assert!(matches!(ast, AstNode::Binary { .. }));
2353    }
2354
2355    #[test]
2356    fn test_parse_dollar_function_call() {
2357        // Test $uppercase function
2358        let ast = parse(r#"$uppercase("hello")"#).unwrap();
2359        match ast {
2360            AstNode::Function {
2361                name,
2362                args,
2363                is_builtin,
2364            } => {
2365                assert_eq!(name, "uppercase");
2366                assert_eq!(args.len(), 1);
2367                assert_eq!(args[0], AstNode::String("hello".to_string()));
2368                assert!(is_builtin); // $ prefix means builtin
2369            }
2370            _ => panic!("Expected Function node"),
2371        }
2372
2373        // Test $sum function
2374        let ast = parse("$sum([1, 2, 3])").unwrap();
2375        match ast {
2376            AstNode::Function {
2377                name,
2378                args,
2379                is_builtin,
2380            } => {
2381                assert_eq!(name, "sum");
2382                assert_eq!(args.len(), 1);
2383                assert!(is_builtin); // $ prefix means builtin
2384            }
2385            _ => panic!("Expected Function node"),
2386        }
2387    }
2388
2389    #[test]
2390    fn test_parse_nested_dollar_functions() {
2391        // Test nested $function calls
2392        let ast = parse(r#"$length($lowercase("HELLO"))"#).unwrap();
2393        match ast {
2394            AstNode::Function {
2395                name,
2396                args,
2397                is_builtin,
2398            } => {
2399                assert_eq!(name, "length");
2400                assert_eq!(args.len(), 1);
2401                assert!(is_builtin);
2402                // Check nested function
2403                match &args[0] {
2404                    AstNode::Function {
2405                        name: inner_name,
2406                        is_builtin: inner_builtin,
2407                        ..
2408                    } => {
2409                        assert_eq!(inner_name, "lowercase");
2410                        assert!(inner_builtin);
2411                    }
2412                    _ => panic!("Expected nested Function node"),
2413                }
2414            }
2415            _ => panic!("Expected Function node"),
2416        }
2417    }
2418
2419    #[test]
2420    fn test_signature_with_repeat_modifier_parses() {
2421        // A lambda literal with a '+' (one-or-more) signature modifier must parse
2422        // without error. This was rejected before jsonata-js 2.2.1 compatibility work
2423        // ("Unexpected token in signature: Plus").
2424        let result = parse("λ($arg1, $arg2)<n+n:o>{{\"a\": $arg1, \"b\": $arg2}}(1, 2, 3)");
2425        assert!(
2426            result.is_ok(),
2427            "expected parse to succeed, got {:?}",
2428            result
2429        );
2430    }
2431
2432    #[test]
2433    fn test_parent_operator_parses_as_prefix() {
2434        // Tests the RAW grammar rule (bare `%` in primary position) in
2435        // isolation from ast_transform's ancestor resolution -- uses
2436        // Parser::new(...).parse() directly rather than the free `parse()`
2437        // function, since the free function now runs ast_transform
2438        // unconditionally (Step 8), and `%.OrderID` alone has no preceding
2439        // step for `%` to resolve against (that's covered by
2440        // ast_transform's own S0217 tests).
2441        let mut parser = Parser::new("%.OrderID".to_string()).unwrap();
2442        let ast = parser.parse().unwrap();
2443        // %.OrderID should parse as a path with two steps: Parent, then Name("OrderID")
2444        match ast {
2445            AstNode::Path { steps } => {
2446                assert_eq!(steps.len(), 2);
2447                assert!(matches!(steps[0].node, AstNode::Parent(_)));
2448                assert!(matches!(steps[1].node, AstNode::Name(ref n) if n == "OrderID"));
2449            }
2450            other => panic!("expected Path, got {:?}", other),
2451        }
2452    }
2453
2454    #[test]
2455    fn test_percent_still_parses_as_modulo_infix() {
2456        // Regression: % must still work as binary modulo when NOT in prefix position
2457        let ast = parse("10 % 3").unwrap();
2458        assert!(matches!(
2459            ast,
2460            AstNode::Binary {
2461                op: BinaryOp::Modulo,
2462                ..
2463            }
2464        ));
2465    }
2466
2467    #[test]
2468    fn test_focus_bind_parses_as_binary_marker() {
2469        // Tests the RAW grammar rule in isolation from ast_transform (which
2470        // now runs unconditionally in the free `parse()` and would rewrite
2471        // this into a Path with a `focus` flag instead -- see
2472        // ast_transform.rs's own real-parser-based tests for that).
2473        let mut parser = Parser::new("Order@$o".to_string()).unwrap();
2474        let ast = parser.parse().unwrap();
2475        match ast {
2476            AstNode::Binary {
2477                op: BinaryOp::FocusBind,
2478                lhs,
2479                rhs,
2480            } => {
2481                // Order is parsed as a Path with a Name step
2482                if let AstNode::Path { steps } = *lhs {
2483                    assert_eq!(steps.len(), 1);
2484                    assert!(matches!(steps[0].node, AstNode::Name(ref n) if n == "Order"));
2485                } else {
2486                    panic!("expected lhs to be Path, got {:?}", lhs);
2487                }
2488                assert!(matches!(*rhs, AstNode::Variable(ref n) if n == "o"));
2489            }
2490            other => panic!("expected Binary{{FocusBind}}, got {:?}", other),
2491        }
2492    }
2493
2494    #[test]
2495    fn test_index_bind_parses_as_binary_marker() {
2496        // #$var now reuses the same generic Binary marker shape as @$var
2497        // (Task 4 retires the dedicated AstNode::IndexBind struct variant).
2498        let mut parser = Parser::new("arr#$i".to_string()).unwrap();
2499        let ast = parser.parse().unwrap();
2500        match ast {
2501            AstNode::Binary {
2502                op: BinaryOp::IndexBind,
2503                lhs,
2504                rhs,
2505            } => {
2506                if let AstNode::Path { steps } = *lhs {
2507                    assert_eq!(steps.len(), 1);
2508                    assert!(matches!(steps[0].node, AstNode::Name(ref n) if n == "arr"));
2509                } else {
2510                    panic!("expected lhs to be Path, got {:?}", lhs);
2511                }
2512                assert!(matches!(*rhs, AstNode::Variable(ref n) if n == "i"));
2513            }
2514            other => panic!("expected Binary{{IndexBind}}, got {:?}", other),
2515        }
2516    }
2517
2518    #[test]
2519    fn test_focus_bind_requires_variable_rhs() {
2520        // S0214: @'s RHS must be a bare variable reference
2521        let err = parse("Order@foo").unwrap_err();
2522        assert!(err.to_string().starts_with("S0214"));
2523    }
2524}