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/// Parser for JSONata expressions using Pratt parsing
671pub struct Parser {
672    lexer: Lexer,
673    current_token: Token,
674}
675
676impl Parser {
677    pub fn new(input: String) -> Result<Self, ParserError> {
678        let mut lexer = Lexer::new(input);
679        let current_token = lexer.next_token()?;
680        Ok(Parser {
681            lexer,
682            current_token,
683        })
684    }
685
686    fn advance(&mut self) -> Result<(), ParserError> {
687        self.current_token = self.lexer.next_token()?;
688        Ok(())
689    }
690
691    fn expect(&mut self, expected: Token) -> Result<(), ParserError> {
692        if std::mem::discriminant(&self.current_token) == std::mem::discriminant(&expected) {
693            self.advance()?;
694            Ok(())
695        } else {
696            Err(ParserError::Expected {
697                expected: format!("{:?}", expected),
698                found: format!("{:?}", self.current_token),
699            })
700        }
701    }
702
703    /// Get the binding power (precedence) for the current token
704    fn binding_power(&self, token: &Token) -> Option<(u8, u8)> {
705        // Returns (left_bp, right_bp) for left and right binding power
706        // Higher numbers = higher precedence
707        match token {
708            // Contextual keywords (treated as operators when in infix position)
709            Token::Identifier(name) if name == "or" => Some((25, 26)),
710            Token::Identifier(name) if name == "and" => Some((30, 31)),
711            Token::Identifier(name) if name == "in" => Some((40, 41)),
712            // Regular operators
713            Token::Or => Some((25, 26)),
714            Token::And => Some((30, 31)),
715            Token::Equal
716            | Token::NotEqual
717            | Token::LessThan
718            | Token::LessThanOrEqual
719            | Token::GreaterThan
720            | Token::GreaterThanOrEqual
721            | Token::In => Some((40, 41)),
722            Token::Ampersand => Some((50, 51)),
723            Token::Plus | Token::Minus => Some((50, 51)),
724            Token::Star | Token::Slash | Token::Percent => Some((60, 61)),
725            Token::Dot => Some((75, 85)), // Right bp is higher to prevent consuming postfix operators
726            Token::LeftBracket => Some((80, 81)),
727            Token::LeftParen => Some((80, 81)),
728            Token::LeftBrace => Some((80, 81)), // Object constructor as postfix
729            Token::Caret => Some((80, 81)),     // Sort operator as postfix
730            Token::Hash => Some((80, 81)),      // Index binding operator as postfix
731            Token::At => Some((80, 81)),        // Focus binding operator as postfix
732            Token::Question => Some((20, 21)),
733            Token::QuestionQuestion => Some((15, 16)), // Coalescing operator
734            Token::QuestionColon => Some((15, 16)),    // Default operator
735            Token::DotDot => Some((20, 21)),
736            Token::ColonEqual => Some((10, 9)), // Right associative
737            Token::TildeArrow => Some((70, 71)), // Chain/pipe operator
738            _ => None,
739        }
740    }
741
742    /// Parse a function signature: <param-types:return-type>
743    fn parse_signature(&mut self) -> Result<String, ParserError> {
744        // Expect <
745        if self.current_token != Token::LessThan {
746            return Err(ParserError::Expected {
747                expected: "<".to_string(),
748                found: format!("{:?}", self.current_token),
749            });
750        }
751
752        // Build signature string by collecting characters until we find >
753        let mut signature = String::from("<");
754        self.advance()?; // skip <
755
756        // Collect all characters until we find the closing >
757        // This is a bit tricky because we need to handle nested <> for array types like a<s>
758        let mut depth = 1;
759
760        while depth > 0 && self.current_token != Token::Eof {
761            match &self.current_token {
762                Token::LessThan => {
763                    signature.push('<');
764                    depth += 1;
765                    self.advance()?;
766                }
767                Token::GreaterThan => {
768                    depth -= 1;
769                    if depth > 0 {
770                        signature.push('>');
771                    }
772                    self.advance()?;
773                }
774                Token::Minus => {
775                    signature.push('-');
776                    self.advance()?;
777                }
778                Token::Plus => {
779                    signature.push('+');
780                    self.advance()?;
781                }
782                Token::Colon => {
783                    signature.push(':');
784                    self.advance()?;
785                }
786                Token::Question => {
787                    signature.push('?');
788                    self.advance()?;
789                }
790                Token::QuestionColon => {
791                    // ?:  gets tokenized as QuestionColon in signatures like s?:s
792                    signature.push('?');
793                    signature.push(':');
794                    self.advance()?;
795                }
796                Token::Identifier(s) => {
797                    signature.push_str(s);
798                    self.advance()?;
799                }
800                Token::LeftParen | Token::RightParen => {
801                    // Handle parentheses for union types like (ns)
802                    let c = if self.current_token == Token::LeftParen {
803                        '('
804                    } else {
805                        ')'
806                    };
807                    signature.push(c);
808                    self.advance()?;
809                }
810                _ => {
811                    return Err(ParserError::UnexpectedToken(format!(
812                        "Unexpected token in signature: {:?}",
813                        self.current_token
814                    )));
815                }
816            }
817        }
818
819        signature.push('>');
820        Ok(signature)
821    }
822
823    /// Parse a primary expression (literals, identifiers, variables, grouping)
824    fn parse_primary(&mut self) -> Result<AstNode, ParserError> {
825        match &self.current_token {
826            Token::String(s) => {
827                let value = s.clone();
828                self.advance()?;
829                Ok(AstNode::String(value))
830            }
831            Token::Number(n) => {
832                let value = *n;
833                self.advance()?;
834                Ok(AstNode::Number(value))
835            }
836            Token::True => {
837                self.advance()?;
838                Ok(AstNode::Boolean(true))
839            }
840            Token::False => {
841                self.advance()?;
842                Ok(AstNode::Boolean(false))
843            }
844            Token::Null => {
845                self.advance()?;
846                Ok(AstNode::Null)
847            }
848            Token::Undefined => {
849                self.advance()?;
850                Ok(AstNode::Undefined)
851            }
852            Token::Regex { pattern, flags } => {
853                let pat = pattern.clone();
854                let flg = flags.clone();
855                self.advance()?;
856                Ok(AstNode::Regex {
857                    pattern: pat,
858                    flags: flg,
859                })
860            }
861            Token::Identifier(name) => {
862                let name = name.clone();
863                self.advance()?;
864                Ok(AstNode::Path {
865                    steps: vec![PathStep::new(AstNode::Name(name))],
866                })
867            }
868            Token::Variable(name) => {
869                let name = name.clone();
870                self.advance()?;
871                Ok(AstNode::Variable(name))
872            }
873            Token::ParentVariable(name) => {
874                let name = name.clone();
875                self.advance()?;
876                Ok(AstNode::ParentVariable(name))
877            }
878            Token::LeftParen => {
879                self.advance()?; // skip '('
880
881                // Check for empty parentheses () which means undefined
882                if self.current_token == Token::RightParen {
883                    self.advance()?;
884                    return Ok(AstNode::Undefined);
885                }
886
887                // Parse block expressions (separated by semicolons)
888                // NOTE: Parentheses ALWAYS create a block in JSONata, even with a single expression.
889                // This is important for variable scoping - ( $x := value ) creates a new scope.
890                let mut expressions = vec![self.parse_expression(0)?];
891
892                while self.current_token == Token::Semicolon {
893                    self.advance()?;
894                    if self.current_token == Token::RightParen {
895                        break;
896                    }
897                    expressions.push(self.parse_expression(0)?);
898                }
899
900                self.expect(Token::RightParen)?;
901
902                // Always create a block, matching JavaScript implementation
903                Ok(AstNode::Block(expressions))
904            }
905            Token::LeftBracket => {
906                self.advance()?; // skip '['
907
908                let mut elements = Vec::new();
909
910                if self.current_token != Token::RightBracket {
911                    loop {
912                        let element = self.parse_expression(0)?;
913                        elements.push(element);
914
915                        if self.current_token != Token::Comma {
916                            break;
917                        }
918                        self.advance()?;
919                    }
920                }
921
922                self.expect(Token::RightBracket)?;
923                Ok(AstNode::Array(elements))
924            }
925            Token::LeftBrace => {
926                self.advance()?; // skip '{'
927
928                let mut pairs = Vec::new();
929
930                if self.current_token != Token::RightBrace {
931                    loop {
932                        let key = self.parse_expression(0)?;
933                        self.expect(Token::Colon)?;
934                        let value = self.parse_expression(0)?;
935                        pairs.push((key, value));
936
937                        if self.current_token != Token::Comma {
938                            break;
939                        }
940                        self.advance()?;
941                    }
942                }
943
944                self.expect(Token::RightBrace)?;
945                Ok(AstNode::Object(pairs))
946            }
947            Token::Pipe => {
948                // Transform operator: |location|update[,delete]|
949                self.advance()?; // skip first '|'
950
951                // Parse location expression
952                let location = self.parse_expression(0)?;
953
954                // Expect second '|'
955                self.expect(Token::Pipe)?;
956
957                // Parse update expression (object constructor)
958                let update = self.parse_expression(0)?;
959
960                // Check for optional delete part
961                let delete = if self.current_token == Token::Comma {
962                    self.advance()?; // skip comma
963                    Some(Box::new(self.parse_expression(0)?))
964                } else {
965                    None
966                };
967
968                // Expect final '|'
969                self.expect(Token::Pipe)?;
970
971                Ok(AstNode::Transform {
972                    location: Box::new(location),
973                    update: Box::new(update),
974                    delete,
975                })
976            }
977            Token::Minus => {
978                self.advance()?;
979                let operand = self.parse_expression(70)?; // High precedence for unary
980                Ok(AstNode::Unary {
981                    op: UnaryOp::Negate,
982                    operand: Box::new(operand),
983                })
984            }
985            Token::Star => {
986                // Wildcard operator in primary position
987                self.advance()?;
988                Ok(AstNode::Wildcard)
989            }
990            Token::StarStar => {
991                // Descendant operator in primary position
992                self.advance()?;
993                Ok(AstNode::Descendant)
994            }
995            Token::Percent => {
996                // Parent operator in primary position. Label is resolved by
997                // ast_transform -- this empty string is never observed by
998                // the evaluator (ast_transform fills every AstNode::Parent
999                // ("") with a real label or errors S0217).
1000                self.advance()?;
1001                Ok(AstNode::Parent(String::new()))
1002            }
1003            Token::Function => {
1004                // Parse lambda: function($param1, $param2, ...) { body }
1005                self.advance()?; // skip 'function'
1006                self.expect(Token::LeftParen)?;
1007
1008                // Parse parameters
1009                let mut params = Vec::new();
1010                if self.current_token != Token::RightParen {
1011                    loop {
1012                        match &self.current_token {
1013                            Token::Variable(name) => {
1014                                params.push(name.clone());
1015                                self.advance()?;
1016                            }
1017                            _ => {
1018                                return Err(ParserError::Expected {
1019                                    expected: "parameter name".to_string(),
1020                                    found: format!("{:?}", self.current_token),
1021                                })
1022                            }
1023                        }
1024
1025                        if self.current_token != Token::Comma {
1026                            break;
1027                        }
1028                        self.advance()?; // skip comma
1029                    }
1030                }
1031
1032                self.expect(Token::RightParen)?;
1033
1034                // Check for optional signature: <type-type:returntype>
1035                let signature = if self.current_token == Token::LessThan {
1036                    Some(self.parse_signature()?)
1037                } else {
1038                    None
1039                };
1040
1041                self.expect(Token::LeftBrace)?;
1042
1043                // Parse body
1044                let body = self.parse_expression(0)?;
1045
1046                self.expect(Token::RightBrace)?;
1047
1048                // Apply tail call optimization to the body
1049                let (optimized_body, is_thunk) = Self::tail_call_optimize(body);
1050
1051                Ok(AstNode::Lambda {
1052                    params,
1053                    body: Box::new(optimized_body),
1054                    signature,
1055                    thunk: is_thunk,
1056                })
1057            }
1058            _ => Err(ParserError::UnexpectedToken(format!(
1059                "{:?}",
1060                self.current_token
1061            ))),
1062        }
1063    }
1064
1065    /// Parse an expression with Pratt parsing
1066    fn parse_expression(&mut self, min_bp: u8) -> Result<AstNode, ParserError> {
1067        let mut lhs = self.parse_primary()?;
1068
1069        loop {
1070            // Check for end of expression
1071            if matches!(
1072                self.current_token,
1073                Token::Eof
1074                    | Token::RightParen
1075                    | Token::RightBracket
1076                    | Token::RightBrace
1077                    | Token::Comma
1078                    | Token::Semicolon
1079                    | Token::Colon
1080            ) {
1081                break;
1082            }
1083
1084            // Get binding power for current operator
1085            let (left_bp, right_bp) = match self.binding_power(&self.current_token) {
1086                Some(bp) => bp,
1087                None => break,
1088            };
1089
1090            if left_bp < min_bp {
1091                break;
1092            }
1093
1094            // Handle infix operators
1095            match &self.current_token {
1096                Token::Dot => {
1097                    self.advance()?;
1098
1099                    // Check for .[expr] syntax (array grouping)
1100                    if self.current_token == Token::LeftBracket {
1101                        self.advance()?;
1102
1103                        // Parse the array elements
1104                        let mut elements = Vec::new();
1105                        if self.current_token != Token::RightBracket {
1106                            loop {
1107                                elements.push(self.parse_expression(0)?);
1108                                if self.current_token != Token::Comma {
1109                                    break;
1110                                }
1111                                self.advance()?;
1112                            }
1113                        }
1114
1115                        self.expect(Token::RightBracket)?;
1116
1117                        // Create ArrayGroup node as a path step
1118                        let mut steps = match lhs {
1119                            AstNode::Path { steps } => steps,
1120                            _ => vec![PathStep::new(lhs)],
1121                        };
1122
1123                        steps.push(PathStep::new(AstNode::ArrayGroup(elements)));
1124                        lhs = AstNode::Path { steps };
1125                    } else if self.current_token == Token::LeftParen {
1126                        // Check for .(expr) syntax (function application)
1127                        self.advance()?;
1128
1129                        // Empty block `.()`: a parenthesised step with no
1130                        // expression (e.g. `Account.Order.().%`). jsonata-js
1131                        // treats `()` as an empty block that evaluates to
1132                        // undefined; keep it as a `FunctionApplication` of an
1133                        // empty `Block` so the ancestry pass can walk past it.
1134                        if self.current_token == Token::RightParen {
1135                            self.advance()?;
1136                            let mut steps = match lhs {
1137                                AstNode::Path { steps } => steps,
1138                                _ => vec![PathStep::new(lhs)],
1139                            };
1140                            steps.push(PathStep::new(AstNode::FunctionApplication(Box::new(
1141                                AstNode::Block(Vec::new()),
1142                            ))));
1143                            lhs = AstNode::Path { steps };
1144                            continue;
1145                        }
1146
1147                        // Parse the expression(s) to apply - may be block with semicolons
1148                        let mut expressions = vec![self.parse_expression(0)?];
1149
1150                        while self.current_token == Token::Semicolon {
1151                            self.advance()?;
1152                            if self.current_token == Token::RightParen {
1153                                break;
1154                            }
1155                            expressions.push(self.parse_expression(0)?);
1156                        }
1157
1158                        self.expect(Token::RightParen)?;
1159
1160                        // Wrap in Block if multiple expressions, otherwise use single expression
1161                        let expr = if expressions.len() == 1 {
1162                            expressions.into_iter().next().unwrap()
1163                        } else {
1164                            AstNode::Block(expressions)
1165                        };
1166
1167                        // Create FunctionApplication node as a path step
1168                        let mut steps = match lhs {
1169                            AstNode::Path { steps } => steps,
1170                            _ => vec![PathStep::new(lhs)],
1171                        };
1172
1173                        steps.push(PathStep::new(AstNode::FunctionApplication(Box::new(expr))));
1174                        lhs = AstNode::Path { steps };
1175                    } else {
1176                        // Normal dot path
1177                        let rhs = self.parse_expression(right_bp)?;
1178
1179                        // Flatten path expressions
1180                        let mut steps = match lhs {
1181                            AstNode::Path { steps } => steps,
1182                            // Convert string literals to field names when used as first step in path
1183                            // e.g., "foo".bar should behave like foo.bar
1184                            AstNode::String(field_name) => {
1185                                vec![PathStep::new(AstNode::Name(field_name))]
1186                            }
1187                            _ => vec![PathStep::new(lhs)],
1188                        };
1189
1190                        // S0213: The literal value cannot be used as a step within a path expression
1191                        // Numbers, booleans (true/false), and null cannot be path steps
1192                        match &rhs {
1193                            AstNode::Number(n) => {
1194                                return Err(ParserError::InvalidSyntax(
1195                                    format!("S0213: The literal value {} cannot be used as a step within a path expression", n),
1196                                ));
1197                            }
1198                            AstNode::Boolean(b) => {
1199                                return Err(ParserError::InvalidSyntax(
1200                                    format!("S0213: The literal value {} cannot be used as a step within a path expression", b),
1201                                ));
1202                            }
1203                            AstNode::Null => {
1204                                return Err(ParserError::InvalidSyntax(
1205                                    "S0213: The literal value null cannot be used as a step within a path expression".to_string(),
1206                                ));
1207                            }
1208                            _ => {}
1209                        }
1210
1211                        match rhs {
1212                            AstNode::Path {
1213                                steps: mut rhs_steps,
1214                            } => {
1215                                steps.append(&mut rhs_steps);
1216                            }
1217                            // Convert string literals to field names when they appear after a dot
1218                            // e.g., $."Field.Name" should access a property named "Field.Name"
1219                            AstNode::String(field_name) => {
1220                                steps.push(PathStep::new(AstNode::Name(field_name)));
1221                            }
1222                            _ => steps.push(PathStep::new(rhs)),
1223                        }
1224
1225                        // Check for following predicates and attach as stages to the last step
1226                        // This implements JSONata semantics where foo.bar[0] has [0] apply during extraction
1227                        while self.current_token == Token::LeftBracket {
1228                            self.advance()?;
1229
1230                            let predicate_expr = if self.current_token == Token::RightBracket {
1231                                // Empty brackets []
1232                                Box::new(AstNode::Boolean(true))
1233                            } else {
1234                                // Normal predicate expression
1235                                let pred = self.parse_expression(0)?;
1236                                Box::new(pred)
1237                            };
1238
1239                            self.expect(Token::RightBracket)?;
1240
1241                            // Attach predicate as stage to the last step
1242                            if let Some(last_step) = steps.last_mut() {
1243                                last_step.stages.push(Stage::Filter(predicate_expr));
1244                            }
1245                        }
1246
1247                        lhs = AstNode::Path { steps };
1248                    }
1249                }
1250                Token::LeftBracket => {
1251                    // S0209: A predicate cannot follow a grouping expression in a step
1252                    // Check if lhs is an ObjectTransform (grouping expression)
1253                    if matches!(lhs, AstNode::ObjectTransform { .. }) {
1254                        return Err(ParserError::InvalidSyntax(
1255                            "S0209: A predicate cannot follow a grouping expression in a step"
1256                                .to_string(),
1257                        ));
1258                    }
1259
1260                    self.advance()?;
1261
1262                    // Predicates in postfix position are always separate steps
1263                    // Predicates as stages are only attached during DOT operator parsing
1264                    if self.current_token == Token::RightBracket {
1265                        // Empty brackets []
1266                        self.advance()?;
1267
1268                        let mut steps = match lhs {
1269                            AstNode::Path { steps } => steps,
1270                            _ => vec![PathStep::new(lhs)],
1271                        };
1272
1273                        steps.push(PathStep::new(AstNode::Predicate(Box::new(
1274                            AstNode::Boolean(true),
1275                        ))));
1276                        lhs = AstNode::Path { steps };
1277                    } else {
1278                        // Normal predicate
1279                        let predicate = self.parse_expression(0)?;
1280                        self.expect(Token::RightBracket)?;
1281
1282                        let mut steps = match lhs {
1283                            AstNode::Path { steps } => steps,
1284                            _ => vec![PathStep::new(lhs)],
1285                        };
1286
1287                        steps.push(PathStep::new(AstNode::Predicate(Box::new(predicate))));
1288                        lhs = AstNode::Path { steps };
1289                    }
1290                }
1291                Token::LeftParen => {
1292                    self.advance()?;
1293
1294                    let mut args = Vec::new();
1295
1296                    if self.current_token != Token::RightParen {
1297                        loop {
1298                            // Check for ? placeholder (partial application)
1299                            if self.current_token == Token::Question {
1300                                args.push(AstNode::Placeholder);
1301                                self.advance()?;
1302                            } else {
1303                                args.push(self.parse_expression(0)?);
1304                            }
1305
1306                            if self.current_token != Token::Comma {
1307                                break;
1308                            }
1309                            self.advance()?;
1310                        }
1311                    }
1312
1313                    self.expect(Token::RightParen)?;
1314
1315                    // Check if lhs is a lambda or callable expression
1316                    match lhs {
1317                        // Direct invocations: lambda(args), block(args), chained calls, function result calls
1318                        AstNode::Lambda { .. }
1319                        | AstNode::Block(_)
1320                        | AstNode::Call { .. }
1321                        | AstNode::Function { .. } => {
1322                            lhs = AstNode::Call {
1323                                procedure: Box::new(lhs),
1324                                args,
1325                            };
1326                        }
1327                        ref other_lhs => {
1328                            // Extract function name from lhs
1329                            match other_lhs {
1330                                // Handle bare function names: uppercase()
1331                                AstNode::Path { steps } if steps.len() == 1 => {
1332                                    let name = match &steps[0].node {
1333                                        AstNode::Name(s) => s.clone(),
1334                                        _ => {
1335                                            return Err(ParserError::InvalidSyntax(
1336                                                "Invalid function name".to_string(),
1337                                            ))
1338                                        }
1339                                    };
1340                                    lhs = AstNode::Function {
1341                                        name,
1342                                        args,
1343                                        is_builtin: false,
1344                                    };
1345                                }
1346                                // Handle path ending with $function: foo.bar.$lowercase(args)
1347                                AstNode::Path { steps } if steps.len() > 1 => {
1348                                    let last_step = &steps[steps.len() - 1].node;
1349
1350                                    // Check if last step is a Variable (function reference)
1351                                    if let AstNode::Variable(func_name) = last_step {
1352                                        // Extract all but the last step as the path context
1353                                        let mut context_steps = steps.clone();
1354                                        context_steps.pop();
1355
1356                                        // Create function call
1357                                        let func_call = AstNode::Function {
1358                                            name: func_name.clone(),
1359                                            args: args.clone(),
1360                                            is_builtin: true, // Variable means $ prefix
1361                                        };
1362
1363                                        // Append function application to the path
1364                                        context_steps.push(PathStep::new(
1365                                            AstNode::FunctionApplication(Box::new(func_call)),
1366                                        ));
1367
1368                                        lhs = AstNode::Path {
1369                                            steps: context_steps,
1370                                        };
1371                                    }
1372                                    // Check if last step is a Lambda (inline function in path)
1373                                    else if let AstNode::Lambda {
1374                                        params,
1375                                        body,
1376                                        signature,
1377                                        thunk,
1378                                    } = last_step
1379                                    {
1380                                        // Extract all but the last step as the path context
1381                                        let mut context_steps = steps.clone();
1382                                        context_steps.pop();
1383
1384                                        // In path context, determine if we need to prepend $
1385                                        // - If fewer args than params, prepend $ (context value) as first arg
1386                                        // - If args == params, use args as-is
1387                                        let full_args = if args.len() < params.len() {
1388                                            let mut new_args =
1389                                                vec![AstNode::Variable("$".to_string())];
1390                                            new_args.extend(args.clone());
1391                                            new_args
1392                                        } else {
1393                                            args.clone()
1394                                        };
1395
1396                                        // Create a lambda invocation block
1397                                        // ($__path_lambda := lambda; $__path_lambda(args...))
1398                                        let lambda_invocation = AstNode::Block(vec![
1399                                            AstNode::Binary {
1400                                                op: crate::ast::BinaryOp::ColonEqual,
1401                                                lhs: Box::new(AstNode::Variable(
1402                                                    "__path_lambda__".to_string(),
1403                                                )),
1404                                                rhs: Box::new(AstNode::Lambda {
1405                                                    params: params.clone(),
1406                                                    body: body.clone(),
1407                                                    signature: signature.clone(),
1408                                                    thunk: *thunk,
1409                                                }),
1410                                            },
1411                                            AstNode::Function {
1412                                                name: "__path_lambda__".to_string(),
1413                                                args: full_args,
1414                                                is_builtin: true,
1415                                            },
1416                                        ]);
1417
1418                                        // Append as function application to the path
1419                                        context_steps.push(PathStep::new(
1420                                            AstNode::FunctionApplication(Box::new(
1421                                                lambda_invocation,
1422                                            )),
1423                                        ));
1424
1425                                        lhs = AstNode::Path {
1426                                            steps: context_steps,
1427                                        };
1428                                    } else {
1429                                        return Err(ParserError::InvalidSyntax(
1430                                            "Invalid function call".to_string(),
1431                                        ));
1432                                    }
1433                                }
1434                                // Handle $-prefixed function names: $uppercase()
1435                                AstNode::Variable(name) => {
1436                                    lhs = AstNode::Function {
1437                                        name: name.clone(),
1438                                        args,
1439                                        is_builtin: true,
1440                                    };
1441                                }
1442                                _ => {
1443                                    return Err(ParserError::InvalidSyntax(
1444                                        "Invalid function call".to_string(),
1445                                    ))
1446                                }
1447                            };
1448                        }
1449                    }
1450                }
1451                Token::Question => {
1452                    self.advance()?;
1453                    let then_branch = self.parse_expression(0)?;
1454
1455                    let else_branch = if self.current_token == Token::Colon {
1456                        self.advance()?;
1457                        // Use 0 for right-associativity: a ? b : c ? d : e parses as a ? b : (c ? d : e)
1458                        Some(Box::new(self.parse_expression(0)?))
1459                    } else {
1460                        None
1461                    };
1462
1463                    lhs = AstNode::Conditional {
1464                        condition: Box::new(lhs),
1465                        then_branch: Box::new(then_branch),
1466                        else_branch,
1467                    };
1468                }
1469                Token::LeftBrace => {
1470                    // S0210: Each step can only have one grouping expression
1471                    // Check if lhs is already an ObjectTransform
1472                    if matches!(lhs, AstNode::ObjectTransform { .. }) {
1473                        return Err(ParserError::InvalidSyntax(
1474                            "S0210: Each step can only have one grouping expression".to_string(),
1475                        ));
1476                    }
1477
1478                    // Object constructor as postfix: expr{key: value}
1479                    self.advance()?; // skip '{'
1480
1481                    let mut pairs = Vec::new();
1482
1483                    if self.current_token != Token::RightBrace {
1484                        loop {
1485                            // Parse key expression - parse_expression handles identifiers correctly
1486                            let key = self.parse_expression(0)?;
1487
1488                            self.expect(Token::Colon)?;
1489
1490                            // Parse value expression - can be any expression including paths
1491                            let value = self.parse_expression(0)?;
1492
1493                            pairs.push((key, value));
1494
1495                            if self.current_token != Token::Comma {
1496                                break;
1497                            }
1498                            self.advance()?; // skip comma
1499                        }
1500                    }
1501
1502                    self.expect(Token::RightBrace)?;
1503
1504                    // Object constructor with input: lhs{k:v} means transform lhs using the object pattern
1505                    lhs = AstNode::ObjectTransform {
1506                        input: Box::new(lhs),
1507                        pattern: pairs,
1508                    };
1509                }
1510                Token::Hash => {
1511                    // Index binding operator: #$var
1512                    // Binds the current array index to the specified variable
1513                    self.advance()?; // skip '#'
1514
1515                    // Expect a variable name. A bare `#` without a `$var` (e.g.
1516                    // `Account.Order@$o#i.Product`) is S0214, mirroring jsonata-js's
1517                    // inline check for `@`/`#` (parser.js ~L834-847).
1518                    let var_name = match &self.current_token {
1519                        Token::Variable(name) => name.clone(),
1520                        _ => {
1521                            return Err(ParserError::Coded {
1522                                code: "S0214",
1523                                message: "Expected a variable reference after #".to_string(),
1524                            });
1525                        }
1526                    };
1527                    self.advance()?; // skip variable
1528
1529                    // Produces a generic Binary(IndexBind) marker -- ast_transform
1530                    // resolves this into a PathStep.index_var flag, mirroring how
1531                    // @$var/FocusBind is represented (see Token::At below). Using
1532                    // the same generic Binary shape (rather than a dedicated
1533                    // AstNode::IndexBind variant) lets that variant be retired
1534                    // from ast.rs entirely.
1535                    lhs = AstNode::Binary {
1536                        op: BinaryOp::IndexBind,
1537                        lhs: Box::new(lhs),
1538                        rhs: Box::new(AstNode::Variable(var_name)),
1539                    };
1540                }
1541                Token::At => {
1542                    // Focus binding operator: @$var
1543                    // Produces a generic Binary(FocusBind) marker -- ast_transform
1544                    // resolves this into a PathStep.focus flag, matching
1545                    // jsonata-js's parser.js:834-847 (which does the same S0214
1546                    // check inline, deferring all other semantics to processAST).
1547                    self.advance()?; // skip '@'
1548
1549                    let var_name = match &self.current_token {
1550                        Token::Variable(name) => name.clone(),
1551                        _ => {
1552                            return Err(ParserError::Coded {
1553                                code: "S0214",
1554                                message: "Expected a variable reference after @".to_string(),
1555                            });
1556                        }
1557                    };
1558                    self.advance()?; // skip variable
1559
1560                    lhs = AstNode::Binary {
1561                        op: BinaryOp::FocusBind,
1562                        lhs: Box::new(lhs),
1563                        rhs: Box::new(AstNode::Variable(var_name)),
1564                    };
1565                }
1566                Token::Caret => {
1567                    // Sort operator: ^(expr) or ^(<expr) or ^(>expr)
1568                    self.advance()?; // skip '^'
1569                    self.expect(Token::LeftParen)?;
1570
1571                    let mut terms = Vec::new();
1572
1573                    loop {
1574                        // Check for optional sort direction prefix
1575                        let ascending = match &self.current_token {
1576                            Token::LessThan => {
1577                                self.advance()?;
1578                                true
1579                            }
1580                            Token::GreaterThan => {
1581                                self.advance()?;
1582                                false
1583                            }
1584                            _ => true, // Default to ascending
1585                        };
1586
1587                        // Parse the sort expression
1588                        let expr = self.parse_expression(0)?;
1589                        terms.push((expr, ascending));
1590
1591                        // Check for more sort terms
1592                        if self.current_token != Token::Comma {
1593                            break;
1594                        }
1595                        self.advance()?; // skip comma
1596                    }
1597
1598                    self.expect(Token::RightParen)?;
1599
1600                    lhs = AstNode::Sort {
1601                        input: Box::new(lhs),
1602                        terms,
1603                    };
1604                }
1605                _ => {
1606                    // Binary operators
1607                    let op = match &self.current_token {
1608                        // Contextual keyword operators
1609                        Token::Identifier(name) if name == "and" => BinaryOp::And,
1610                        Token::Identifier(name) if name == "or" => BinaryOp::Or,
1611                        Token::Identifier(name) if name == "in" => BinaryOp::In,
1612                        // Regular operators
1613                        Token::Plus => BinaryOp::Add,
1614                        Token::Minus => BinaryOp::Subtract,
1615                        Token::Star => BinaryOp::Multiply,
1616                        Token::Slash => BinaryOp::Divide,
1617                        Token::Percent => BinaryOp::Modulo,
1618                        Token::Equal => BinaryOp::Equal,
1619                        Token::NotEqual => BinaryOp::NotEqual,
1620                        Token::LessThan => BinaryOp::LessThan,
1621                        Token::LessThanOrEqual => BinaryOp::LessThanOrEqual,
1622                        Token::GreaterThan => BinaryOp::GreaterThan,
1623                        Token::GreaterThanOrEqual => BinaryOp::GreaterThanOrEqual,
1624                        Token::And => BinaryOp::And,
1625                        Token::Or => BinaryOp::Or,
1626                        Token::In => BinaryOp::In,
1627                        Token::Ampersand => BinaryOp::Concatenate,
1628                        Token::DotDot => BinaryOp::Range,
1629                        Token::ColonEqual => BinaryOp::ColonEqual,
1630                        Token::QuestionQuestion => BinaryOp::Coalesce,
1631                        Token::QuestionColon => BinaryOp::Default,
1632                        Token::TildeArrow => BinaryOp::ChainPipe,
1633                        _ => {
1634                            return Err(ParserError::UnexpectedToken(format!(
1635                                "{:?}",
1636                                self.current_token
1637                            )))
1638                        }
1639                    };
1640
1641                    self.advance()?;
1642                    let rhs = self.parse_expression(right_bp)?;
1643
1644                    lhs = AstNode::Binary {
1645                        op,
1646                        lhs: Box::new(lhs),
1647                        rhs: Box::new(rhs),
1648                    };
1649                }
1650            }
1651        }
1652
1653        Ok(lhs)
1654    }
1655
1656    pub fn parse(&mut self) -> Result<AstNode, ParserError> {
1657        let ast = self.parse_expression(0)?;
1658
1659        if self.current_token != Token::Eof {
1660            return Err(ParserError::Expected {
1661                expected: "end of expression".to_string(),
1662                found: format!("{:?}", self.current_token),
1663            });
1664        }
1665
1666        Ok(ast)
1667    }
1668
1669    /// Analyze an expression for tail call optimization
1670    /// Returns (optimized_expr, is_thunk) where:
1671    /// - optimized_expr is the expression (unchanged)
1672    /// - is_thunk is true if the expression's tail position is a function call
1673    ///
1674    /// A tail position is where a function call's result is directly returned:
1675    /// - The body itself if it's a function call
1676    /// - Both branches of a conditional at tail position
1677    /// - The last expression of a block at tail position
1678    fn tail_call_optimize(expr: AstNode) -> (AstNode, bool) {
1679        let is_thunk = Self::is_tail_call(&expr);
1680        (expr, is_thunk)
1681    }
1682
1683    /// Check if an expression is in tail call position
1684    /// Returns true if the expression is a function call (or contains function calls in all tail positions)
1685    fn is_tail_call(expr: &AstNode) -> bool {
1686        match expr {
1687            // Direct function calls are tail calls
1688            AstNode::Function { .. } => true,
1689            AstNode::Call { .. } => true,
1690
1691            // Conditional: both branches must be tail calls (or at least one if only one branch)
1692            AstNode::Conditional {
1693                then_branch,
1694                else_branch,
1695                ..
1696            } => {
1697                let then_is_tail = Self::is_tail_call(then_branch);
1698                let else_is_tail = else_branch.as_ref().is_some_and(|e| Self::is_tail_call(e));
1699                // At least one branch should be a tail call for TCO to be useful
1700                then_is_tail || else_is_tail
1701            }
1702
1703            // Block: last expression is tail position
1704            AstNode::Block(exprs) => exprs.last().is_some_and(Self::is_tail_call),
1705
1706            // Variable binding with result: the result expression is tail position
1707            AstNode::Binary {
1708                op: BinaryOp::ColonEqual,
1709                rhs,
1710                ..
1711            } => {
1712                // The rhs (or next expression) could be tail position
1713                // But typically := is used for assignment within blocks
1714                // Check if rhs is a block (common pattern)
1715                Self::is_tail_call(rhs)
1716            }
1717
1718            // Anything else is not a tail call
1719            _ => false,
1720        }
1721    }
1722}
1723
1724/// Parse a JSONata expression string into an AST
1725///
1726/// This is the main entry point for parsing. Runs the post-parse
1727/// ast_transform pass (ancestor-slot resolution, @/#/% unification)
1728/// unconditionally, matching jsonata-js's processAST always running
1729/// immediately after the raw Pratt parse.
1730pub fn parse(expression: &str) -> Result<AstNode, ParserError> {
1731    let mut parser = Parser::new(expression.to_string())?;
1732    let raw_ast = parser.parse()?;
1733    crate::ast_transform::resolve_ancestry(raw_ast).map_err(|e| match e {
1734        crate::ast_transform::AstTransformError::Coded { code, message } => {
1735            ParserError::Coded { code, message }
1736        }
1737    })
1738}
1739
1740#[cfg(test)]
1741mod tests {
1742    use super::*;
1743
1744    // Lexer tests
1745    #[test]
1746    fn test_lexer_numbers() {
1747        let mut lexer = Lexer::new("42 3.14 -10 2.5e10 1E-5".to_string());
1748
1749        assert_eq!(lexer.next_token().unwrap(), Token::Number(42.0));
1750        assert_eq!(lexer.next_token().unwrap(), Token::Number(3.14));
1751        // Negation is handled as a unary operator, not part of the number literal
1752        assert_eq!(lexer.next_token().unwrap(), Token::Minus);
1753        assert_eq!(lexer.next_token().unwrap(), Token::Number(10.0));
1754        assert_eq!(lexer.next_token().unwrap(), Token::Number(2.5e10));
1755        assert_eq!(lexer.next_token().unwrap(), Token::Number(1e-5));
1756        assert_eq!(lexer.next_token().unwrap(), Token::Eof);
1757    }
1758
1759    #[test]
1760    fn test_lexer_strings() {
1761        let mut lexer = Lexer::new(r#""hello" 'world' "with\nnewline""#.to_string());
1762
1763        assert_eq!(
1764            lexer.next_token().unwrap(),
1765            Token::String("hello".to_string())
1766        );
1767        assert_eq!(
1768            lexer.next_token().unwrap(),
1769            Token::String("world".to_string())
1770        );
1771        assert_eq!(
1772            lexer.next_token().unwrap(),
1773            Token::String("with\nnewline".to_string())
1774        );
1775        assert_eq!(lexer.next_token().unwrap(), Token::Eof);
1776    }
1777
1778    #[test]
1779    fn test_lexer_string_escapes() {
1780        let mut lexer = Lexer::new(r#""a\"b\\c\/d""#.to_string());
1781        assert_eq!(
1782            lexer.next_token().unwrap(),
1783            Token::String("a\"b\\c/d".to_string())
1784        );
1785    }
1786
1787    #[test]
1788    fn test_lexer_keywords() {
1789        let mut lexer = Lexer::new("true false null and or in".to_string());
1790
1791        assert_eq!(lexer.next_token().unwrap(), Token::True);
1792        assert_eq!(lexer.next_token().unwrap(), Token::False);
1793        assert_eq!(lexer.next_token().unwrap(), Token::Null);
1794        // "and", "or", "in" are now contextual keywords - lexer emits them as identifiers
1795        assert_eq!(
1796            lexer.next_token().unwrap(),
1797            Token::Identifier("and".to_string())
1798        );
1799        assert_eq!(
1800            lexer.next_token().unwrap(),
1801            Token::Identifier("or".to_string())
1802        );
1803        assert_eq!(
1804            lexer.next_token().unwrap(),
1805            Token::Identifier("in".to_string())
1806        );
1807        assert_eq!(lexer.next_token().unwrap(), Token::Eof);
1808    }
1809
1810    #[test]
1811    fn test_lexer_identifiers() {
1812        let mut lexer = Lexer::new("foo bar_baz test123".to_string());
1813
1814        assert_eq!(
1815            lexer.next_token().unwrap(),
1816            Token::Identifier("foo".to_string())
1817        );
1818        assert_eq!(
1819            lexer.next_token().unwrap(),
1820            Token::Identifier("bar_baz".to_string())
1821        );
1822        assert_eq!(
1823            lexer.next_token().unwrap(),
1824            Token::Identifier("test123".to_string())
1825        );
1826        assert_eq!(lexer.next_token().unwrap(), Token::Eof);
1827    }
1828
1829    #[test]
1830    fn test_lexer_variables() {
1831        let mut lexer = Lexer::new("$var $foo_bar".to_string());
1832
1833        assert_eq!(
1834            lexer.next_token().unwrap(),
1835            Token::Variable("var".to_string())
1836        );
1837        assert_eq!(
1838            lexer.next_token().unwrap(),
1839            Token::Variable("foo_bar".to_string())
1840        );
1841        assert_eq!(lexer.next_token().unwrap(), Token::Eof);
1842    }
1843
1844    #[test]
1845    fn test_lexer_operators() {
1846        // Test non-slash operators first
1847        let mut lexer = Lexer::new("+ - * % = != < <= > >= & . .. := ".to_string());
1848
1849        assert_eq!(lexer.next_token().unwrap(), Token::Plus);
1850        assert_eq!(lexer.next_token().unwrap(), Token::Minus);
1851        assert_eq!(lexer.next_token().unwrap(), Token::Star);
1852        assert_eq!(lexer.next_token().unwrap(), Token::Percent);
1853        assert_eq!(lexer.next_token().unwrap(), Token::Equal);
1854        assert_eq!(lexer.next_token().unwrap(), Token::NotEqual);
1855        assert_eq!(lexer.next_token().unwrap(), Token::LessThan);
1856        assert_eq!(lexer.next_token().unwrap(), Token::LessThanOrEqual);
1857        assert_eq!(lexer.next_token().unwrap(), Token::GreaterThan);
1858        assert_eq!(lexer.next_token().unwrap(), Token::GreaterThanOrEqual);
1859        assert_eq!(lexer.next_token().unwrap(), Token::Ampersand);
1860        assert_eq!(lexer.next_token().unwrap(), Token::Dot);
1861        assert_eq!(lexer.next_token().unwrap(), Token::DotDot);
1862        assert_eq!(lexer.next_token().unwrap(), Token::ColonEqual);
1863        assert_eq!(lexer.next_token().unwrap(), Token::Eof);
1864
1865        // Slash is context-dependent: after a value token it's division, otherwise regex.
1866        // Test slash after a number (value context) to get Token::Slash.
1867        let mut lexer2 = Lexer::new("42 / 2".to_string());
1868        assert_eq!(lexer2.next_token().unwrap(), Token::Number(42.0));
1869        assert_eq!(lexer2.next_token().unwrap(), Token::Slash);
1870        assert_eq!(lexer2.next_token().unwrap(), Token::Number(2.0));
1871        assert_eq!(lexer2.next_token().unwrap(), Token::Eof);
1872    }
1873
1874    #[test]
1875    fn test_lexer_delimiters() {
1876        let mut lexer = Lexer::new("()[]{},:;?".to_string());
1877
1878        assert_eq!(lexer.next_token().unwrap(), Token::LeftParen);
1879        assert_eq!(lexer.next_token().unwrap(), Token::RightParen);
1880        assert_eq!(lexer.next_token().unwrap(), Token::LeftBracket);
1881        assert_eq!(lexer.next_token().unwrap(), Token::RightBracket);
1882        assert_eq!(lexer.next_token().unwrap(), Token::LeftBrace);
1883        assert_eq!(lexer.next_token().unwrap(), Token::RightBrace);
1884        assert_eq!(lexer.next_token().unwrap(), Token::Comma);
1885        assert_eq!(lexer.next_token().unwrap(), Token::Colon);
1886        assert_eq!(lexer.next_token().unwrap(), Token::Semicolon);
1887        assert_eq!(lexer.next_token().unwrap(), Token::Question);
1888        assert_eq!(lexer.next_token().unwrap(), Token::Eof);
1889    }
1890
1891    #[test]
1892    fn test_empty_brackets() {
1893        let mut parser = Parser::new("foo[]".to_string()).unwrap();
1894        let ast = parser.parse().unwrap();
1895
1896        // Should create a Path with two steps: Name("foo") and Predicate(Boolean(true))
1897        if let AstNode::Path { steps } = ast {
1898            assert_eq!(steps.len(), 2);
1899            assert!(matches!(steps[0].node, AstNode::Name(ref s) if s == "foo"));
1900            if let AstNode::Predicate(pred) = &steps[1].node {
1901                assert!(matches!(**pred, AstNode::Boolean(true)));
1902            } else {
1903                panic!("Expected Predicate as second step, got {:?}", steps[1].node);
1904            }
1905        } else {
1906            panic!("Expected Path, got {:?}", ast);
1907        }
1908    }
1909
1910    #[test]
1911    fn test_lexer_comments() {
1912        let mut lexer = Lexer::new("foo /* comment */ bar".to_string());
1913
1914        assert_eq!(
1915            lexer.next_token().unwrap(),
1916            Token::Identifier("foo".to_string())
1917        );
1918        assert_eq!(
1919            lexer.next_token().unwrap(),
1920            Token::Identifier("bar".to_string())
1921        );
1922        assert_eq!(lexer.next_token().unwrap(), Token::Eof);
1923    }
1924
1925    #[test]
1926    fn test_lexer_backtick_names() {
1927        let mut lexer = Lexer::new("`field name` `with-dash`".to_string());
1928
1929        assert_eq!(
1930            lexer.next_token().unwrap(),
1931            Token::Identifier("field name".to_string())
1932        );
1933        assert_eq!(
1934            lexer.next_token().unwrap(),
1935            Token::Identifier("with-dash".to_string())
1936        );
1937        assert_eq!(lexer.next_token().unwrap(), Token::Eof);
1938    }
1939
1940    // Parser tests
1941    #[test]
1942    fn test_parse_number() {
1943        let ast = parse("42").unwrap();
1944        assert_eq!(ast, AstNode::Number(42.0));
1945    }
1946
1947    #[test]
1948    fn test_parse_string() {
1949        let ast = parse(r#""hello""#).unwrap();
1950        assert_eq!(ast, AstNode::String("hello".to_string()));
1951    }
1952
1953    #[test]
1954    fn test_parse_boolean() {
1955        let ast = parse("true").unwrap();
1956        assert_eq!(ast, AstNode::Boolean(true));
1957
1958        let ast = parse("false").unwrap();
1959        assert_eq!(ast, AstNode::Boolean(false));
1960    }
1961
1962    #[test]
1963    fn test_parse_null() {
1964        let ast = parse("null").unwrap();
1965        assert_eq!(ast, AstNode::Null);
1966    }
1967
1968    #[test]
1969    fn test_parse_variable() {
1970        let ast = parse("$var").unwrap();
1971        assert_eq!(ast, AstNode::Variable("var".to_string()));
1972    }
1973
1974    #[test]
1975    fn test_parse_identifier() {
1976        let ast = parse("foo").unwrap();
1977        assert_eq!(
1978            ast,
1979            AstNode::Path {
1980                steps: vec![PathStep::new(AstNode::Name("foo".to_string()))]
1981            }
1982        );
1983    }
1984
1985    #[test]
1986    fn test_parse_addition() {
1987        let ast = parse("1 + 2").unwrap();
1988        match ast {
1989            AstNode::Binary { op, lhs, rhs } => {
1990                assert_eq!(op, BinaryOp::Add);
1991                assert_eq!(*lhs, AstNode::Number(1.0));
1992                assert_eq!(*rhs, AstNode::Number(2.0));
1993            }
1994            _ => panic!("Expected Binary node"),
1995        }
1996    }
1997
1998    #[test]
1999    fn test_parse_precedence() {
2000        // 1 + 2 * 3 should parse as 1 + (2 * 3)
2001        let ast = parse("1 + 2 * 3").unwrap();
2002        match ast {
2003            AstNode::Binary {
2004                op: BinaryOp::Add,
2005                lhs,
2006                rhs,
2007            } => {
2008                assert_eq!(*lhs, AstNode::Number(1.0));
2009                match *rhs {
2010                    AstNode::Binary {
2011                        op: BinaryOp::Multiply,
2012                        lhs,
2013                        rhs,
2014                    } => {
2015                        assert_eq!(*lhs, AstNode::Number(2.0));
2016                        assert_eq!(*rhs, AstNode::Number(3.0));
2017                    }
2018                    _ => panic!("Expected Binary node for multiplication"),
2019                }
2020            }
2021            _ => panic!("Expected Binary node for addition"),
2022        }
2023    }
2024
2025    #[test]
2026    fn test_parse_parentheses() {
2027        // (1 + 2) * 3 should parse as Block([1 + 2]) * 3
2028        // Parenthesized expressions always create a Block in JSONata
2029        let ast = parse("(1 + 2) * 3").unwrap();
2030        match ast {
2031            AstNode::Binary {
2032                op: BinaryOp::Multiply,
2033                lhs,
2034                rhs,
2035            } => {
2036                match *lhs {
2037                    AstNode::Block(ref exprs) => {
2038                        assert_eq!(exprs.len(), 1);
2039                        match &exprs[0] {
2040                            AstNode::Binary {
2041                                op: BinaryOp::Add,
2042                                lhs,
2043                                rhs,
2044                            } => {
2045                                assert_eq!(**lhs, AstNode::Number(1.0));
2046                                assert_eq!(**rhs, AstNode::Number(2.0));
2047                            }
2048                            _ => panic!("Expected Binary node for addition inside block"),
2049                        }
2050                    }
2051                    _ => panic!(
2052                        "Expected Block node for parenthesized expression, got {:?}",
2053                        lhs
2054                    ),
2055                }
2056                assert_eq!(*rhs, AstNode::Number(3.0));
2057            }
2058            _ => panic!("Expected Binary node for multiplication"),
2059        }
2060    }
2061
2062    #[test]
2063    fn test_parse_array() {
2064        let ast = parse("[1, 2, 3]").unwrap();
2065        match ast {
2066            AstNode::Array(elements) => {
2067                assert_eq!(elements.len(), 3);
2068                assert_eq!(elements[0], AstNode::Number(1.0));
2069                assert_eq!(elements[1], AstNode::Number(2.0));
2070                assert_eq!(elements[2], AstNode::Number(3.0));
2071            }
2072            _ => panic!("Expected Array node"),
2073        }
2074    }
2075
2076    #[test]
2077    fn test_parse_object() {
2078        let ast = parse(r#"{"a": 1, "b": 2}"#).unwrap();
2079        match ast {
2080            AstNode::Object(pairs) => {
2081                assert_eq!(pairs.len(), 2);
2082                assert_eq!(pairs[0].0, AstNode::String("a".to_string()));
2083                assert_eq!(pairs[0].1, AstNode::Number(1.0));
2084                assert_eq!(pairs[1].0, AstNode::String("b".to_string()));
2085                assert_eq!(pairs[1].1, AstNode::Number(2.0));
2086            }
2087            _ => panic!("Expected Object node"),
2088        }
2089    }
2090
2091    #[test]
2092    fn test_parse_path() {
2093        let ast = parse("foo.bar").unwrap();
2094        match ast {
2095            AstNode::Path { steps } => {
2096                assert_eq!(steps.len(), 2);
2097                assert_eq!(steps[0].node, AstNode::Name("foo".to_string()));
2098                assert_eq!(steps[1].node, AstNode::Name("bar".to_string()));
2099            }
2100            _ => panic!("Expected Path node"),
2101        }
2102    }
2103
2104    #[test]
2105    fn test_parse_function_call() {
2106        let ast = parse("sum(1, 2, 3)").unwrap();
2107        match ast {
2108            AstNode::Function {
2109                name,
2110                args,
2111                is_builtin,
2112            } => {
2113                assert_eq!(name, "sum");
2114                assert_eq!(args.len(), 3);
2115                assert_eq!(args[0], AstNode::Number(1.0));
2116                assert_eq!(args[1], AstNode::Number(2.0));
2117                assert_eq!(args[2], AstNode::Number(3.0));
2118                assert!(!is_builtin); // Bare function call (no $ prefix)
2119            }
2120            _ => panic!("Expected Function node"),
2121        }
2122    }
2123
2124    #[test]
2125    fn test_parse_conditional() {
2126        let ast = parse("x > 0 ? 1 : -1").unwrap();
2127        match ast {
2128            AstNode::Conditional {
2129                condition,
2130                then_branch,
2131                else_branch,
2132            } => {
2133                assert!(matches!(*condition, AstNode::Binary { .. }));
2134                assert_eq!(*then_branch, AstNode::Number(1.0));
2135                // Negative numbers are parsed as Unary { Negate, Number(1.0) }
2136                assert_eq!(
2137                    else_branch,
2138                    Some(Box::new(AstNode::Unary {
2139                        op: UnaryOp::Negate,
2140                        operand: Box::new(AstNode::Number(1.0)),
2141                    }))
2142                );
2143            }
2144            _ => panic!("Expected Conditional node"),
2145        }
2146    }
2147
2148    #[test]
2149    fn test_parse_comparison() {
2150        let ast = parse("x < 10").unwrap();
2151        match ast {
2152            AstNode::Binary { op, .. } => {
2153                assert_eq!(op, BinaryOp::LessThan);
2154            }
2155            _ => panic!("Expected Binary node"),
2156        }
2157    }
2158
2159    #[test]
2160    fn test_parse_logical_and() {
2161        let ast = parse("true and false").unwrap();
2162        match ast {
2163            AstNode::Binary { op, lhs, rhs } => {
2164                assert_eq!(op, BinaryOp::And);
2165                assert_eq!(*lhs, AstNode::Boolean(true));
2166                assert_eq!(*rhs, AstNode::Boolean(false));
2167            }
2168            _ => panic!("Expected Binary node"),
2169        }
2170    }
2171
2172    #[test]
2173    fn test_parse_string_concatenation() {
2174        let ast = parse(r#""hello" & " " & "world""#).unwrap();
2175        match ast {
2176            AstNode::Binary { op, .. } => {
2177                assert_eq!(op, BinaryOp::Concatenate);
2178            }
2179            _ => panic!("Expected Binary node"),
2180        }
2181    }
2182
2183    #[test]
2184    fn test_parse_unary_minus() {
2185        // Negative numbers are parsed as Unary { Negate, Number(5.0) }
2186        let ast = parse("-5").unwrap();
2187        assert_eq!(
2188            ast,
2189            AstNode::Unary {
2190                op: UnaryOp::Negate,
2191                operand: Box::new(AstNode::Number(5.0)),
2192            }
2193        );
2194    }
2195
2196    #[test]
2197    fn test_parse_block() {
2198        let ast = parse("(1; 2; 3)").unwrap();
2199        match ast {
2200            AstNode::Block(expressions) => {
2201                assert_eq!(expressions.len(), 3);
2202                assert_eq!(expressions[0], AstNode::Number(1.0));
2203                assert_eq!(expressions[1], AstNode::Number(2.0));
2204                assert_eq!(expressions[2], AstNode::Number(3.0));
2205            }
2206            _ => panic!("Expected Block node"),
2207        }
2208    }
2209
2210    #[test]
2211    fn test_parse_complex_expression() {
2212        // Test a more complex expression
2213        let ast = parse("(a + b) * c.d").unwrap();
2214        assert!(matches!(ast, AstNode::Binary { .. }));
2215    }
2216
2217    #[test]
2218    fn test_parse_dollar_function_call() {
2219        // Test $uppercase function
2220        let ast = parse(r#"$uppercase("hello")"#).unwrap();
2221        match ast {
2222            AstNode::Function {
2223                name,
2224                args,
2225                is_builtin,
2226            } => {
2227                assert_eq!(name, "uppercase");
2228                assert_eq!(args.len(), 1);
2229                assert_eq!(args[0], AstNode::String("hello".to_string()));
2230                assert!(is_builtin); // $ prefix means builtin
2231            }
2232            _ => panic!("Expected Function node"),
2233        }
2234
2235        // Test $sum function
2236        let ast = parse("$sum([1, 2, 3])").unwrap();
2237        match ast {
2238            AstNode::Function {
2239                name,
2240                args,
2241                is_builtin,
2242            } => {
2243                assert_eq!(name, "sum");
2244                assert_eq!(args.len(), 1);
2245                assert!(is_builtin); // $ prefix means builtin
2246            }
2247            _ => panic!("Expected Function node"),
2248        }
2249    }
2250
2251    #[test]
2252    fn test_parse_nested_dollar_functions() {
2253        // Test nested $function calls
2254        let ast = parse(r#"$length($lowercase("HELLO"))"#).unwrap();
2255        match ast {
2256            AstNode::Function {
2257                name,
2258                args,
2259                is_builtin,
2260            } => {
2261                assert_eq!(name, "length");
2262                assert_eq!(args.len(), 1);
2263                assert!(is_builtin);
2264                // Check nested function
2265                match &args[0] {
2266                    AstNode::Function {
2267                        name: inner_name,
2268                        is_builtin: inner_builtin,
2269                        ..
2270                    } => {
2271                        assert_eq!(inner_name, "lowercase");
2272                        assert!(inner_builtin);
2273                    }
2274                    _ => panic!("Expected nested Function node"),
2275                }
2276            }
2277            _ => panic!("Expected Function node"),
2278        }
2279    }
2280
2281    #[test]
2282    fn test_signature_with_repeat_modifier_parses() {
2283        // A lambda literal with a '+' (one-or-more) signature modifier must parse
2284        // without error. This was rejected before jsonata-js 2.2.1 compatibility work
2285        // ("Unexpected token in signature: Plus").
2286        let result = parse("λ($arg1, $arg2)<n+n:o>{{\"a\": $arg1, \"b\": $arg2}}(1, 2, 3)");
2287        assert!(
2288            result.is_ok(),
2289            "expected parse to succeed, got {:?}",
2290            result
2291        );
2292    }
2293
2294    #[test]
2295    fn test_parent_operator_parses_as_prefix() {
2296        // Tests the RAW grammar rule (bare `%` in primary position) in
2297        // isolation from ast_transform's ancestor resolution -- uses
2298        // Parser::new(...).parse() directly rather than the free `parse()`
2299        // function, since the free function now runs ast_transform
2300        // unconditionally (Step 8), and `%.OrderID` alone has no preceding
2301        // step for `%` to resolve against (that's covered by
2302        // ast_transform's own S0217 tests).
2303        let mut parser = Parser::new("%.OrderID".to_string()).unwrap();
2304        let ast = parser.parse().unwrap();
2305        // %.OrderID should parse as a path with two steps: Parent, then Name("OrderID")
2306        match ast {
2307            AstNode::Path { steps } => {
2308                assert_eq!(steps.len(), 2);
2309                assert!(matches!(steps[0].node, AstNode::Parent(_)));
2310                assert!(matches!(steps[1].node, AstNode::Name(ref n) if n == "OrderID"));
2311            }
2312            other => panic!("expected Path, got {:?}", other),
2313        }
2314    }
2315
2316    #[test]
2317    fn test_percent_still_parses_as_modulo_infix() {
2318        // Regression: % must still work as binary modulo when NOT in prefix position
2319        let ast = parse("10 % 3").unwrap();
2320        assert!(matches!(
2321            ast,
2322            AstNode::Binary {
2323                op: BinaryOp::Modulo,
2324                ..
2325            }
2326        ));
2327    }
2328
2329    #[test]
2330    fn test_focus_bind_parses_as_binary_marker() {
2331        // Tests the RAW grammar rule in isolation from ast_transform (which
2332        // now runs unconditionally in the free `parse()` and would rewrite
2333        // this into a Path with a `focus` flag instead -- see
2334        // ast_transform.rs's own real-parser-based tests for that).
2335        let mut parser = Parser::new("Order@$o".to_string()).unwrap();
2336        let ast = parser.parse().unwrap();
2337        match ast {
2338            AstNode::Binary {
2339                op: BinaryOp::FocusBind,
2340                lhs,
2341                rhs,
2342            } => {
2343                // Order is parsed as a Path with a Name step
2344                if let AstNode::Path { steps } = *lhs {
2345                    assert_eq!(steps.len(), 1);
2346                    assert!(matches!(steps[0].node, AstNode::Name(ref n) if n == "Order"));
2347                } else {
2348                    panic!("expected lhs to be Path, got {:?}", lhs);
2349                }
2350                assert!(matches!(*rhs, AstNode::Variable(ref n) if n == "o"));
2351            }
2352            other => panic!("expected Binary{{FocusBind}}, got {:?}", other),
2353        }
2354    }
2355
2356    #[test]
2357    fn test_index_bind_parses_as_binary_marker() {
2358        // #$var now reuses the same generic Binary marker shape as @$var
2359        // (Task 4 retires the dedicated AstNode::IndexBind struct variant).
2360        let mut parser = Parser::new("arr#$i".to_string()).unwrap();
2361        let ast = parser.parse().unwrap();
2362        match ast {
2363            AstNode::Binary {
2364                op: BinaryOp::IndexBind,
2365                lhs,
2366                rhs,
2367            } => {
2368                if let AstNode::Path { steps } = *lhs {
2369                    assert_eq!(steps.len(), 1);
2370                    assert!(matches!(steps[0].node, AstNode::Name(ref n) if n == "arr"));
2371                } else {
2372                    panic!("expected lhs to be Path, got {:?}", lhs);
2373                }
2374                assert!(matches!(*rhs, AstNode::Variable(ref n) if n == "i"));
2375            }
2376            other => panic!("expected Binary{{IndexBind}}, got {:?}", other),
2377        }
2378    }
2379
2380    #[test]
2381    fn test_focus_bind_requires_variable_rhs() {
2382        // S0214: @'s RHS must be a bare variable reference
2383        let err = parse("Order@foo").unwrap_err();
2384        assert!(err.to_string().starts_with("S0214"));
2385    }
2386}