Skip to main content

helios_fhirpath/
parser.rs

1//! # FHIRPath Expression Parser
2//!
3//! This module provides a complete parser for the FHIRPath expression language,
4//! implementing the official FHIRPath grammar specification. It transforms FHIRPath
5//! text into an Abstract Syntax Tree (AST) that can be evaluated against FHIR resources.
6//!
7//! ## Overview
8//!
9//! The parser handles all FHIRPath syntax elements including:
10//!
11//! - **Literals**: Numbers, strings, booleans, dates, times, quantities
12//! - **Path navigation**: Member access and nested paths (e.g., `Patient.name.given`)
13//! - **Function calls**: Both built-in and user-defined functions
14//! - **Operators**: Arithmetic, logical, comparison, and type operators
15//! - **Collections**: Collection literals and operations
16//! - **Comments**: Single-line (`//`) and multi-line (`/* */`) comments
17//!
18//! ## Key Types
19//!
20//! - [`Expression`]: The main AST node type representing any FHIRPath expression
21//! - [`Literal`]: Represents literal values (strings, numbers, dates, etc.)
22//! - [`Term`]: Basic terms (literals, invocations, variables)
23//! - [`Invocation`]: Function calls and member access
24//! - [`TypeSpecifier`]: Type names for `is` and `as` operations
25//!
26//! ## Parser Function
27//!
28//! The main entry point is [`parser()`], which returns a parser that can process
29//! FHIRPath text and produce an [`Expression`] AST.
30//!
31//! ## Examples
32//!
33//! ```rust
34//! use helios_fhirpath::parser::parser;
35//! use chumsky::Parser;
36//!
37//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
38//! // Parse a simple path expression
39//! let expr = parser().parse("Patient.name.given").unwrap();
40//!
41//! // Parse a function call with filter
42//! let expr = parser().parse("Patient.name.where(use = 'official')").unwrap();
43//!
44//! // Parse arithmetic expression
45//! let expr = parser().parse("(value + 10) * 2").unwrap();
46//!
47//! // Parse with literal values
48//! let expr = parser().parse("birthDate >= @1990-01-01").unwrap();
49//! # Ok(())
50//! # }
51//! ```
52//!
53//! ## Grammar Implementation
54//!
55//! The parser implements the FHIRPath grammar with proper operator precedence:
56//!
57//! 1. **Literals and Terms**: Numbers, strings, identifiers, parenthesized expressions
58//! 2. **Postfix Operators**: Member access (`.`), indexing (`[]`)
59//! 3. **Prefix Operators**: Unary plus/minus (`+`, `-`)
60//! 4. **Multiplicative**: `*`, `/`, `div`, `mod`
61//! 5. **Additive**: `+`, `-`, `&` (string concatenation)
62//! 6. **Type Operations**: `is`, `as`
63//! 7. **Union**: `|` (collection union)
64//! 8. **Inequality**: `<`, `<=`, `>`, `>=`
65//! 9. **Equality**: `=`, `~`, `!=`, `!~`
66//! 10. **Membership**: `in`, `contains`
67//! 11. **Logical AND**: `and`
68//! 12. **Logical OR/XOR**: `or`, `xor`
69//! 13. **Implies**: `implies`
70//!
71//! ## Literal Syntax
72//!
73//! The parser supports rich literal syntax:
74//!
75//! - **Strings**: `'text with \'escapes\''`
76//! - **Numbers**: `42` (integer), `3.14` (decimal)
77//! - **Booleans**: `true`, `false`
78//! - **Dates**: `@2024-01-15`, `@2024-01`, `@2024`
79//! - **DateTimes**: `@2024-01-15T14:30:00Z`, `@2024-01-15T14:30:00-05:00`
80//! - **Times**: `@T14:30:00`, `@T14:30:00.123`
81//! - **Quantities**: `5 'mg'`, `10.5 'cm'`, `3 days`
82//! - **Empty**: `{}`
83//!
84//! ## Comments
85//!
86//! The parser supports comments that are ignored during parsing:
87//!
88//! ```fhirpath
89//! Patient.name.given  // Get first name
90//! /* Multi-line comment
91//!    spanning multiple lines */
92//! ```
93//!
94//! ## Error Handling
95//!
96//! Parse errors include detailed information about the location and nature of
97//! syntax errors, making it easy to identify and fix issues in FHIRPath expressions.
98
99use chumsky::Parser;
100use chumsky::error::Rich;
101use chumsky::prelude::*;
102use rust_decimal::Decimal;
103use std::fmt;
104use std::str::FromStr;
105
106/// Represents a literal value in FHIRPath
107///
108/// This enum represents all the different types of literal values that can appear
109/// in a FHIRPath expression, including:
110/// - Empty value (`{}`)
111/// - Boolean values (true/false)
112/// - String literals (e.g., 'text')
113/// - Numeric values (integers and decimals)
114/// - Date/time literals (date, datetime, time)
115/// - Quantity values (numeric values with units)
116///
117/// These literals are used in the abstract syntax tree (AST) produced by the parser
118/// and are later evaluated into concrete values during expression evaluation.
119#[derive(Debug, Clone, PartialEq)]
120pub enum Literal {
121    /// The empty value, represented as `{}` in FHIRPath
122    Null,
123    /// Boolean true/false values
124    Boolean(bool),
125    /// String literals enclosed in single quotes
126    String(String),
127    /// Decimal numbers (with a decimal point)
128    Number(Decimal),
129    /// Integer numbers (without a decimal point)
130    Integer(i64),
131    /// Date literals, starting with @, such as @2022-01-01
132    Date(helios_fhir::PrecisionDate),
133    /// DateTime literals with optional time and timezone parts
134    DateTime(helios_fhir::PrecisionDateTime),
135    /// Time literals, starting with @T, such as @T12:00:00
136    Time(helios_fhir::PrecisionTime),
137    /// Quantity values with a numeric value and a unit, such as 5 'mg'
138    Quantity(Decimal, String),
139}
140
141/// Represents a FHIRPath expression
142///
143/// This enum represents the different kinds of expressions that can appear
144/// in a FHIRPath expression tree. It forms the core of the abstract syntax tree (AST)
145/// produced by the parser. Each variant corresponds to a different type of expression
146/// in the FHIRPath language, including basic terms, operators, and function invocations.
147///
148/// The Expression tree is built during parsing and later evaluated by the evaluator
149/// to produce a result value. The structure preserves operator precedence and
150/// expression nesting as specified in the FHIRPath grammar.
151#[derive(Debug, Clone, PartialEq)]
152pub enum Expression {
153    /// A basic term (literal, invocation, etc.)
154    Term(Term),
155
156    /// A method or function invocation on an expression
157    /// (e.g., `Patient.name.given.first()`)
158    Invocation(Box<Expression>, Invocation),
159
160    /// An indexer expression (e.g., `Patient.name[0]`)
161    Indexer(Box<Expression>, Box<Expression>),
162
163    /// A unary polarity expression (+ or -)
164    /// (e.g., `-5` or `+value`)
165    Polarity(char, Box<Expression>),
166
167    /// A multiplicative expression (*, /, div, mod)
168    /// (e.g., `value * 2` or `amount div 10`)
169    Multiplicative(Box<Expression>, String, Box<Expression>),
170
171    /// An additive expression (+ or -)
172    /// (e.g., `value + 5` or `total - tax`)
173    Additive(Box<Expression>, String, Box<Expression>),
174
175    /// A type operation (is, as)
176    /// (e.g., `value is Integer` or `patient as Patient`)
177    Type(Box<Expression>, String, TypeSpecifier),
178
179    /// A union operation (|)
180    /// (e.g., `Patient.name | Patient.address`)
181    Union(Box<Expression>, Box<Expression>),
182
183    /// An inequality comparison (<, <=, >, >=)
184    /// (e.g., `value > 5` or `date <= today()`)
185    Inequality(Box<Expression>, String, Box<Expression>),
186
187    /// An equality comparison (=, !=, ~, !~)
188    /// (e.g., `name = 'John'` or `birthDate ~ @2020`)
189    Equality(Box<Expression>, String, Box<Expression>),
190
191    /// A membership test (in, contains)
192    /// (e.g., `'John' in Patient.name.given` or `Patient.name contains 'John'`)
193    Membership(Box<Expression>, String, Box<Expression>),
194
195    /// A logical AND operation
196    /// (e.g., `value > 5 and value < 10`)
197    And(Box<Expression>, Box<Expression>),
198
199    /// A logical OR or XOR operation
200    /// (e.g., `status = 'active' or status = 'pending'`)
201    Or(Box<Expression>, String, Box<Expression>),
202
203    /// A logical IMPLIES operation
204    /// (e.g., `exists() implies value > 0`)
205    Implies(Box<Expression>, Box<Expression>),
206
207    /// A lambda expression with optional identifier
208    /// (e.g., `item => item.value > 10`)
209    Lambda(Option<String>, Box<Expression>),
210
211    /// An instance selector expression
212    /// (e.g., `Quantity { value: 5, unit: 'mg' }`)
213    /// Contains the type name and a list of field name/expression pairs.
214    InstanceSelector(String, Vec<(String, Box<Expression>)>),
215}
216
217/// Represents a type specifier in FHIRPath
218///
219/// This enum is used to represent types in type operations like 'is' and 'as'.
220/// It supports both simple types and namespace-qualified types as defined in the
221/// FHIRPath specification.
222///
223/// Type specifiers are used in expressions like:
224/// - `value is Integer`
225/// - `patient is FHIR.Patient`
226/// - `value as System.Decimal`
227///
228/// The parser determines whether an identifier is a simple type name or a
229/// namespace-qualified type name based on the presence of a dot separator.
230#[derive(Debug, Clone, PartialEq)]
231pub enum TypeSpecifier {
232    /// A qualified identifier representing a type, possibly with a namespace
233    ///
234    /// The first String is either:
235    /// - The namespace (when `Option<String>` is Some), or
236    /// - The type name (when `Option<String>` is None)
237    ///
238    /// The `Option<String>` is:
239    /// - Some(type_name) when a namespace is provided, or
240    /// - None when it's a simple type without a namespace
241    ///
242    /// Examples:
243    /// - FHIR.Patient -> QualifiedIdentifier("FHIR", Some("Patient"))
244    /// - Boolean -> QualifiedIdentifier("Boolean", None)
245    /// - System.Boolean -> QualifiedIdentifier("System", Some("Boolean"))
246    QualifiedIdentifier(String, Option<String>),
247}
248
249/// Represents a basic term in a FHIRPath expression
250///
251/// A term is the most fundamental unit in a FHIRPath expression.
252/// It can be a literal value, an invocation, a variable reference,
253/// or a parenthesized expression. Terms are the leaves of the expression
254/// tree in the abstract syntax tree (AST).
255///
256/// Terms can appear alone or as part of more complex expressions,
257/// and they are the starting point for expression evaluation.
258#[derive(Debug, Clone, PartialEq)]
259pub enum Term {
260    /// An invocation, such as a member access, function call, or special identifier
261    /// (e.g., `name`, `first()`, `$this`)
262    Invocation(Invocation),
263
264    /// A literal value like a number, string, boolean, or date
265    /// (e.g., `42`, `'text'`, `true`, `@2022-01-01`)
266    Literal(Literal),
267
268    /// An external constant or environment variable reference
269    /// (e.g., `%context`, `%ucum`, `%terminologies`)
270    ExternalConstant(String),
271
272    /// A parenthesized expression
273    /// (e.g., `(1 + 2)`, `(Patient.name)`)
274    Parenthesized(Box<Expression>),
275}
276
277/// Represents an invocation in a FHIRPath expression
278///
279/// An invocation represents different ways to reference or call something in FHIRPath.
280/// This includes member access, function calls, and special contextual identifiers
281/// like $this, $index, and $total.
282///
283/// Invocations are fundamental building blocks in FHIRPath expressions and
284/// are used for navigation, function application, and context references.
285#[derive(Debug, Clone, PartialEq)]
286pub enum Invocation {
287    /// A member access, referencing a property by name
288    /// (e.g., `Patient.name`, `Observation.value`)
289    Member(String),
290
291    /// A function call with optional arguments
292    /// (e.g., `first()`, `where(value > 5)`, `substring(2, 5)`)
293    Function(String, Vec<Expression>),
294
295    /// A reference to the current focus item ($this)
296    /// Used in expressions like `$this.name` or in lambda expressions
297    This,
298
299    /// A reference to the current index ($index)
300    /// Used in expressions like `$index > 5` in filtering operations
301    Index,
302
303    /// A reference to the current aggregate total ($total)
304    /// Used in the aggregate() function to access the running total
305    Total,
306}
307
308// Removed Unit, DateTimePrecision, PluralDateTimePrecision enums
309
310/// Span information: (start_position, length) in the source text
311#[derive(Debug, Clone, PartialEq)]
312pub struct ExprSpan {
313    pub position: usize,
314    pub length: usize,
315}
316
317/// An Expression node with span information attached
318#[derive(Debug, Clone)]
319pub struct SpannedExpression {
320    pub kind: SpannedExprKind,
321    pub span: ExprSpan,
322}
323
324/// Mirrors Expression variants but children are Box<SpannedExpression>
325#[derive(Debug, Clone)]
326pub enum SpannedExprKind {
327    Term(SpannedTerm),
328    Invocation(Box<SpannedExpression>, SpannedInvocation),
329    Indexer(Box<SpannedExpression>, Box<SpannedExpression>),
330    Polarity(char, Box<SpannedExpression>),
331    Multiplicative(Box<SpannedExpression>, String, Box<SpannedExpression>),
332    Additive(Box<SpannedExpression>, String, Box<SpannedExpression>),
333    Type(Box<SpannedExpression>, String, TypeSpecifier),
334    Union(Box<SpannedExpression>, Box<SpannedExpression>),
335    Inequality(Box<SpannedExpression>, String, Box<SpannedExpression>),
336    Equality(Box<SpannedExpression>, String, Box<SpannedExpression>),
337    Membership(Box<SpannedExpression>, String, Box<SpannedExpression>),
338    And(Box<SpannedExpression>, Box<SpannedExpression>),
339    Or(Box<SpannedExpression>, String, Box<SpannedExpression>),
340    Implies(Box<SpannedExpression>, Box<SpannedExpression>),
341    Lambda(Option<String>, Box<SpannedExpression>),
342    InstanceSelector(String, Vec<(String, Box<SpannedExpression>)>),
343}
344
345#[derive(Debug, Clone)]
346pub enum SpannedTerm {
347    Literal(Literal),
348    Invocation(SpannedInvocation),
349    ExternalConstant(String),
350    Parenthesized(Box<SpannedExpression>),
351}
352
353#[derive(Debug, Clone)]
354pub enum SpannedInvocation {
355    Member(String),
356    Function(String, Vec<SpannedExpression>),
357    This,
358    Index,
359    Total,
360}
361
362impl SpannedExpression {
363    pub fn to_expression(&self) -> Expression {
364        match &self.kind {
365            SpannedExprKind::Term(t) => Expression::Term(t.to_term()),
366            SpannedExprKind::Invocation(base, inv) => {
367                Expression::Invocation(Box::new(base.to_expression()), inv.to_invocation())
368            }
369            SpannedExprKind::Indexer(expr, idx) => Expression::Indexer(
370                Box::new(expr.to_expression()),
371                Box::new(idx.to_expression()),
372            ),
373            SpannedExprKind::Polarity(op, expr) => {
374                Expression::Polarity(*op, Box::new(expr.to_expression()))
375            }
376            SpannedExprKind::Multiplicative(l, op, r) => Expression::Multiplicative(
377                Box::new(l.to_expression()),
378                op.clone(),
379                Box::new(r.to_expression()),
380            ),
381            SpannedExprKind::Additive(l, op, r) => Expression::Additive(
382                Box::new(l.to_expression()),
383                op.clone(),
384                Box::new(r.to_expression()),
385            ),
386            SpannedExprKind::Type(expr, op, ts) => {
387                Expression::Type(Box::new(expr.to_expression()), op.clone(), ts.clone())
388            }
389            SpannedExprKind::Union(l, r) => {
390                Expression::Union(Box::new(l.to_expression()), Box::new(r.to_expression()))
391            }
392            SpannedExprKind::Inequality(l, op, r) => Expression::Inequality(
393                Box::new(l.to_expression()),
394                op.clone(),
395                Box::new(r.to_expression()),
396            ),
397            SpannedExprKind::Equality(l, op, r) => Expression::Equality(
398                Box::new(l.to_expression()),
399                op.clone(),
400                Box::new(r.to_expression()),
401            ),
402            SpannedExprKind::Membership(l, op, r) => Expression::Membership(
403                Box::new(l.to_expression()),
404                op.clone(),
405                Box::new(r.to_expression()),
406            ),
407            SpannedExprKind::And(l, r) => {
408                Expression::And(Box::new(l.to_expression()), Box::new(r.to_expression()))
409            }
410            SpannedExprKind::Or(l, op, r) => Expression::Or(
411                Box::new(l.to_expression()),
412                op.clone(),
413                Box::new(r.to_expression()),
414            ),
415            SpannedExprKind::Implies(l, r) => {
416                Expression::Implies(Box::new(l.to_expression()), Box::new(r.to_expression()))
417            }
418            SpannedExprKind::Lambda(param, expr) => {
419                Expression::Lambda(param.clone(), Box::new(expr.to_expression()))
420            }
421            SpannedExprKind::InstanceSelector(type_name, fields) => Expression::InstanceSelector(
422                type_name.clone(),
423                fields
424                    .iter()
425                    .map(|(name, expr)| (name.clone(), Box::new(expr.to_expression())))
426                    .collect(),
427            ),
428        }
429    }
430}
431
432impl SpannedTerm {
433    pub fn to_term(&self) -> Term {
434        match self {
435            SpannedTerm::Literal(l) => Term::Literal(l.clone()),
436            SpannedTerm::Invocation(i) => Term::Invocation(i.to_invocation()),
437            SpannedTerm::ExternalConstant(s) => Term::ExternalConstant(s.clone()),
438            SpannedTerm::Parenthesized(e) => Term::Parenthesized(Box::new(e.to_expression())),
439        }
440    }
441}
442
443impl SpannedInvocation {
444    pub fn to_invocation(&self) -> Invocation {
445        match self {
446            SpannedInvocation::Member(s) => Invocation::Member(s.clone()),
447            SpannedInvocation::Function(name, args) => Invocation::Function(
448                name.clone(),
449                args.iter().map(|a| a.to_expression()).collect(),
450            ),
451            SpannedInvocation::This => Invocation::This,
452            SpannedInvocation::Index => Invocation::Index,
453            SpannedInvocation::Total => Invocation::Total,
454        }
455    }
456}
457
458impl fmt::Display for Literal {
459    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
460        match self {
461            Literal::Null => write!(f, "{{}}"),
462            Literal::Boolean(b) => write!(f, "{}", b),
463            Literal::String(s) => write!(f, "'{}'", s),
464            Literal::Number(d) => write!(f, "{}", d), // Use Decimal's Display
465            Literal::Integer(n) => write!(f, "{}", n),
466            Literal::Date(d) => write!(f, "@{}", d.original_string()),
467            Literal::DateTime(dt) => write!(f, "@{}", dt.original_string()),
468            Literal::Time(t) => write!(f, "@T{}", t.original_string()),
469            Literal::Quantity(d, u) => write!(f, "{} '{}'", d, u), // Use Decimal's Display and unit string
470        }
471    }
472}
473
474/// Helper function to remove backticks from identifiers if present
475fn clean_backtick_identifier(id: &str) -> String {
476    if id.starts_with('`') && id.ends_with('`') && id.len() >= 3 {
477        id[1..id.len() - 1].to_string()
478    } else {
479        id.to_string()
480    }
481}
482
483/// Combines a sequence of code units (from literal scalars and `\uXXXX` escapes)
484/// into a FHIRPath string. Adjacent high/low UTF-16 surrogate escapes are joined
485/// into a single Unicode scalar value; unpaired surrogates are rejected. This
486/// matches the FHIRPath string-literal rules (FHIR-53554).
487fn combine_string_code_units(codes: Vec<u32>) -> Result<String, &'static str> {
488    let mut out = String::with_capacity(codes.len());
489    let mut i = 0;
490    while i < codes.len() {
491        let c = codes[i];
492        if (0xD800..=0xDBFF).contains(&c) {
493            if let Some(&lo) = codes.get(i + 1) {
494                if (0xDC00..=0xDFFF).contains(&lo) {
495                    let scalar = 0x10000 + ((c - 0xD800) << 10) + (lo - 0xDC00);
496                    match char::from_u32(scalar) {
497                        Some(ch) => {
498                            out.push(ch);
499                            i += 2;
500                            continue;
501                        }
502                        None => return Err("Invalid surrogate pair"),
503                    }
504                }
505            }
506            return Err("Unpaired high surrogate in \\uXXXX escape");
507        }
508        if (0xDC00..=0xDFFF).contains(&c) {
509            return Err("Unpaired low surrogate in \\uXXXX escape");
510        }
511        match char::from_u32(c) {
512            Some(ch) => out.push(ch),
513            None => return Err("Invalid Unicode code point"),
514        }
515        i += 1;
516    }
517    Ok(out)
518}
519
520/// Creates a parser for FHIRPath expressions
521///
522/// This function creates and returns a parser that can parse FHIRPath expressions
523/// according to the official FHIRPath grammar specification. The parser uses the
524/// chumsky parsing library to implement a recursive descent parser with proper
525/// handling of operator precedence and associativity.
526///
527/// The parser handles all FHIRPath syntax elements including:
528/// - Literals (numbers, strings, dates, times, etc.)
529/// - Path navigation and member access
530/// - Function invocation
531/// - Mathematical operations
532/// - Logical operations
533/// - Comparison and equality tests
534/// - Collection operators
535/// - Type testing operations
536///
537/// # Returns
538///
539/// A parser that can consume a string of characters and produce an Expression
540/// representing the abstract syntax tree (AST) of the parsed FHIRPath expression.
541///
542/// # Errors
543///
544/// The parser returns detailed error information when it encounters syntax errors
545/// in the input, including the location and nature of the error.
546/// Parser that matches a custom whitespace including comments  
547fn custom_padded<'src, T, P>(
548    parser: P,
549) -> impl Parser<'src, &'src str, T, extra::Err<Rich<'src, char>>> + Clone
550where
551    P: Parser<'src, &'src str, T, extra::Err<Rich<'src, char>>> + Clone,
552    T: Clone,
553{
554    // First consume any leading whitespace/comments
555    let ws_or_comment = choice((
556        // Regular whitespace
557        text::whitespace().at_least(1).ignored(),
558        // Single-line comment: // ... newline or EOF
559        just("//")
560            .then(any().and_is(text::newline().or(end()).not()).repeated())
561            .ignored(),
562        // Multi-line comment: /* ... */
563        just("/*")
564            .then(any().and_is(just("*/").not()).repeated())
565            .then(just("*/"))
566            .ignored(),
567    ))
568    .repeated()
569    .ignored();
570
571    ws_or_comment
572        .then(parser)
573        .map(|(_, result)| result)
574        .then_ignore(ws_or_comment)
575}
576
577pub fn parser<'src>()
578-> impl Parser<'src, &'src str, Expression, extra::Err<Rich<'src, char>>> + Clone + 'src {
579    // Parser for escape sequences within string literals. Produces a raw
580    // code unit (u32) so that `\uXXXX` UTF-16 surrogate escapes can be paired
581    // into a single scalar value by `combine_string_code_units`.
582    let esc = just('\\').ignore_then(choice((
583        just('`').to('`' as u32),
584        just('\'').to('\'' as u32),
585        just('\\').to('\\' as u32),
586        just('/').to('/' as u32),
587        just('f').to(0x000C_u32),
588        just('n').to('\n' as u32),
589        just('r').to('\r' as u32),
590        just('t').to('\t' as u32),
591        just('"').to('"' as u32),
592        // Unicode escape sequence: \uXXXX. May be a surrogate half that will
593        // be paired with a following escape in `combine_string_code_units`.
594        just('u').ignore_then(
595            any()
596                .filter(|c: &char| c.is_ascii_hexdigit())
597                .repeated()
598                .exactly(4)
599                .collect::<String>()
600                .try_map(
601                    |digits: String, span| match u32::from_str_radix(&digits, 16) {
602                        Ok(code) => Ok(code),
603                        Err(_) => Err(Rich::custom(span, "Invalid hex digits")),
604                    },
605                ),
606        ),
607    )));
608
609    // Helper macro to make a parser skip whitespace and comments
610    macro_rules! padded {
611        ($p:expr) => {
612            custom_padded($p)
613        };
614    }
615
616    // LITERAL PARSERS
617
618    // Parser for null/empty literals: {}
619    // In FHIRPath, the empty collection is represented as {}
620    let null = just('{').then(just('}')).to(Literal::Null);
621
622    // Parser for boolean literals: true, false
623    // Note: These need to be parsed before identifiers to avoid ambiguity
624    let boolean = choice((
625        text::keyword("true").to(Literal::Boolean(true)),
626        text::keyword("false").to(Literal::Boolean(false)),
627    ))
628    .boxed();
629
630    // Parser for string literals: 'text'
631    // Handles escape sequences and allows any characters between single quotes
632    let string = just('\'')
633        .ignore_then(
634            none_of("\\\'")
635                .map(|c: char| c as u32)
636                .or(esc)
637                .repeated()
638                .collect::<Vec<u32>>(),
639        )
640        .then_ignore(just('\''))
641        .try_map(|codes, span| combine_string_code_units(codes).map_err(|e| Rich::custom(span, e)))
642        .map(Literal::String)
643        .boxed();
644
645    // Parser for integer literals
646    //
647    // Parses sequences of digits without a decimal point into an i64 value.
648    // The FHIRPath specification defines integers as 64-bit signed values.
649    // This parser validates that the integer is within the valid range.
650    let integer = any()
651        .filter(|c: &char| c.is_ascii_digit())
652        .repeated()
653        .at_least(1) // Require at least one digit
654        .collect::<String>()
655        .try_map(|digits: String, span| match i64::from_str(&digits) {
656            Ok(n) => Ok(Literal::Integer(n)),
657            Err(_) => Err(Rich::custom(span, format!("Invalid integer: {}", digits))),
658        });
659    let integer = padded!(integer); // Allow whitespace around integers
660
661    // Parser for decimal number literals
662    //
663    // Parses numbers with a decimal point into a Decimal value.
664    // The FHIRPath specification uses arbitrary precision decimal values,
665    // represented here using the rust_decimal crate's Decimal type.
666    //
667    // Format: <digits>.<digits>
668    // Example: 3.14159
669    let number = any()
670        .filter(|c: &char| c.is_ascii_digit())
671        .repeated()
672        .at_least(1) // Require at least one digit before the decimal
673        .collect::<String>()
674        .then(just('.')) // Require the decimal point
675        .then(
676            any()
677                .filter(|c: &char| c.is_ascii_digit())
678                .repeated()
679                .at_least(1) // Require at least one digit after the decimal
680                .collect::<String>(),
681        )
682        .try_map(|((i, _), d), span| {
683            let num_str = format!("{}.{}", i, d);
684            match Decimal::from_str(&num_str) {
685                Ok(decimal) => Ok(Literal::Number(decimal)),
686                Err(_) => Err(Rich::custom(span, format!("Invalid number: {}", num_str))),
687            }
688        })
689        .padded(); // Allow whitespace around numbers
690
691    // Parser for time format components
692    //
693    // Handles the FHIRPath time format: HH(:mm(:ss(.sss)?)?)?
694    // This can be as simple as just hours (HH) or include minutes,
695    // seconds, and milliseconds with the appropriate separators.
696    //
697    // Examples:
698    // - 12 (just hours)
699    // - 14:30 (hours and minutes)
700    // - 09:45:30 (hours, minutes, seconds)
701    // - 23:59:59.999 (hours, minutes, seconds, and milliseconds)
702    let time_format = any()
703        .filter(|c: &char| c.is_ascii_digit())
704        .repeated()
705        .at_least(2) // Hours: exactly 2 digits
706        .at_most(2)
707        .collect::<String>()
708        .then(
709            just(':') // Optional minutes part
710                .ignore_then(
711                    any()
712                        .filter(|c: &char| c.is_ascii_digit())
713                        .repeated()
714                        .at_least(2) // Minutes: exactly 2 digits
715                        .at_most(2)
716                        .collect::<String>(),
717                )
718                .then(
719                    just(':') // Optional seconds part
720                        .ignore_then(
721                            any()
722                                .filter(|c: &char| c.is_ascii_digit())
723                                .repeated()
724                                .at_least(2) // Seconds: exactly 2 digits
725                                .at_most(2)
726                                .collect::<String>(),
727                        )
728                        .then(
729                            just('.') // Optional milliseconds part
730                                .ignore_then(
731                                    any()
732                                        .filter(|c: &char| c.is_ascii_digit())
733                                        .repeated()
734                                        .at_least(1) // Milliseconds: 1-3 digits
735                                        .at_most(3)
736                                        .collect::<String>(),
737                                )
738                                .or_not(),
739                        )
740                        .or_not(),
741                )
742                .or_not(),
743        )
744        .map(|(hours, rest_opt)| {
745            // Combine all the parts into a single time string
746            let mut result = hours;
747            if let Some((minutes, seconds_part)) = rest_opt {
748                result.push(':');
749                result.push_str(&minutes);
750
751                if let Some((seconds, milliseconds)) = seconds_part {
752                    result.push(':');
753                    result.push_str(&seconds);
754
755                    // milliseconds is an Option<String>
756                    if let Some(ms) = milliseconds {
757                        result.push('.');
758                        result.push_str(&ms);
759                    }
760                }
761            }
762            result
763        });
764
765    // Parser for timezone format
766    //
767    // Handles the two timezone formats defined in FHIRPath:
768    // - 'Z' for UTC/Zulu time
769    // - (+|-)HH:mm for timezone offset (e.g., +01:00, -05:30)
770    //
771    // This parser validates the format and produces a string
772    // representation of the timezone.
773    let timezone_format = just('Z')
774        .to("Z".to_string()) // UTC/Zulu time
775        .or(one_of("+-") // Or timezone offset
776            .map(|c: char| c.to_string()) // Get sign as string
777            .then(
778                any()
779                    .filter(|c: &char| c.is_ascii_digit())
780                    .repeated()
781                    .at_most(2) // Hours: exactly 2 digits
782                    .at_least(2)
783                    .collect::<String>(),
784            )
785            .then(just(':')) // Colon separator
786            .then(
787                any()
788                    .filter(|c: &char| c.is_ascii_digit())
789                    .repeated()
790                    .at_most(2) // Minutes: exactly 2 digits
791                    .at_least(2)
792                    .collect::<String>(),
793            )
794            .map(|(((sign, hour), _), min)| format!("{}{}:{}", sign, hour, min)));
795
796    // Parser for date format
797    //
798    // Handles the FHIRPath date format: YYYY(-MM(-DD)?)?
799    // This parser supports multiple date precisions:
800    // - Year only: YYYY (e.g., 2022)
801    // - Year and month: YYYY-MM (e.g., 2022-01)
802    // - Full date: YYYY-MM-DD (e.g., 2022-01-15)
803    //
804    // The parser validates the format and produces a string representation
805    // of the date for use in Date literals.
806    //
807    // Examples:
808    //
809    // - 1972 (year only)
810    // - 2015-12 (year and month)
811    // - 1972-12-14 (full date)
812    let date_format_str = any()
813        .filter(|c: &char| c.is_ascii_digit())
814        .repeated()
815        .exactly(4) // Year: exactly 4 digits
816        .collect::<String>()
817        .then(
818            just('-') // Optional month part
819                .ignore_then(
820                    any()
821                        .filter(|c: &char| c.is_ascii_digit())
822                        .repeated()
823                        .exactly(2) // Month: exactly 2 digits
824                        .collect::<String>()
825                        .then(
826                            just('-') // Optional day part
827                                .ignore_then(
828                                    any()
829                                        .filter(|c: &char| c.is_ascii_digit())
830                                        .repeated()
831                                        .exactly(2) // Day: exactly 2 digits
832                                        .collect::<String>(),
833                                )
834                                .or_not(),
835                        ),
836                )
837                .or_not(),
838        )
839        .map(|(year, month_part)| {
840            // Combine all the parts into a single date string
841            let mut date_str = year;
842
843            // month_part is Option<(month_str, Option<day_str>)>
844            if let Some((month_str, day_part)) = month_part {
845                date_str.push('-');
846                date_str.push_str(&month_str);
847
848                // day_part is Option<day_str>
849                if let Some(day_str) = day_part {
850                    date_str.push('-');
851                    date_str.push_str(&day_str);
852                }
853            }
854
855            date_str // Returns String
856        })
857        .boxed();
858
859    // Parser for unit values in quantity literals
860    //
861    // Units in FHIRPath can be specified either as predefined time unit keywords
862    // or as arbitrary string literals enclosed in single quotes.
863    //
864    // This parser handles both forms:
865    // - Time unit keywords (year, month, day, hour, minute, second, etc.)
866    // - String literal units ('mg', 'kg', 'cm', etc.)
867    //
868    // The parser returns the unit as a String regardless of which form was used.
869
870    // Parser for time unit keywords
871    // These are the predefined time unit keywords in FHIRPath
872    let unit_keyword = choice((
873        // Singular forms
874        text::keyword("year").to("year".to_string()),
875        text::keyword("month").to("month".to_string()),
876        text::keyword("week").to("week".to_string()),
877        text::keyword("day").to("day".to_string()),
878        text::keyword("hour").to("hour".to_string()),
879        text::keyword("minute").to("minute".to_string()),
880        text::keyword("second").to("second".to_string()),
881        text::keyword("millisecond").to("millisecond".to_string()),
882        // Plural forms
883        text::keyword("years").to("years".to_string()),
884        text::keyword("months").to("months".to_string()),
885        text::keyword("weeks").to("weeks".to_string()),
886        text::keyword("days").to("days".to_string()),
887        text::keyword("hours").to("hours".to_string()),
888        text::keyword("minutes").to("minutes".to_string()),
889        text::keyword("seconds").to("seconds".to_string()),
890        text::keyword("milliseconds").to("milliseconds".to_string()),
891    ));
892
893    // Parser for string literal units
894    // These are arbitrary units enclosed in single quotes
895    let unit_string_literal = just('\'')
896        .ignore_then(
897            none_of("\\\'")
898                .map(|c: char| c as u32)
899                .or(esc)
900                .repeated()
901                .collect::<Vec<u32>>(),
902        )
903        .then_ignore(just('\''))
904        .try_map(|codes, span| combine_string_code_units(codes).map_err(|e| Rich::custom(span, e)));
905
906    // Combined parser for all unit forms
907    let unit = choice((
908        unit_keyword,        // Time unit keywords
909        unit_string_literal, // String literal units
910    ))
911    .boxed() // Box for recursive definitions
912    .padded(); // Allow whitespace around units
913
914    // Define integer/number parsers specifically for quantity, without consuming trailing whitespace.
915    let integer_for_quantity = any()
916        .filter(|c: &char| c.is_ascii_digit())
917        .repeated()
918        .at_least(1)
919        .collect::<String>()
920        .try_map(|digits: String, span| match i64::from_str(&digits) {
921            Ok(n) => Ok(n), // Return the i64 directly
922            Err(_) => Err(Rich::custom(span, format!("Invalid integer: {}", digits))),
923        });
924
925    let number_for_quantity = any()
926        .filter(|c: &char| c.is_ascii_digit())
927        .repeated()
928        .at_least(1)
929        .collect::<String>()
930        .then(just('.'))
931        .then(
932            any()
933                .filter(|c: &char| c.is_ascii_digit())
934                .repeated()
935                .at_least(1)
936                .collect::<String>(),
937        )
938        .try_map(|((i, _), d), span| {
939            let num_str = format!("{}.{}", i, d);
940            match Decimal::from_str(&num_str) {
941                Ok(decimal) => Ok(decimal), // Return the Decimal directly
942                Err(_) => Err(Rich::custom(span, format!("Invalid number: {}", num_str))),
943            }
944        });
945
946    // Quantity parser: (integer_for_quantity | number_for_quantity) + required whitespace + unit
947    // This parser consumes the whole quantity structure.
948    let quantity = choice((
949        // Try integer quantity first
950        integer_for_quantity
951            .then_ignore(text::whitespace().at_least(1)) // Require whitespace
952            .then(unit.clone()) // Parse the unit string
953            .map(|(i, u_str)| Literal::Quantity(Decimal::from(i), u_str)), // Create Literal::Quantity with Decimal and String unit
954        // Then try decimal quantity
955        number_for_quantity
956            .then_ignore(text::whitespace().at_least(1)) // Require whitespace
957            .then(unit.clone()) // Parse the unit string
958            .map(|(d, u_str)| Literal::Quantity(d, u_str)), // Create Literal::Quantity with Decimal and String unit
959    ));
960
961    // Removed unused emit_error helper function
962
963    // Parser for DateTime: @Date T Time [Timezone]
964    let datetime_literal = just('@')
965        .ignore_then(date_format_str.clone())
966        .then_ignore(just('T'))
967        .then(time_format)
968        .then(timezone_format.clone().or_not())
969        .try_map(|((date_str, time_str), tz_opt), span| {
970            let full_str = if let Some(tz) = tz_opt {
971                format!("{}T{}{}", date_str, time_str, tz)
972            } else {
973                format!("{}T{}", date_str, time_str)
974            };
975
976            helios_fhir::PrecisionDateTime::parse(&full_str)
977                .ok_or_else(|| Rich::custom(span, format!("Invalid datetime format: {}", full_str)))
978                .map(Literal::DateTime)
979        });
980
981    // Parser for Partial DateTime: @Date T
982    let partial_datetime_literal = just('@')
983        .ignore_then(date_format_str.clone())
984        .then_ignore(just('T'))
985        .try_map(|date_str, span| {
986            let full_str = format!("{}T", date_str);
987            helios_fhir::PrecisionDateTime::parse(&full_str)
988                .ok_or_else(|| {
989                    Rich::custom(
990                        span,
991                        format!("Invalid partial datetime format: {}", full_str),
992                    )
993                })
994                .map(Literal::DateTime)
995        });
996
997    // Parser for Time: @ T Time (strictly no timezone)
998    // Uses try_map to fail parsing if a timezone is present.
999    let time_literal = just('@')
1000        .ignore_then(
1001            just('T')
1002                .ignore_then(time_format)
1003                .then(timezone_format.or_not()), // Parse time and optional timezone
1004        )
1005        .try_map(|(time_str, tz_opt), span| {
1006            // Validate that timezone is not present
1007            if tz_opt.is_some() {
1008                Err(Rich::custom(
1009                    span,
1010                    "Time literal cannot have a timezone offset",
1011                ))
1012            } else {
1013                helios_fhir::PrecisionTime::parse(&time_str)
1014                    .ok_or_else(|| Rich::custom(span, format!("Invalid time format: {}", time_str)))
1015                    .map(Literal::Time)
1016            }
1017        });
1018
1019    // Parser for Date: @ Date
1020    let date_literal = just('@')
1021        .ignore_then(date_format_str.clone())
1022        .try_map(|date_str, span| {
1023            helios_fhir::PrecisionDate::parse(&date_str)
1024                .ok_or_else(|| Rich::custom(span, format!("Invalid date format: {}", date_str)))
1025                .map(Literal::Date)
1026        });
1027
1028    // Order matters: try quantity before plain number/integer.
1029    // Specific date/time formats should be tried before more general ones if there's ambiguity,
1030    // though the new structure aims to make them distinct.
1031    let literal = choice((
1032        null,
1033        boolean,
1034        string,
1035        quantity,                          // Try quantity first
1036        number,                            // Then number (requires '.')
1037        integer,                           // Then integer
1038        padded!(datetime_literal),         // @Date T Time [TZ]
1039        padded!(partial_datetime_literal), // @Date T
1040        padded!(time_literal),             // @ T Time (will fail if TZ present)
1041        padded!(date_literal),             // @Date
1042    ))
1043    .map(Term::Literal);
1044
1045    // IDENTIFIER: ([A-Za-z] | '_')([A-Za-z0-9] | '_')*
1046    let standard_identifier = any()
1047        .filter(|c: &char| c.is_ascii_alphabetic() || *c == '_')
1048        .then(
1049            any()
1050                .filter(|c: &char| c.is_ascii_alphanumeric() || *c == '_')
1051                .repeated()
1052                .collect::<Vec<_>>(),
1053        )
1054        .map(|(first, rest): (char, Vec<char>)| {
1055            let mut s = first.to_string();
1056            s.extend(rest);
1057            s
1058        })
1059        .padded();
1060
1061    // DELIMITEDIDENTIFIER: '`' (ESC | .)*? '`'
1062    let delimited_identifier = just('`')
1063        .ignore_then(
1064            none_of("`")
1065                .map(|c: char| c as u32)
1066                .or(esc)
1067                .repeated()
1068                .collect::<Vec<u32>>(),
1069        )
1070        .then_ignore(just('`'))
1071        .try_map(|codes, span| combine_string_code_units(codes).map_err(|e| Rich::custom(span, e)))
1072        .padded();
1073
1074    // Combined identifier parser - allow true/false as identifiers
1075    // Also allow keywords used in specific contexts (like 'as', 'is') to be parsed as identifiers
1076    // when they appear where an identifier is expected (e.g., in function calls or member access).
1077    // The context of the grammar will differentiate their use.
1078    let identifier = choice((
1079        standard_identifier,
1080        delimited_identifier,
1081        // Allow keywords to be parsed as identifiers if they appear in identifier positions
1082        text::keyword("as").to(String::from("as")),
1083        text::keyword("contains").to(String::from("contains")),
1084        text::keyword("in").to(String::from("in")),
1085        text::keyword("is").to(String::from("is")),
1086        text::keyword("true").to(String::from("true")), // Allow 'true' as identifier
1087        text::keyword("false").to(String::from("false")), // Allow 'false' as identifier
1088                                                        // Add other keywords if they can appear as identifiers in some contexts
1089    ));
1090
1091    // Qualified identifier (for type specifiers)
1092    // Handles all these patterns:
1093    // - Single identifier: Boolean, Patient, etc.
1094    // - Namespace.Type: System.Boolean, FHIR.Patient
1095    // - Backtick quoted: `System`.`Boolean`, FHIR.`Patient`
1096    let qualified_identifier = {
1097        // First try to handle explicit namespace.type pattern
1098        let explicit_namespace_type = identifier
1099            .clone()
1100            .then(just('.').ignore_then(identifier.clone()))
1101            .map(|(namespace, type_name)| {
1102                // Clean both parts (removing backticks if present)
1103                let clean_ns = clean_backtick_identifier(&namespace);
1104                let clean_type = clean_backtick_identifier(&type_name);
1105                TypeSpecifier::QualifiedIdentifier(clean_ns, Some(clean_type))
1106            });
1107
1108        // Then handle standalone identifiers (which might themselves contain dots)
1109        let standalone_type = identifier.clone().map(|id| {
1110            let clean_id = clean_backtick_identifier(&id);
1111
1112            // Check if this identifier already contains dots (like "System.Boolean")
1113            if clean_id.contains('.') {
1114                // This might be a pre-qualified identifier typed directly
1115                // Split at the last dot to get namespace and type
1116                if let Some(last_dot_pos) = clean_id.rfind('.') {
1117                    let namespace = clean_id[..last_dot_pos].to_string();
1118                    let type_name = clean_id[last_dot_pos + 1..].to_string();
1119                    TypeSpecifier::QualifiedIdentifier(namespace, Some(type_name))
1120                } else {
1121                    // Shouldn't happen if contains('.') returned true, but just in case
1122                    TypeSpecifier::QualifiedIdentifier(clean_id, None)
1123                }
1124            } else {
1125                // Simple unqualified type name
1126                TypeSpecifier::QualifiedIdentifier(clean_id, None)
1127            }
1128        });
1129
1130        // Try explicit namespace.type first, then fallback to standalone identifier
1131        choice((explicit_namespace_type.boxed(), standalone_type.boxed())).boxed()
1132    };
1133    let qualified_identifier = padded!(qualified_identifier);
1134
1135    // Create a separate string parser for external constants
1136    let string_for_external = just('\'')
1137        .ignore_then(
1138            none_of("\'\\")
1139                .map(|c: char| c as u32)
1140                .or(esc)
1141                .repeated()
1142                .collect::<Vec<u32>>(),
1143        )
1144        .then_ignore(just('\''))
1145        .try_map(|codes, span| combine_string_code_units(codes).map_err(|e| Rich::custom(span, e)))
1146        .padded();
1147
1148    // External constants
1149    let external_constant = just('%')
1150        .ignore_then(choice((identifier.clone(), string_for_external)))
1151        .map(Term::ExternalConstant)
1152        .padded();
1153
1154    // Use explicit boxing to prevent infinite type recursion in chumsky 0.10
1155
1156    recursive(|expr| {
1157        // Atom: the most basic elements like literals, identifiers, parenthesized expressions.
1158        let atom = choice((
1159            // Box each branch individually to ensure type uniformity for choice
1160            literal.clone().map(Expression::Term).boxed(), // Map literal Term to Expression here
1161            external_constant.clone().map(Expression::Term).boxed(),
1162            // Function call: identifier(...) - Try this *before* simple identifier
1163            identifier
1164                .clone()
1165                .then(
1166                    expr.clone()
1167                        .separated_by(just(',').padded())
1168                        .allow_trailing()
1169                        .collect::<Vec<_>>()
1170                        // Ensure parentheses are padded to handle potential whitespace
1171                        .delimited_by(just('(').padded(), just(')').padded()),
1172                )
1173                // Directly create the Expression::Term(Term::Invocation(...)) structure
1174                .map(|(name, params)| {
1175                    Expression::Term(Term::Invocation(Invocation::Function(name, params)))
1176                })
1177                .boxed(),
1178            // Instance selector: TypeName { field: expr, ... }
1179            identifier
1180                .clone()
1181                .then(
1182                    identifier
1183                        .clone()
1184                        .then_ignore(just(':').padded())
1185                        .then(expr.clone().boxed())
1186                        .separated_by(just(',').padded())
1187                        .allow_trailing()
1188                        .collect::<Vec<_>>()
1189                        .delimited_by(just('{').padded(), just('}').padded()),
1190                )
1191                .map(|(type_name, fields)| {
1192                    Expression::InstanceSelector(
1193                        type_name,
1194                        fields.into_iter().map(|(k, v)| (k, Box::new(v))).collect(),
1195                    )
1196                })
1197                .boxed(),
1198            // Simple identifier, $this, $index, $total (parsed if not a function call)
1199            choice((
1200                identifier.clone().map(Invocation::Member),
1201                just("$this").to(Invocation::This),
1202                just("$index").to(Invocation::Index),
1203                just("$total").to(Invocation::Total),
1204            ))
1205            .map(Term::Invocation) // Map these simple invocations to Term
1206            .map(Expression::Term) // Map Term to Expression
1207            .boxed(),
1208            // Parenthesized expression - add extra boxing to break recursion
1209            expr.clone()
1210                .boxed()
1211                .delimited_by(just('(').padded(), just(')').padded())
1212                .boxed(),
1213        ))
1214        .padded();
1215
1216        // Postfix operators: . (member/function invocation) and [] (indexer)
1217        let postfix_op = choice((
1218            // Member/Function Invocation: '.' followed by identifier, optionally followed by args (...)
1219            just('.')
1220                .ignore_then(
1221                    identifier.clone().then(
1222                        // Optionally parse arguments
1223                        expr.clone()
1224                            .boxed()
1225                            .separated_by(just(',').padded())
1226                            .allow_trailing()
1227                            .collect::<Vec<_>>()
1228                            .delimited_by(just('(').padded(), just(')').padded())
1229                            .or_not(), // Make arguments optional
1230                    ),
1231                )
1232                .map(|(name, params_opt)| {
1233                    // Create the correct Invocation based on whether params were found
1234                    let invocation = match params_opt {
1235                        Some(params) => Invocation::Function(name, params),
1236                        None => Invocation::Member(name),
1237                    };
1238                    // Return the closure
1239                    Box::new(move |left: Expression| {
1240                        Expression::Invocation(Box::new(left), invocation.clone())
1241                    }) as Box<dyn Fn(Expression) -> Expression>
1242                }),
1243            // Indexer
1244            expr.clone()
1245                .delimited_by(just('[').padded(), just(']').padded())
1246                .map(|idx| {
1247                    Box::new(move |left: Expression| {
1248                        Expression::Indexer(Box::new(left), Box::new(idx.clone()))
1249                    }) as Box<dyn Fn(Expression) -> Expression>
1250                }),
1251        ))
1252        .boxed(); // Box the choice result
1253
1254        let atom_with_postfix = atom
1255            .clone()
1256            .then(postfix_op.repeated().collect::<Vec<_>>())
1257            .map(|(left, ops)| ops.into_iter().fold(left, |acc, op| op(acc)));
1258
1259        // Prefix operators (Polarity)
1260        let prefix_op = choice((just('+').to('+'), just('-').to('-'))).padded();
1261
1262        let term_with_polarity = prefix_op
1263            .repeated()
1264            .collect::<Vec<_>>()
1265            .then(atom_with_postfix)
1266            .map(|(ops, right)| {
1267                ops.into_iter()
1268                    .rev()
1269                    .fold(right, |acc, op| Expression::Polarity(op, Box::new(acc)))
1270            });
1271
1272        // Infix operators with precedence levels (from high to low)
1273
1274        // Level 1: Multiplicative (*, /, div, mod) - Left associative
1275        let op_mul = choice((
1276            just('*').to("*"),
1277            just('/').to("/"),
1278            text::keyword("div").to("div"),
1279            text::keyword("mod").to("mod"),
1280        ))
1281        .padded();
1282        let multiplicative = term_with_polarity
1283            .clone()
1284            .then(
1285                op_mul
1286                    .then(term_with_polarity)
1287                    .repeated()
1288                    .collect::<Vec<_>>(),
1289            )
1290            .map(|(left, ops)| {
1291                ops.into_iter().fold(left, |acc, (op_str, right)| {
1292                    Expression::Multiplicative(Box::new(acc), op_str.to_string(), Box::new(right))
1293                })
1294            });
1295
1296        // Level 2: Additive (+, -, &) - Left associative
1297        let op_add = choice((just('+').to("+"), just('-').to("-"), just('&').to("&"))).padded();
1298        let additive = multiplicative
1299            .clone()
1300            .then(op_add.then(multiplicative).repeated().collect::<Vec<_>>())
1301            .map(|(left, ops)| {
1302                ops.into_iter().fold(left, |acc, (op_str, right)| {
1303                    Expression::Additive(Box::new(acc), op_str.to_string(), Box::new(right))
1304                })
1305            });
1306
1307        // Level 3: Type (is, as) - Left associative
1308        // Per FHIRPath spec, type operations bind tighter than union
1309        let op_type = choice((text::keyword("is").to("is"), text::keyword("as").to("as"))).padded();
1310        let type_expr = additive
1311            .clone()
1312            .then(
1313                op_type
1314                    .then(qualified_identifier.clone())
1315                    .repeated()
1316                    .collect::<Vec<_>>(),
1317            ) // Type specifier follows 'is'/'as'
1318            .map(|(left, ops)| {
1319                ops.into_iter().fold(left, |acc, (op_str, type_spec)| {
1320                    Expression::Type(Box::new(acc), op_str.to_string(), type_spec)
1321                })
1322            });
1323
1324        // Level 4: Union (|) - Left associative
1325        let op_union = just('|').padded();
1326        let union = type_expr
1327            .clone()
1328            .then(op_union.then(type_expr).repeated().collect::<Vec<_>>())
1329            .map(|(left, ops)| {
1330                ops.into_iter().fold(left, |acc, (_, right)| {
1331                    Expression::Union(Box::new(acc), Box::new(right))
1332                })
1333            });
1334
1335        // Level 5: Inequality (<, <=, >, >=) - Left associative
1336        let op_ineq = choice((
1337            just("<=").to("<="),
1338            just("<").to("<"),
1339            just(">=").to(">="),
1340            just(">").to(">"),
1341        ))
1342        .padded();
1343        let inequality = union
1344            .clone()
1345            .then(op_ineq.then(union).repeated().collect::<Vec<_>>())
1346            .map(|(left, ops)| {
1347                ops.into_iter().fold(left, |acc, (op_str, right)| {
1348                    Expression::Inequality(Box::new(acc), op_str.to_string(), Box::new(right))
1349                })
1350            });
1351
1352        // Level 6: Equality (=, ~, !=, !~) - Left associative
1353        let op_eq = choice((
1354            just("=").to("="),
1355            just("~").to("~"),
1356            just("!=").to("!="),
1357            just("!~").to("!~"),
1358        ))
1359        .padded();
1360        let equality = inequality
1361            .clone()
1362            .boxed()
1363            .then(
1364                op_eq
1365                    .then(inequality.clone().boxed())
1366                    .repeated()
1367                    .collect::<Vec<_>>(),
1368            )
1369            .map(|(left, ops)| {
1370                ops.into_iter().fold(left, |acc, (op_str, right)| {
1371                    Expression::Equality(Box::new(acc), op_str.to_string(), Box::new(right))
1372                })
1373            });
1374
1375        // Level 7: Membership (in, contains) - Left associative
1376        let op_mem = choice((
1377            text::keyword("in").to("in"),
1378            text::keyword("contains").to("contains"),
1379        ))
1380        .padded();
1381        let membership = equality
1382            .clone()
1383            .boxed()
1384            .then(
1385                op_mem
1386                    .then(equality.clone().boxed())
1387                    .repeated()
1388                    .collect::<Vec<_>>(),
1389            )
1390            .map(|(left, ops)| {
1391                ops.into_iter().fold(left, |acc, (op_str, right)| {
1392                    Expression::Membership(Box::new(acc), op_str.to_string(), Box::new(right))
1393                })
1394            });
1395
1396        // Level 8: Logical AND (and) - Left associative
1397        let op_and = text::keyword("and").padded();
1398        let logical_and = membership
1399            .clone()
1400            .boxed()
1401            .then(
1402                op_and
1403                    .then(membership.clone().boxed())
1404                    .repeated()
1405                    .collect::<Vec<_>>(),
1406            )
1407            .map(|(left, ops)| {
1408                ops.into_iter().fold(left, |acc, (_, right)| {
1409                    Expression::And(Box::new(acc), Box::new(right))
1410                })
1411            });
1412
1413        // Level 9: Logical OR/XOR (or, xor) - Left associative
1414        let op_or = choice((text::keyword("or").to("or"), text::keyword("xor").to("xor"))).padded();
1415        let logical_or = logical_and
1416            .clone()
1417            .boxed()
1418            .then(
1419                op_or
1420                    .then(logical_and.clone().boxed())
1421                    .repeated()
1422                    .collect::<Vec<_>>(),
1423            )
1424            .map(|(left, ops)| {
1425                ops.into_iter().fold(left, |acc, (op_str, right)| {
1426                    Expression::Or(Box::new(acc), op_str.to_string(), Box::new(right))
1427                })
1428            });
1429
1430        // Level 10: Implies (implies) - Right associative
1431        let op_implies = text::keyword("implies").padded();
1432        logical_or
1433            .clone()
1434            .boxed()
1435            .then(
1436                op_implies
1437                    .then(logical_or.clone().boxed())
1438                    .repeated()
1439                    .collect::<Vec<_>>(),
1440            )
1441            .map(|(left, ops)| {
1442                ops.into_iter().fold(left, |acc, (_, right)| {
1443                    Expression::Implies(Box::new(acc), Box::new(right))
1444                })
1445            })
1446    }) // Close the recursive closure here
1447    .then_ignore(end()) // Ensure the entire input is consumed after the expression
1448}
1449
1450/// Creates a parser for FHIRPath expressions that includes span (position + length) information.
1451///
1452/// This parser produces `SpannedExpression` nodes, each annotated with their position and length
1453/// in the source text. It is used for the `parseDebugTree` validation output and debug-trace features.
1454///
1455/// The parser structure mirrors `parser()` exactly but wraps each node with span information
1456/// using chumsky's `map_with` combinator.
1457pub fn spanned_parser<'src>()
1458-> impl Parser<'src, &'src str, SpannedExpression, extra::Err<Rich<'src, char>>> + Clone + 'src {
1459    // Parser for escape sequences (same as parser()). Emits a raw code unit
1460    // so surrogate pairs can be combined in `combine_string_code_units`.
1461    let esc = just('\\').ignore_then(choice((
1462        just('`').to('`' as u32),
1463        just('\'').to('\'' as u32),
1464        just('\\').to('\\' as u32),
1465        just('/').to('/' as u32),
1466        just('f').to(0x000C_u32),
1467        just('n').to('\n' as u32),
1468        just('r').to('\r' as u32),
1469        just('t').to('\t' as u32),
1470        just('"').to('"' as u32),
1471        just('u').ignore_then(
1472            any()
1473                .filter(|c: &char| c.is_ascii_hexdigit())
1474                .repeated()
1475                .exactly(4)
1476                .collect::<String>()
1477                .try_map(
1478                    |digits: String, span| match u32::from_str_radix(&digits, 16) {
1479                        Ok(code) => Ok(code),
1480                        Err(_) => Err(Rich::custom(span, "Invalid hex digits")),
1481                    },
1482                ),
1483        ),
1484    )));
1485
1486    // LITERAL PARSERS
1487
1488    let null = just('{').then(just('}')).to(Literal::Null);
1489
1490    let boolean = choice((
1491        text::keyword("true").to(Literal::Boolean(true)),
1492        text::keyword("false").to(Literal::Boolean(false)),
1493    ))
1494    .boxed();
1495
1496    let string = just('\'')
1497        .ignore_then(
1498            none_of("\\\'")
1499                .map(|c: char| c as u32)
1500                .or(esc)
1501                .repeated()
1502                .collect::<Vec<u32>>(),
1503        )
1504        .then_ignore(just('\''))
1505        .try_map(|codes, span| combine_string_code_units(codes).map_err(|e| Rich::custom(span, e)))
1506        .map(Literal::String)
1507        .boxed();
1508
1509    let integer = any()
1510        .filter(|c: &char| c.is_ascii_digit())
1511        .repeated()
1512        .at_least(1)
1513        .collect::<String>()
1514        .try_map(|digits: String, span| match i64::from_str(&digits) {
1515            Ok(n) => Ok(Literal::Integer(n)),
1516            Err(_) => Err(Rich::custom(span, format!("Invalid integer: {}", digits))),
1517        });
1518    let integer = custom_padded(integer);
1519
1520    let number = any()
1521        .filter(|c: &char| c.is_ascii_digit())
1522        .repeated()
1523        .at_least(1)
1524        .collect::<String>()
1525        .then(just('.'))
1526        .then(
1527            any()
1528                .filter(|c: &char| c.is_ascii_digit())
1529                .repeated()
1530                .at_least(1)
1531                .collect::<String>(),
1532        )
1533        .try_map(|((i, _), d), span| {
1534            let num_str = format!("{}.{}", i, d);
1535            match Decimal::from_str(&num_str) {
1536                Ok(decimal) => Ok(Literal::Number(decimal)),
1537                Err(_) => Err(Rich::custom(span, format!("Invalid number: {}", num_str))),
1538            }
1539        })
1540        .padded();
1541
1542    let time_format = any()
1543        .filter(|c: &char| c.is_ascii_digit())
1544        .repeated()
1545        .at_least(2)
1546        .at_most(2)
1547        .collect::<String>()
1548        .then(
1549            just(':')
1550                .ignore_then(
1551                    any()
1552                        .filter(|c: &char| c.is_ascii_digit())
1553                        .repeated()
1554                        .at_least(2)
1555                        .at_most(2)
1556                        .collect::<String>(),
1557                )
1558                .then(
1559                    just(':')
1560                        .ignore_then(
1561                            any()
1562                                .filter(|c: &char| c.is_ascii_digit())
1563                                .repeated()
1564                                .at_least(2)
1565                                .at_most(2)
1566                                .collect::<String>(),
1567                        )
1568                        .then(
1569                            just('.')
1570                                .ignore_then(
1571                                    any()
1572                                        .filter(|c: &char| c.is_ascii_digit())
1573                                        .repeated()
1574                                        .at_least(1)
1575                                        .at_most(3)
1576                                        .collect::<String>(),
1577                                )
1578                                .or_not(),
1579                        )
1580                        .or_not(),
1581                )
1582                .or_not(),
1583        )
1584        .map(|(hours, rest_opt)| {
1585            let mut result = hours;
1586            if let Some((minutes, seconds_part)) = rest_opt {
1587                result.push(':');
1588                result.push_str(&minutes);
1589                if let Some((seconds, milliseconds)) = seconds_part {
1590                    result.push(':');
1591                    result.push_str(&seconds);
1592                    if let Some(ms) = milliseconds {
1593                        result.push('.');
1594                        result.push_str(&ms);
1595                    }
1596                }
1597            }
1598            result
1599        });
1600
1601    let timezone_format = just('Z').to("Z".to_string()).or(one_of("+-")
1602        .map(|c: char| c.to_string())
1603        .then(
1604            any()
1605                .filter(|c: &char| c.is_ascii_digit())
1606                .repeated()
1607                .at_most(2)
1608                .at_least(2)
1609                .collect::<String>(),
1610        )
1611        .then(just(':'))
1612        .then(
1613            any()
1614                .filter(|c: &char| c.is_ascii_digit())
1615                .repeated()
1616                .at_most(2)
1617                .at_least(2)
1618                .collect::<String>(),
1619        )
1620        .map(|(((sign, hour), _), min)| format!("{}{}:{}", sign, hour, min)));
1621
1622    let date_format_str = any()
1623        .filter(|c: &char| c.is_ascii_digit())
1624        .repeated()
1625        .exactly(4)
1626        .collect::<String>()
1627        .then(
1628            just('-')
1629                .ignore_then(
1630                    any()
1631                        .filter(|c: &char| c.is_ascii_digit())
1632                        .repeated()
1633                        .exactly(2)
1634                        .collect::<String>()
1635                        .then(
1636                            just('-')
1637                                .ignore_then(
1638                                    any()
1639                                        .filter(|c: &char| c.is_ascii_digit())
1640                                        .repeated()
1641                                        .exactly(2)
1642                                        .collect::<String>(),
1643                                )
1644                                .or_not(),
1645                        ),
1646                )
1647                .or_not(),
1648        )
1649        .map(|(year, month_part)| {
1650            let mut date_str = year;
1651            if let Some((month_str, day_part)) = month_part {
1652                date_str.push('-');
1653                date_str.push_str(&month_str);
1654                if let Some(day_str) = day_part {
1655                    date_str.push('-');
1656                    date_str.push_str(&day_str);
1657                }
1658            }
1659            date_str
1660        })
1661        .boxed();
1662
1663    let unit_keyword = choice((
1664        text::keyword("year").to("year".to_string()),
1665        text::keyword("month").to("month".to_string()),
1666        text::keyword("week").to("week".to_string()),
1667        text::keyword("day").to("day".to_string()),
1668        text::keyword("hour").to("hour".to_string()),
1669        text::keyword("minute").to("minute".to_string()),
1670        text::keyword("second").to("second".to_string()),
1671        text::keyword("millisecond").to("millisecond".to_string()),
1672        text::keyword("years").to("years".to_string()),
1673        text::keyword("months").to("months".to_string()),
1674        text::keyword("weeks").to("weeks".to_string()),
1675        text::keyword("days").to("days".to_string()),
1676        text::keyword("hours").to("hours".to_string()),
1677        text::keyword("minutes").to("minutes".to_string()),
1678        text::keyword("seconds").to("seconds".to_string()),
1679        text::keyword("milliseconds").to("milliseconds".to_string()),
1680    ));
1681
1682    let unit_string_literal = just('\'')
1683        .ignore_then(
1684            none_of("\\\'")
1685                .map(|c: char| c as u32)
1686                .or(esc)
1687                .repeated()
1688                .collect::<Vec<u32>>(),
1689        )
1690        .then_ignore(just('\''))
1691        .try_map(|codes, span| combine_string_code_units(codes).map_err(|e| Rich::custom(span, e)));
1692
1693    let unit = choice((unit_keyword, unit_string_literal)).boxed().padded();
1694
1695    let integer_for_quantity = any()
1696        .filter(|c: &char| c.is_ascii_digit())
1697        .repeated()
1698        .at_least(1)
1699        .collect::<String>()
1700        .try_map(|digits: String, span| match i64::from_str(&digits) {
1701            Ok(n) => Ok(n),
1702            Err(_) => Err(Rich::custom(span, format!("Invalid integer: {}", digits))),
1703        });
1704
1705    let number_for_quantity = any()
1706        .filter(|c: &char| c.is_ascii_digit())
1707        .repeated()
1708        .at_least(1)
1709        .collect::<String>()
1710        .then(just('.'))
1711        .then(
1712            any()
1713                .filter(|c: &char| c.is_ascii_digit())
1714                .repeated()
1715                .at_least(1)
1716                .collect::<String>(),
1717        )
1718        .try_map(|((i, _), d), span| {
1719            let num_str = format!("{}.{}", i, d);
1720            match Decimal::from_str(&num_str) {
1721                Ok(decimal) => Ok(decimal),
1722                Err(_) => Err(Rich::custom(span, format!("Invalid number: {}", num_str))),
1723            }
1724        });
1725
1726    let quantity = choice((
1727        integer_for_quantity
1728            .then_ignore(text::whitespace().at_least(1))
1729            .then(unit.clone())
1730            .map(|(i, u_str)| Literal::Quantity(Decimal::from(i), u_str)),
1731        number_for_quantity
1732            .then_ignore(text::whitespace().at_least(1))
1733            .then(unit.clone())
1734            .map(|(d, u_str)| Literal::Quantity(d, u_str)),
1735    ));
1736
1737    let datetime_literal = just('@')
1738        .ignore_then(date_format_str.clone())
1739        .then_ignore(just('T'))
1740        .then(time_format)
1741        .then(timezone_format.clone().or_not())
1742        .try_map(|((date_str, time_str), tz_opt), span| {
1743            let full_str = if let Some(tz) = tz_opt {
1744                format!("{}T{}{}", date_str, time_str, tz)
1745            } else {
1746                format!("{}T{}", date_str, time_str)
1747            };
1748            helios_fhir::PrecisionDateTime::parse(&full_str)
1749                .ok_or_else(|| Rich::custom(span, format!("Invalid datetime format: {}", full_str)))
1750                .map(Literal::DateTime)
1751        });
1752
1753    let partial_datetime_literal = just('@')
1754        .ignore_then(date_format_str.clone())
1755        .then_ignore(just('T'))
1756        .try_map(|date_str, span| {
1757            let full_str = format!("{}T", date_str);
1758            helios_fhir::PrecisionDateTime::parse(&full_str)
1759                .ok_or_else(|| {
1760                    Rich::custom(
1761                        span,
1762                        format!("Invalid partial datetime format: {}", full_str),
1763                    )
1764                })
1765                .map(Literal::DateTime)
1766        });
1767
1768    let time_literal = just('@')
1769        .ignore_then(
1770            just('T')
1771                .ignore_then(time_format)
1772                .then(timezone_format.or_not()),
1773        )
1774        .try_map(|(time_str, tz_opt), span| {
1775            if tz_opt.is_some() {
1776                Err(Rich::custom(
1777                    span,
1778                    "Time literal cannot have a timezone offset",
1779                ))
1780            } else {
1781                helios_fhir::PrecisionTime::parse(&time_str)
1782                    .ok_or_else(|| Rich::custom(span, format!("Invalid time format: {}", time_str)))
1783                    .map(Literal::Time)
1784            }
1785        });
1786
1787    let date_literal = just('@')
1788        .ignore_then(date_format_str.clone())
1789        .try_map(|date_str, span| {
1790            helios_fhir::PrecisionDate::parse(&date_str)
1791                .ok_or_else(|| Rich::custom(span, format!("Invalid date format: {}", date_str)))
1792                .map(Literal::Date)
1793        });
1794
1795    let literal = choice((
1796        null,
1797        boolean,
1798        string,
1799        quantity,
1800        number,
1801        integer,
1802        custom_padded(datetime_literal),
1803        custom_padded(partial_datetime_literal),
1804        custom_padded(time_literal),
1805        custom_padded(date_literal),
1806    ))
1807    .map(Term::Literal);
1808
1809    // IDENTIFIER PARSERS
1810
1811    let standard_identifier = any()
1812        .filter(|c: &char| c.is_ascii_alphabetic() || *c == '_')
1813        .then(
1814            any()
1815                .filter(|c: &char| c.is_ascii_alphanumeric() || *c == '_')
1816                .repeated()
1817                .collect::<Vec<_>>(),
1818        )
1819        .map(|(first, rest): (char, Vec<char>)| {
1820            let mut s = first.to_string();
1821            s.extend(rest);
1822            s
1823        })
1824        .padded();
1825
1826    let delimited_identifier = just('`')
1827        .ignore_then(
1828            none_of("`")
1829                .map(|c: char| c as u32)
1830                .or(esc)
1831                .repeated()
1832                .collect::<Vec<u32>>(),
1833        )
1834        .then_ignore(just('`'))
1835        .try_map(|codes, span| combine_string_code_units(codes).map_err(|e| Rich::custom(span, e)))
1836        .padded();
1837
1838    let identifier = choice((
1839        standard_identifier,
1840        delimited_identifier,
1841        text::keyword("as").to(String::from("as")),
1842        text::keyword("contains").to(String::from("contains")),
1843        text::keyword("in").to(String::from("in")),
1844        text::keyword("is").to(String::from("is")),
1845        text::keyword("true").to(String::from("true")),
1846        text::keyword("false").to(String::from("false")),
1847    ));
1848
1849    let qualified_identifier = {
1850        let explicit_namespace_type = identifier
1851            .clone()
1852            .then(just('.').ignore_then(identifier.clone()))
1853            .map(|(namespace, type_name)| {
1854                let clean_ns = clean_backtick_identifier(&namespace);
1855                let clean_type = clean_backtick_identifier(&type_name);
1856                TypeSpecifier::QualifiedIdentifier(clean_ns, Some(clean_type))
1857            });
1858
1859        let standalone_type = identifier.clone().map(|id| {
1860            let clean_id = clean_backtick_identifier(&id);
1861            if clean_id.contains('.') {
1862                if let Some(last_dot_pos) = clean_id.rfind('.') {
1863                    let namespace = clean_id[..last_dot_pos].to_string();
1864                    let type_name = clean_id[last_dot_pos + 1..].to_string();
1865                    TypeSpecifier::QualifiedIdentifier(namespace, Some(type_name))
1866                } else {
1867                    TypeSpecifier::QualifiedIdentifier(clean_id, None)
1868                }
1869            } else {
1870                TypeSpecifier::QualifiedIdentifier(clean_id, None)
1871            }
1872        });
1873
1874        choice((explicit_namespace_type.boxed(), standalone_type.boxed())).boxed()
1875    };
1876    let qualified_identifier = custom_padded(qualified_identifier);
1877
1878    let string_for_external = just('\'')
1879        .ignore_then(
1880            none_of("\'\\")
1881                .map(|c: char| c as u32)
1882                .or(esc)
1883                .repeated()
1884                .collect::<Vec<u32>>(),
1885        )
1886        .then_ignore(just('\''))
1887        .try_map(|codes, span| combine_string_code_units(codes).map_err(|e| Rich::custom(span, e)))
1888        .padded();
1889
1890    let external_constant = just('%')
1891        .ignore_then(choice((identifier.clone(), string_for_external)))
1892        .map(Term::ExternalConstant)
1893        .padded();
1894
1895    // RECURSIVE EXPRESSION PARSER (produces SpannedExpression)
1896
1897    recursive(
1898        |spanned_expr: Recursive<
1899            dyn Parser<'src, &'src str, SpannedExpression, extra::Err<Rich<'src, char>>> + 'src,
1900        >| {
1901            // Helper to create a SpannedExpression from kind and chumsky span
1902            #[inline]
1903            fn make_spanned(kind: SpannedExprKind, start: usize, end: usize) -> SpannedExpression {
1904                SpannedExpression {
1905                    kind,
1906                    span: ExprSpan {
1907                        position: start,
1908                        length: end - start,
1909                    },
1910                }
1911            }
1912
1913            // Atom: basic elements with span tracking
1914            let atom = choice((
1915                // Literal atom
1916                literal
1917                    .clone()
1918                    .map_with(|term, extra| {
1919                        let s = extra.span();
1920                        let spanned_term = match term {
1921                            Term::Literal(l) => SpannedTerm::Literal(l),
1922                            _ => unreachable!(),
1923                        };
1924                        make_spanned(SpannedExprKind::Term(spanned_term), s.start, s.end)
1925                    })
1926                    .boxed(),
1927                // External constant
1928                external_constant
1929                    .clone()
1930                    .map_with(|term, extra| {
1931                        let s = extra.span();
1932                        let spanned_term = match term {
1933                            Term::ExternalConstant(name) => SpannedTerm::ExternalConstant(name),
1934                            _ => unreachable!(),
1935                        };
1936                        make_spanned(SpannedExprKind::Term(spanned_term), s.start, s.end)
1937                    })
1938                    .boxed(),
1939                // Function call: identifier(...)
1940                identifier
1941                    .clone()
1942                    .then(
1943                        spanned_expr
1944                            .clone()
1945                            .separated_by(just(',').padded())
1946                            .allow_trailing()
1947                            .collect::<Vec<_>>()
1948                            .delimited_by(just('(').padded(), just(')').padded()),
1949                    )
1950                    .map_with(|(name, params), extra| {
1951                        let s = extra.span();
1952                        make_spanned(
1953                            SpannedExprKind::Term(SpannedTerm::Invocation(
1954                                SpannedInvocation::Function(name, params),
1955                            )),
1956                            s.start,
1957                            s.end,
1958                        )
1959                    })
1960                    .boxed(),
1961                // Instance selector: TypeName { field: expr, ... }
1962                identifier
1963                    .clone()
1964                    .then(
1965                        identifier
1966                            .clone()
1967                            .then_ignore(just(':').padded())
1968                            .then(spanned_expr.clone().boxed())
1969                            .separated_by(just(',').padded())
1970                            .allow_trailing()
1971                            .collect::<Vec<_>>()
1972                            .delimited_by(just('{').padded(), just('}').padded()),
1973                    )
1974                    .map_with(|(type_name, fields), extra| {
1975                        let s = extra.span();
1976                        make_spanned(
1977                            SpannedExprKind::InstanceSelector(
1978                                type_name,
1979                                fields.into_iter().map(|(k, v)| (k, Box::new(v))).collect(),
1980                            ),
1981                            s.start,
1982                            s.end,
1983                        )
1984                    })
1985                    .boxed(),
1986                // Simple invocation ($this, $index, $total, identifiers)
1987                choice((
1988                    identifier.clone().map(SpannedInvocation::Member),
1989                    just("$this").to(SpannedInvocation::This),
1990                    just("$index").to(SpannedInvocation::Index),
1991                    just("$total").to(SpannedInvocation::Total),
1992                ))
1993                .map_with(|inv, extra| {
1994                    let s = extra.span();
1995                    make_spanned(
1996                        SpannedExprKind::Term(SpannedTerm::Invocation(inv)),
1997                        s.start,
1998                        s.end,
1999                    )
2000                })
2001                .boxed(),
2002                // Parenthesized expression
2003                spanned_expr
2004                    .clone()
2005                    .boxed()
2006                    .delimited_by(just('(').padded(), just(')').padded())
2007                    .map_with(|inner, extra| {
2008                        let s = extra.span();
2009                        make_spanned(
2010                            SpannedExprKind::Term(SpannedTerm::Parenthesized(Box::new(inner))),
2011                            s.start,
2012                            s.end,
2013                        )
2014                    })
2015                    .boxed(),
2016            ))
2017            .padded();
2018
2019            // Postfix operators: . (invocation) and [] (indexer)
2020            let postfix_op = choice((
2021                // Member/Function Invocation
2022                just('.')
2023                    .ignore_then(
2024                        identifier.clone().then(
2025                            spanned_expr
2026                                .clone()
2027                                .boxed()
2028                                .separated_by(just(',').padded())
2029                                .allow_trailing()
2030                                .collect::<Vec<_>>()
2031                                .delimited_by(just('(').padded(), just(')').padded())
2032                                .or_not(),
2033                        ),
2034                    )
2035                    .map_with(|(name, params_opt), extra| {
2036                        let op_end = extra.span().end;
2037                        let invocation = match params_opt {
2038                            Some(params) => SpannedInvocation::Function(name, params),
2039                            None => SpannedInvocation::Member(name),
2040                        };
2041                        Box::new(move |left: SpannedExpression| {
2042                            let start = left.span.position;
2043                            make_spanned(
2044                                SpannedExprKind::Invocation(Box::new(left), invocation.clone()),
2045                                start,
2046                                op_end,
2047                            )
2048                        })
2049                            as Box<dyn Fn(SpannedExpression) -> SpannedExpression>
2050                    }),
2051                // Indexer
2052                spanned_expr
2053                    .clone()
2054                    .delimited_by(just('[').padded(), just(']').padded())
2055                    .map_with(|idx, extra| {
2056                        let op_end = extra.span().end;
2057                        Box::new(move |left: SpannedExpression| {
2058                            let start = left.span.position;
2059                            make_spanned(
2060                                SpannedExprKind::Indexer(Box::new(left), Box::new(idx.clone())),
2061                                start,
2062                                op_end,
2063                            )
2064                        })
2065                            as Box<dyn Fn(SpannedExpression) -> SpannedExpression>
2066                    }),
2067            ))
2068            .boxed();
2069
2070            let atom_with_postfix = atom
2071                .clone()
2072                .then(postfix_op.repeated().collect::<Vec<_>>())
2073                .map(|(left, ops)| ops.into_iter().fold(left, |acc, op| op(acc)));
2074
2075            // Prefix operators (Polarity)
2076            let prefix_op = choice((just('+').to('+'), just('-').to('-'))).padded();
2077
2078            let term_with_polarity = prefix_op
2079                .repeated()
2080                .collect::<Vec<_>>()
2081                .then(atom_with_postfix)
2082                .map_with(|(ops, right), extra| {
2083                    if ops.is_empty() {
2084                        right
2085                    } else {
2086                        let full_start = extra.span().start;
2087                        ops.into_iter().rev().fold(right, |acc, op| {
2088                            let end = acc.span.position + acc.span.length;
2089                            make_spanned(
2090                                SpannedExprKind::Polarity(op, Box::new(acc)),
2091                                full_start,
2092                                end,
2093                            )
2094                        })
2095                    }
2096                });
2097
2098            // Infix operators with precedence levels
2099
2100            // Level 1: Multiplicative
2101            let op_mul = choice((
2102                just('*').to("*"),
2103                just('/').to("/"),
2104                text::keyword("div").to("div"),
2105                text::keyword("mod").to("mod"),
2106            ))
2107            .padded();
2108            let multiplicative = term_with_polarity
2109                .clone()
2110                .then(
2111                    op_mul
2112                        .then(term_with_polarity)
2113                        .repeated()
2114                        .collect::<Vec<_>>(),
2115                )
2116                .map(|(left, ops)| {
2117                    ops.into_iter().fold(left, |acc, (op_str, right)| {
2118                        let start = acc.span.position;
2119                        let end = right.span.position + right.span.length;
2120                        make_spanned(
2121                            SpannedExprKind::Multiplicative(
2122                                Box::new(acc),
2123                                op_str.to_string(),
2124                                Box::new(right),
2125                            ),
2126                            start,
2127                            end,
2128                        )
2129                    })
2130                });
2131
2132            // Level 2: Additive
2133            let op_add = choice((just('+').to("+"), just('-').to("-"), just('&').to("&"))).padded();
2134            let additive = multiplicative
2135                .clone()
2136                .then(op_add.then(multiplicative).repeated().collect::<Vec<_>>())
2137                .map(|(left, ops)| {
2138                    ops.into_iter().fold(left, |acc, (op_str, right)| {
2139                        let start = acc.span.position;
2140                        let end = right.span.position + right.span.length;
2141                        make_spanned(
2142                            SpannedExprKind::Additive(
2143                                Box::new(acc),
2144                                op_str.to_string(),
2145                                Box::new(right),
2146                            ),
2147                            start,
2148                            end,
2149                        )
2150                    })
2151                });
2152
2153            // Level 3: Type (is, as)
2154            let op_type =
2155                choice((text::keyword("is").to("is"), text::keyword("as").to("as"))).padded();
2156            let type_expr = additive
2157                .clone()
2158                .then(
2159                    op_type
2160                        .then(qualified_identifier.clone())
2161                        .repeated()
2162                        .collect::<Vec<_>>(),
2163                )
2164                .map_with(|(left, ops), extra| {
2165                    if ops.is_empty() {
2166                        left
2167                    } else {
2168                        let full_end = extra.span().end;
2169                        ops.into_iter().fold(left, |acc, (op_str, type_spec)| {
2170                            let start = acc.span.position;
2171                            make_spanned(
2172                                SpannedExprKind::Type(Box::new(acc), op_str.to_string(), type_spec),
2173                                start,
2174                                full_end,
2175                            )
2176                        })
2177                    }
2178                });
2179
2180            // Level 4: Union (|)
2181            let op_union = just('|').padded();
2182            let union = type_expr
2183                .clone()
2184                .then(op_union.then(type_expr).repeated().collect::<Vec<_>>())
2185                .map(|(left, ops)| {
2186                    ops.into_iter().fold(left, |acc, (_, right)| {
2187                        let start = acc.span.position;
2188                        let end = right.span.position + right.span.length;
2189                        make_spanned(
2190                            SpannedExprKind::Union(Box::new(acc), Box::new(right)),
2191                            start,
2192                            end,
2193                        )
2194                    })
2195                });
2196
2197            // Level 5: Inequality (<, <=, >, >=)
2198            let op_ineq = choice((
2199                just("<=").to("<="),
2200                just("<").to("<"),
2201                just(">=").to(">="),
2202                just(">").to(">"),
2203            ))
2204            .padded();
2205            let inequality = union
2206                .clone()
2207                .then(op_ineq.then(union).repeated().collect::<Vec<_>>())
2208                .map(|(left, ops)| {
2209                    ops.into_iter().fold(left, |acc, (op_str, right)| {
2210                        let start = acc.span.position;
2211                        let end = right.span.position + right.span.length;
2212                        make_spanned(
2213                            SpannedExprKind::Inequality(
2214                                Box::new(acc),
2215                                op_str.to_string(),
2216                                Box::new(right),
2217                            ),
2218                            start,
2219                            end,
2220                        )
2221                    })
2222                });
2223
2224            // Level 6: Equality (=, ~, !=, !~)
2225            let op_eq = choice((
2226                just("=").to("="),
2227                just("~").to("~"),
2228                just("!=").to("!="),
2229                just("!~").to("!~"),
2230            ))
2231            .padded();
2232            let equality = inequality
2233                .clone()
2234                .boxed()
2235                .then(
2236                    op_eq
2237                        .then(inequality.clone().boxed())
2238                        .repeated()
2239                        .collect::<Vec<_>>(),
2240                )
2241                .map(|(left, ops)| {
2242                    ops.into_iter().fold(left, |acc, (op_str, right)| {
2243                        let start = acc.span.position;
2244                        let end = right.span.position + right.span.length;
2245                        make_spanned(
2246                            SpannedExprKind::Equality(
2247                                Box::new(acc),
2248                                op_str.to_string(),
2249                                Box::new(right),
2250                            ),
2251                            start,
2252                            end,
2253                        )
2254                    })
2255                });
2256
2257            // Level 7: Membership (in, contains)
2258            let op_mem = choice((
2259                text::keyword("in").to("in"),
2260                text::keyword("contains").to("contains"),
2261            ))
2262            .padded();
2263            let membership = equality
2264                .clone()
2265                .boxed()
2266                .then(
2267                    op_mem
2268                        .then(equality.clone().boxed())
2269                        .repeated()
2270                        .collect::<Vec<_>>(),
2271                )
2272                .map(|(left, ops)| {
2273                    ops.into_iter().fold(left, |acc, (op_str, right)| {
2274                        let start = acc.span.position;
2275                        let end = right.span.position + right.span.length;
2276                        make_spanned(
2277                            SpannedExprKind::Membership(
2278                                Box::new(acc),
2279                                op_str.to_string(),
2280                                Box::new(right),
2281                            ),
2282                            start,
2283                            end,
2284                        )
2285                    })
2286                });
2287
2288            // Level 8: Logical AND
2289            let op_and = text::keyword("and").padded();
2290            let logical_and = membership
2291                .clone()
2292                .boxed()
2293                .then(
2294                    op_and
2295                        .then(membership.clone().boxed())
2296                        .repeated()
2297                        .collect::<Vec<_>>(),
2298                )
2299                .map(|(left, ops)| {
2300                    ops.into_iter().fold(left, |acc, (_, right)| {
2301                        let start = acc.span.position;
2302                        let end = right.span.position + right.span.length;
2303                        make_spanned(
2304                            SpannedExprKind::And(Box::new(acc), Box::new(right)),
2305                            start,
2306                            end,
2307                        )
2308                    })
2309                });
2310
2311            // Level 9: Logical OR/XOR
2312            let op_or =
2313                choice((text::keyword("or").to("or"), text::keyword("xor").to("xor"))).padded();
2314            let logical_or = logical_and
2315                .clone()
2316                .boxed()
2317                .then(
2318                    op_or
2319                        .then(logical_and.clone().boxed())
2320                        .repeated()
2321                        .collect::<Vec<_>>(),
2322                )
2323                .map(|(left, ops)| {
2324                    ops.into_iter().fold(left, |acc, (op_str, right)| {
2325                        let start = acc.span.position;
2326                        let end = right.span.position + right.span.length;
2327                        make_spanned(
2328                            SpannedExprKind::Or(Box::new(acc), op_str.to_string(), Box::new(right)),
2329                            start,
2330                            end,
2331                        )
2332                    })
2333                });
2334
2335            // Level 10: Implies
2336            let op_implies = text::keyword("implies").padded();
2337            logical_or
2338                .clone()
2339                .boxed()
2340                .then(
2341                    op_implies
2342                        .then(logical_or.clone().boxed())
2343                        .repeated()
2344                        .collect::<Vec<_>>(),
2345                )
2346                .map(|(left, ops)| {
2347                    ops.into_iter().fold(left, |acc, (_, right)| {
2348                        let start = acc.span.position;
2349                        let end = right.span.position + right.span.length;
2350                        make_spanned(
2351                            SpannedExprKind::Implies(Box::new(acc), Box::new(right)),
2352                            start,
2353                            end,
2354                        )
2355                    })
2356                })
2357        },
2358    )
2359    .then_ignore(end())
2360}