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