Skip to main content

jsonata_core/
evaluator.rs

1// Expression evaluator
2// Mirrors jsonata.js from the reference implementation
3
4#![allow(clippy::cloned_ref_to_slice_refs)]
5#![allow(clippy::explicit_counter_loop)]
6#![allow(clippy::too_many_arguments)]
7#![allow(clippy::manual_strip)]
8
9use std::cmp::Ordering;
10use std::collections::{HashMap, HashSet};
11
12use crate::ast::{AstNode, BinaryOp, PathStep, Stage};
13use crate::parser;
14use crate::value::JValue;
15use indexmap::IndexMap;
16use std::rc::Rc;
17use thiserror::Error;
18
19/// Specialized sort comparator for `$l.field op $r.field` patterns.
20/// Bypasses the full AST evaluator for simple field-based sort comparisons.
21///
22/// In JSONata `$sort`, the comparator returns true when `$l` should come AFTER `$r`.
23/// `$l.field > $r.field` swaps when left > right, producing ascending order.
24/// `$l.field < $r.field` swaps when left < right, producing descending order.
25struct SpecializedSortComparator {
26    field: String,
27    descending: bool,
28}
29
30/// Pre-extracted sort key for the Schwartzian transform in specialized sorting.
31enum SortKey {
32    Num(f64),
33    Str(Rc<str>),
34    None,
35}
36
37fn compare_sort_keys(a: &SortKey, b: &SortKey, descending: bool) -> Ordering {
38    let ord = match (a, b) {
39        (SortKey::Num(x), SortKey::Num(y)) => x.partial_cmp(y).unwrap_or(Ordering::Equal),
40        (SortKey::Str(x), SortKey::Str(y)) => (**x).cmp(&**y),
41        (SortKey::None, SortKey::None) => Ordering::Equal,
42        (SortKey::None, _) => Ordering::Greater,
43        (_, SortKey::None) => Ordering::Less,
44        // Mixed types: maintain original order
45        _ => Ordering::Equal,
46    };
47    if descending {
48        ord.reverse()
49    } else {
50        ord
51    }
52}
53
54/// Try to extract a specialized sort comparator from a lambda AST node.
55/// Detects patterns like `function($l, $r) { $l.field > $r.field }`.
56fn try_specialize_sort_comparator(
57    body: &AstNode,
58    left_param: &str,
59    right_param: &str,
60) -> Option<SpecializedSortComparator> {
61    let AstNode::Binary { op, lhs, rhs } = body else {
62        return None;
63    };
64
65    // Returns true if op means "swap when left > right" (ascending order).
66    let is_ascending = |op: &BinaryOp| -> Option<bool> {
67        match op {
68            BinaryOp::GreaterThan | BinaryOp::GreaterThanOrEqual => Some(true),
69            BinaryOp::LessThan | BinaryOp::LessThanOrEqual => Some(false),
70            _ => None,
71        }
72    };
73
74    // Extract field name from a `$param.field` path with no stages.
75    let extract_var_field = |node: &AstNode, param: &str| -> Option<String> {
76        let AstNode::Path { steps } = node else {
77            return None;
78        };
79        if steps.len() != 2 {
80            return None;
81        }
82        let AstNode::Variable(var) = &steps[0].node else {
83            return None;
84        };
85        if var != param {
86            return None;
87        }
88        let AstNode::Name(field) = &steps[1].node else {
89            return None;
90        };
91        if !steps[0].stages.is_empty() || !steps[1].stages.is_empty() {
92            return None;
93        }
94        Some(field.clone())
95    };
96
97    // Try both orientations: $l.field op $r.field and $r.field op $l.field (flipped).
98    for flipped in [false, true] {
99        let (lhs_param, rhs_param) = if flipped {
100            (right_param, left_param)
101        } else {
102            (left_param, right_param)
103        };
104        if let (Some(lhs_field), Some(rhs_field)) = (
105            extract_var_field(lhs, lhs_param),
106            extract_var_field(rhs, rhs_param),
107        ) {
108            if lhs_field == rhs_field {
109                let descending = match op {
110                    // Subtraction: `$l.f - $r.f` → positive when l > r → ascending.
111                    // Flipped `$r.f - $l.f` → positive when r > l → descending.
112                    BinaryOp::Subtract => flipped,
113                    // Comparison: `$l.f > $r.f` → ascending, flipped inverts.
114                    _ => {
115                        let ascending = is_ascending(op)?;
116                        if flipped {
117                            ascending
118                        } else {
119                            !ascending
120                        }
121                    }
122                };
123                return Some(SpecializedSortComparator {
124                    field: lhs_field,
125                    descending,
126                });
127            }
128        }
129    }
130    None
131}
132
133// ──────────────────────────────────────────────────────────────────────────────
134// CompiledExpr — unified compiled expression framework
135// ──────────────────────────────────────────────────────────────────────────────
136//
137// Generalizes SpecializedPredicate and CompiledObjectMap into a single IR that
138// can represent arbitrary simple expressions without AST walking.  Evaluated in
139// a tight loop with no recursion tracking, no scope management, and no AstNode
140// pattern matching.
141
142/// Shape cache: maps field names to their positional index in an IndexMap.
143/// When all objects in an array share the same key ordering (extremely common
144/// in JSON data), field lookups become O(1) Vec index access via `get_index()`
145/// instead of O(1)-amortized hash lookups.
146type ShapeCache = HashMap<String, usize>;
147
148/// Build a shape cache from the first object in an array.
149/// Returns None if the data is not an object.
150fn build_shape_cache(first_element: &JValue) -> Option<ShapeCache> {
151    match first_element {
152        JValue::Object(obj) => {
153            let mut cache = HashMap::with_capacity(obj.len());
154            for (idx, (key, _)) in obj.iter().enumerate() {
155                cache.insert(key.clone(), idx);
156            }
157            Some(cache)
158        }
159        _ => None,
160    }
161}
162
163/// Comparison operator for compiled expressions.
164#[derive(Debug, Clone, Copy)]
165pub(crate) enum CompiledCmp {
166    Eq,
167    Ne,
168    Lt,
169    Le,
170    Gt,
171    Ge,
172}
173
174/// Arithmetic operator for compiled expressions.
175#[derive(Debug, Clone, Copy)]
176pub(crate) enum CompiledArithOp {
177    Add,
178    Sub,
179    Mul,
180    Div,
181    Mod,
182}
183
184/// Unified compiled expression — replaces SpecializedPredicate & CompiledObjectMap.
185///
186/// `try_compile_expr()` converts an AstNode subtree into a CompiledExpr at
187/// expression-compile time (once), then `eval_compiled()` evaluates it per
188/// element in O(expression-size) with no heap allocation in the hot path.
189#[derive(Clone, Debug)]
190pub(crate) enum CompiledExpr {
191    // ── Leaves ──────────────────────────────────────────────────────────
192    /// A literal value known at compile time.
193    Literal(JValue),
194    /// Explicit `null` literal from `AstNode::Null`.
195    /// Distinct from field-lookup-produced null: triggers T2010/T2002 errors
196    /// in comparisons/arithmetic, matching the tree-walker's `explicit_null` semantics.
197    ExplicitNull,
198    /// Single-level field lookup on the current object: `obj.get("field")`.
199    FieldLookup(String),
200    /// Two-level nested field lookup: `obj.get("a")?.get("b")`.
201    NestedFieldLookup(String, String),
202    /// Variable lookup from enclosing scope (e.g. `$var`).
203    /// Resolved at eval time via a provided variable map.
204    VariableLookup(String),
205
206    // ── Comparison ──────────────────────────────────────────────────────
207    Compare {
208        op: CompiledCmp,
209        lhs: Box<CompiledExpr>,
210        rhs: Box<CompiledExpr>,
211    },
212
213    // ── Arithmetic ──────────────────────────────────────────────────────
214    Arithmetic {
215        op: CompiledArithOp,
216        lhs: Box<CompiledExpr>,
217        rhs: Box<CompiledExpr>,
218    },
219
220    // ── String ──────────────────────────────────────────────────────────
221    Concat(Box<CompiledExpr>, Box<CompiledExpr>),
222
223    // ── Logical ─────────────────────────────────────────────────────────
224    And(Box<CompiledExpr>, Box<CompiledExpr>),
225    Or(Box<CompiledExpr>, Box<CompiledExpr>),
226    Not(Box<CompiledExpr>),
227    /// Negation of a numeric value.
228    Negate(Box<CompiledExpr>),
229
230    // ── Conditional ─────────────────────────────────────────────────────
231    Conditional {
232        condition: Box<CompiledExpr>,
233        then_expr: Box<CompiledExpr>,
234        else_expr: Option<Box<CompiledExpr>>,
235    },
236
237    // ── Compound ────────────────────────────────────────────────────────
238    /// Object construction: `{"key1": expr1, "key2": expr2, ...}`
239    ObjectConstruct(Vec<(String, CompiledExpr)>),
240    /// Array construction: `[expr1, expr2, ...]`
241    ///
242    /// Each element carries a `bool` flag: `true` means the element originated
243    /// from an explicit `AstNode::Array` constructor and must be kept nested even
244    /// if it evaluates to an array. `false` means the element's array value is
245    /// flattened one level into the outer result (JSONata `[a.b, ...]` semantics).
246    /// Undefined values are always skipped.
247    ArrayConstruct(Vec<(CompiledExpr, bool)>),
248
249    // ── Phase 2 extensions ──────────────────────────────────────────────
250    /// Named variable lookup from context scope (any `$name` not in lambda params).
251    /// Compiled when a named variable is encountered and no allowed_vars list is
252    /// provided (top-level compilation). At runtime, returns the value from the vars
253    /// map (lambda params or captured env), or Undefined if not present.
254    #[allow(dead_code)]
255    ContextVar(String),
256
257    /// Multi-step field path with optional per-step filters: `a.b[pred].c`
258    /// Applies implicit array-mapping semantics at each step.
259    FieldPath(Vec<CompiledStep>),
260
261    /// Call a pure, side-effect-free builtin with compiled arguments.
262    /// Only builtins in COMPILABLE_BUILTINS are allowed here.
263    BuiltinCall {
264        name: &'static str,
265        args: Vec<CompiledExpr>,
266    },
267
268    /// Sequential block: evaluate all expressions, return last value.
269    Block(Vec<CompiledExpr>),
270
271    /// Coalesce (`??`): return lhs if it is defined and non-null, else rhs.
272    Coalesce(Box<CompiledExpr>, Box<CompiledExpr>),
273
274    // ── Higher-order functions with inline lambdas ───────────────────────
275    /// `$map(array, function($v [, $i]) { body })` — compiled when the second
276    /// argument is an inline lambda literal (not a stored variable).
277    /// `params` holds the lambda parameter names (without `$`), 1 or 2 elements.
278    MapCall {
279        array: Box<CompiledExpr>,
280        params: Vec<String>,
281        body: Box<CompiledExpr>,
282    },
283    /// `$filter(array, function($v [, $i]) { body })` — compiled when the second
284    /// argument is an inline lambda literal.
285    FilterCall {
286        array: Box<CompiledExpr>,
287        params: Vec<String>,
288        body: Box<CompiledExpr>,
289    },
290    /// `$reduce(array, function($acc, $v) { body } [, initial])` — compiled when the
291    /// second argument is an inline lambda literal with exactly 2 parameters.
292    ReduceCall {
293        array: Box<CompiledExpr>,
294        params: Vec<String>,
295        body: Box<CompiledExpr>,
296        initial: Option<Box<CompiledExpr>>,
297    },
298}
299
300/// One step in a compiled `FieldPath`.
301#[derive(Clone, Debug)]
302pub(crate) struct CompiledStep {
303    /// Field name to look up at this step.
304    pub field: String,
305    /// Optional predicate filter compiled from a `Stage::Filter` stage.
306    pub filter: Option<CompiledExpr>,
307}
308
309/// Try to compile an AstNode subtree into a CompiledExpr.
310/// Returns None for anything that requires full AST evaluation (lambda calls,
311/// function calls with side effects, complex paths, etc.).
312pub(crate) fn try_compile_expr(node: &AstNode) -> Option<CompiledExpr> {
313    try_compile_expr_inner(node, None)
314}
315
316/// Like `try_compile_expr` but additionally allows the specified variable names
317/// to be compiled as `VariableLookup`. Used by HOF integration where lambda
318/// parameters are known and will be provided via the `vars` map at eval time.
319pub(crate) fn try_compile_expr_with_allowed_vars(
320    node: &AstNode,
321    allowed_vars: &[&str],
322) -> Option<CompiledExpr> {
323    try_compile_expr_inner(node, Some(allowed_vars))
324}
325
326fn try_compile_expr_inner(node: &AstNode, allowed_vars: Option<&[&str]>) -> Option<CompiledExpr> {
327    match node {
328        // ── Literals ────────────────────────────────────────────────────
329        AstNode::String(s) => Some(CompiledExpr::Literal(JValue::string(s.clone()))),
330        AstNode::Number(n) => Some(CompiledExpr::Literal(JValue::Number(*n))),
331        AstNode::Boolean(b) => Some(CompiledExpr::Literal(JValue::Bool(*b))),
332        AstNode::Null => Some(CompiledExpr::ExplicitNull),
333
334        // ── Field access ────────────────────────────────────────────────
335        AstNode::Name(field) => Some(CompiledExpr::FieldLookup(field.clone())),
336
337        // ── Variable lookup ─────────────────────────────────────────────
338        // $ (empty name) always refers to the current element.
339        // Named variables: in HOF mode (allowed_vars=Some), only compile if the
340        // variable is in the allowed set (lambda params supplied via vars map).
341        // In top-level mode (allowed_vars=None), compile unknown variables as
342        // ContextVar — they return Undefined at runtime when no bindings are passed.
343        AstNode::Variable(var) if var.is_empty() => Some(CompiledExpr::VariableLookup(var.clone())),
344        AstNode::Variable(var) => {
345            if let Some(allowed) = allowed_vars {
346                // HOF mode: only compile if the variable is a known lambda param.
347                if allowed.contains(&var.as_str()) {
348                    return Some(CompiledExpr::VariableLookup(var.clone()));
349                }
350            }
351            // Named variables require Context for correct lookup (scope stack, builtins
352            // registry). The compiled fast path passes ctx=None, so fall back to the
353            // tree-walker for all non-empty variable references.
354            None
355        }
356
357        // ── Path expressions ────────────────────────────────────────────
358        AstNode::Path { steps } => try_compile_path(steps, allowed_vars),
359
360        // ── Binary operations ───────────────────────────────────────────
361        AstNode::Binary { op, lhs, rhs } => {
362            let compiled_lhs = try_compile_expr_inner(lhs, allowed_vars)?;
363            let compiled_rhs = try_compile_expr_inner(rhs, allowed_vars)?;
364            match op {
365                // Comparison
366                BinaryOp::Equal => Some(CompiledExpr::Compare {
367                    op: CompiledCmp::Eq,
368                    lhs: Box::new(compiled_lhs),
369                    rhs: Box::new(compiled_rhs),
370                }),
371                BinaryOp::NotEqual => Some(CompiledExpr::Compare {
372                    op: CompiledCmp::Ne,
373                    lhs: Box::new(compiled_lhs),
374                    rhs: Box::new(compiled_rhs),
375                }),
376                BinaryOp::LessThan => Some(CompiledExpr::Compare {
377                    op: CompiledCmp::Lt,
378                    lhs: Box::new(compiled_lhs),
379                    rhs: Box::new(compiled_rhs),
380                }),
381                BinaryOp::LessThanOrEqual => Some(CompiledExpr::Compare {
382                    op: CompiledCmp::Le,
383                    lhs: Box::new(compiled_lhs),
384                    rhs: Box::new(compiled_rhs),
385                }),
386                BinaryOp::GreaterThan => Some(CompiledExpr::Compare {
387                    op: CompiledCmp::Gt,
388                    lhs: Box::new(compiled_lhs),
389                    rhs: Box::new(compiled_rhs),
390                }),
391                BinaryOp::GreaterThanOrEqual => Some(CompiledExpr::Compare {
392                    op: CompiledCmp::Ge,
393                    lhs: Box::new(compiled_lhs),
394                    rhs: Box::new(compiled_rhs),
395                }),
396                // Arithmetic
397                BinaryOp::Add => Some(CompiledExpr::Arithmetic {
398                    op: CompiledArithOp::Add,
399                    lhs: Box::new(compiled_lhs),
400                    rhs: Box::new(compiled_rhs),
401                }),
402                BinaryOp::Subtract => Some(CompiledExpr::Arithmetic {
403                    op: CompiledArithOp::Sub,
404                    lhs: Box::new(compiled_lhs),
405                    rhs: Box::new(compiled_rhs),
406                }),
407                BinaryOp::Multiply => Some(CompiledExpr::Arithmetic {
408                    op: CompiledArithOp::Mul,
409                    lhs: Box::new(compiled_lhs),
410                    rhs: Box::new(compiled_rhs),
411                }),
412                BinaryOp::Divide => Some(CompiledExpr::Arithmetic {
413                    op: CompiledArithOp::Div,
414                    lhs: Box::new(compiled_lhs),
415                    rhs: Box::new(compiled_rhs),
416                }),
417                BinaryOp::Modulo => Some(CompiledExpr::Arithmetic {
418                    op: CompiledArithOp::Mod,
419                    lhs: Box::new(compiled_lhs),
420                    rhs: Box::new(compiled_rhs),
421                }),
422                // Logical
423                BinaryOp::And => Some(CompiledExpr::And(
424                    Box::new(compiled_lhs),
425                    Box::new(compiled_rhs),
426                )),
427                BinaryOp::Or => Some(CompiledExpr::Or(
428                    Box::new(compiled_lhs),
429                    Box::new(compiled_rhs),
430                )),
431                // String concat
432                BinaryOp::Concatenate => Some(CompiledExpr::Concat(
433                    Box::new(compiled_lhs),
434                    Box::new(compiled_rhs),
435                )),
436                // Coalesce: return lhs if defined/non-null, else rhs
437                BinaryOp::Coalesce => Some(CompiledExpr::Coalesce(
438                    Box::new(compiled_lhs),
439                    Box::new(compiled_rhs),
440                )),
441                // Anything else (Range, In, ColonEqual, ChainPipe, etc.) — not compilable
442                _ => None,
443            }
444        }
445
446        // ── Unary operations ────────────────────────────────────────────
447        AstNode::Unary { op, operand } => {
448            let compiled = try_compile_expr_inner(operand, allowed_vars)?;
449            match op {
450                crate::ast::UnaryOp::Not => Some(CompiledExpr::Not(Box::new(compiled))),
451                crate::ast::UnaryOp::Negate => Some(CompiledExpr::Negate(Box::new(compiled))),
452            }
453        }
454
455        // ── Conditional ─────────────────────────────────────────────────
456        AstNode::Conditional {
457            condition,
458            then_branch,
459            else_branch,
460        } => {
461            let cond = try_compile_expr_inner(condition, allowed_vars)?;
462            let then_e = try_compile_expr_inner(then_branch, allowed_vars)?;
463            let else_e = match else_branch {
464                Some(e) => Some(Box::new(try_compile_expr_inner(e, allowed_vars)?)),
465                None => None,
466            };
467            Some(CompiledExpr::Conditional {
468                condition: Box::new(cond),
469                then_expr: Box::new(then_e),
470                else_expr: else_e,
471            })
472        }
473
474        // ── Object construction ─────────────────────────────────────────
475        AstNode::Object(pairs) => {
476            let mut fields = Vec::with_capacity(pairs.len());
477            for (key_node, val_node) in pairs {
478                // Key must be a string literal
479                let key = match key_node {
480                    AstNode::String(s) => s.clone(),
481                    _ => return None,
482                };
483                let val = try_compile_expr_inner(val_node, allowed_vars)?;
484                fields.push((key, val));
485            }
486            Some(CompiledExpr::ObjectConstruct(fields))
487        }
488
489        // ── Array construction ──────────────────────────────────────────
490        AstNode::Array(elems) => {
491            let mut compiled = Vec::with_capacity(elems.len());
492            for elem in elems {
493                // Tag whether the element itself is an array constructor: if so, its
494                // array value must be kept nested rather than flattened (tree-walker parity).
495                let is_nested = matches!(elem, AstNode::Array(_));
496                compiled.push((try_compile_expr_inner(elem, allowed_vars)?, is_nested));
497            }
498            Some(CompiledExpr::ArrayConstruct(compiled))
499        }
500
501        // ── Block (sequential evaluation) ───────────────────────────────
502        AstNode::Block(exprs) if !exprs.is_empty() => {
503            let compiled: Option<Vec<CompiledExpr>> = exprs
504                .iter()
505                .map(|e| try_compile_expr_inner(e, allowed_vars))
506                .collect();
507            compiled.map(CompiledExpr::Block)
508        }
509
510        // ── Pure builtin function calls ──────────────────────────────────
511        AstNode::Function {
512            name,
513            args,
514            is_builtin: true,
515        } => {
516            if is_compilable_builtin(name) {
517                // Arity guard: if the call site passes more args than the builtin accepts,
518                // fall back to the tree-walker so it can raise the correct T0410 error.
519                if let Some(max) = compilable_builtin_max_args(name) {
520                    if args.len() > max {
521                        return None;
522                    }
523                }
524                let compiled_args: Option<Vec<CompiledExpr>> = args
525                    .iter()
526                    .map(|a| try_compile_expr_inner(a, allowed_vars))
527                    .collect();
528                compiled_args.map(|cargs| CompiledExpr::BuiltinCall {
529                    name: static_builtin_name(name),
530                    args: cargs,
531                })
532            } else {
533                try_compile_hof_expr(name, args, allowed_vars)
534            }
535        }
536
537        // Everything else: Lambda, non-pure builtins, Sort, Transform, etc.
538        _ => None,
539    }
540}
541
542/// Extract an inline lambda's params and body from an AST node, returning `None` if the
543/// node is not a simple lambda (i.e. has a signature or is a TCO thunk).
544fn extract_inline_lambda(node: &AstNode) -> Option<(&Vec<String>, &AstNode)> {
545    match node {
546        AstNode::Lambda {
547            params,
548            body,
549            signature: None,
550            thunk: false,
551        } => Some((params, body)),
552        _ => None,
553    }
554}
555
556/// Compile the array argument + lambda body for a HOF call, returning `None` if either
557/// fails to compile. The lambda params are added to the allowed-vars set so the body
558/// can reference them.
559fn compile_hof_array_and_body(
560    array_node: &AstNode,
561    params: &[String],
562    body: &AstNode,
563    allowed_vars: Option<&[&str]>,
564) -> Option<(Box<CompiledExpr>, Box<CompiledExpr>)> {
565    let array = try_compile_expr_inner(array_node, allowed_vars)?;
566    let param_refs: Vec<&str> = params.iter().map(|s| s.as_str()).collect();
567    let compiled_body = try_compile_expr_inner(body, Some(&param_refs))?;
568    Some((Box::new(array), Box::new(compiled_body)))
569}
570
571/// Try to compile a higher-order function call (`$map`, `$filter`, `$reduce`) when the
572/// callback argument is an inline lambda literal with a compilable body.
573///
574/// Returns `None` when:
575/// - The callback is not an inline lambda (e.g. a stored variable `$f`) — fall back so
576///   the tree-walker can look up the lambda at runtime.
577/// - The lambda has a signature or is a TCO thunk — semantics require the full evaluator.
578/// - The lambda body is not fully compilable — fall back transparently.
579/// - Param count is outside the supported range (see per-function constraints below).
580fn try_compile_hof_expr(
581    name: &str,
582    args: &[AstNode],
583    allowed_vars: Option<&[&str]>,
584) -> Option<CompiledExpr> {
585    match name {
586        "map" | "filter" => {
587            if args.len() != 2 {
588                return None;
589            }
590            let (params, body) = extract_inline_lambda(&args[1])?;
591            if params.is_empty() || params.len() > 2 {
592                return None;
593            }
594            let (array, compiled_body) =
595                compile_hof_array_and_body(&args[0], params, body, allowed_vars)?;
596            if name == "map" {
597                Some(CompiledExpr::MapCall {
598                    array,
599                    params: params.clone(),
600                    body: compiled_body,
601                })
602            } else {
603                Some(CompiledExpr::FilterCall {
604                    array,
605                    params: params.clone(),
606                    body: compiled_body,
607                })
608            }
609        }
610        "reduce" => {
611            if args.len() < 2 || args.len() > 3 {
612                return None;
613            }
614            let (params, body) = extract_inline_lambda(&args[1])?;
615            if params.len() != 2 {
616                return None;
617            }
618            let (array, compiled_body) =
619                compile_hof_array_and_body(&args[0], params, body, allowed_vars)?;
620            let initial = if args.len() == 3 {
621                Some(Box::new(try_compile_expr_inner(&args[2], allowed_vars)?))
622            } else {
623                None
624            };
625            Some(CompiledExpr::ReduceCall {
626                array,
627                params: params.clone(),
628                body: compiled_body,
629                initial,
630            })
631        }
632        _ => None,
633    }
634}
635
636/// Returns true if the named builtin is pure (no side effects, no context dependency)
637/// and can be safely compiled into a BuiltinCall.
638fn is_compilable_builtin(name: &str) -> bool {
639    matches!(
640        name,
641        "string"
642            | "length"
643            | "substring"
644            | "substringBefore"
645            | "substringAfter"
646            | "uppercase"
647            | "lowercase"
648            | "trim"
649            | "contains"
650            | "split"
651            | "join"
652            | "number"
653            | "floor"
654            | "ceil"
655            | "round"
656            | "abs"
657            | "sqrt"
658            | "sum"
659            | "max"
660            | "min"
661            | "average"
662            | "count"
663            | "boolean"
664            | "not"
665            | "keys"
666            | "append"
667            | "reverse"
668            | "distinct"
669            | "merge"
670    )
671}
672
673/// Maximum number of explicit arguments accepted by each compilable builtin.
674/// Returns `None` for variadic functions with no fixed upper bound.
675/// Used at compile time to fall back to the tree-walker for over-arity calls
676/// (which the tree-walker turns into the correct T0410/T0411 type errors).
677fn compilable_builtin_max_args(name: &str) -> Option<usize> {
678    match name {
679        "string" => Some(2),
680        "length" | "uppercase" | "lowercase" | "trim" => Some(1),
681        "substring" | "split" => Some(3),
682        "substringBefore" | "substringAfter" | "contains" | "join" | "append" | "round" => Some(2),
683        "number" | "floor" | "ceil" | "abs" | "sqrt" => Some(1),
684        "sum" | "max" | "min" | "average" | "count" => Some(1),
685        "boolean" | "not" | "keys" | "reverse" | "distinct" => Some(1),
686        "merge" => None, // variadic: $merge(obj1, obj2, …) or $merge([…])
687        _ => None,
688    }
689}
690
691/// Return the `&'static str` for a known compilable builtin name.
692/// SAFETY: only called after `is_compilable_builtin` returns true.
693fn static_builtin_name(name: &str) -> &'static str {
694    match name {
695        "string" => "string",
696        "length" => "length",
697        "substring" => "substring",
698        "substringBefore" => "substringBefore",
699        "substringAfter" => "substringAfter",
700        "uppercase" => "uppercase",
701        "lowercase" => "lowercase",
702        "trim" => "trim",
703        "contains" => "contains",
704        "split" => "split",
705        "join" => "join",
706        "number" => "number",
707        "floor" => "floor",
708        "ceil" => "ceil",
709        "round" => "round",
710        "abs" => "abs",
711        "sqrt" => "sqrt",
712        "sum" => "sum",
713        "max" => "max",
714        "min" => "min",
715        "average" => "average",
716        "count" => "count",
717        "boolean" => "boolean",
718        "not" => "not",
719        "keys" => "keys",
720        "append" => "append",
721        "reverse" => "reverse",
722        "distinct" => "distinct",
723        "merge" => "merge",
724        _ => unreachable!("Not a compilable builtin: {}", name),
725    }
726}
727
728/// Evaluate a compiled expression against a single element.
729///
730/// `data` is the current element (typically an object from an array).
731/// `vars` is an optional map of variable bindings (for HOF lambda parameters).
732///
733/// This is the tight inner loop — no recursion tracking, no scope push/pop,
734/// no AstNode pattern matching.
735#[inline(always)]
736pub(crate) fn eval_compiled(
737    expr: &CompiledExpr,
738    data: &JValue,
739    vars: Option<&HashMap<&str, &JValue>>,
740) -> Result<JValue, EvaluatorError> {
741    eval_compiled_inner(expr, data, vars, None, None)
742}
743
744/// Like `eval_compiled` but with an optional shape cache for O(1) positional
745/// field access. The shape cache maps field names to their index in the object's
746/// internal Vec, enabling `get_index()` instead of hash lookups.
747#[inline(always)]
748fn eval_compiled_shaped(
749    expr: &CompiledExpr,
750    data: &JValue,
751    vars: Option<&HashMap<&str, &JValue>>,
752    shape: &ShapeCache,
753) -> Result<JValue, EvaluatorError> {
754    eval_compiled_inner(expr, data, vars, None, Some(shape))
755}
756
757/// Clone the outer variable bindings into a new HashMap with the given capacity hint.
758/// Used by HOF eval arms to create per-iteration variable scopes that merge outer vars
759/// with lambda parameters.
760#[inline]
761fn clone_outer_vars<'a>(
762    vars: Option<&HashMap<&'a str, &'a JValue>>,
763    capacity: usize,
764) -> HashMap<&'a str, &'a JValue> {
765    vars.map(|v| v.iter().map(|(&k, v)| (k, *v)).collect())
766        .unwrap_or_else(|| HashMap::with_capacity(capacity))
767}
768
769fn eval_compiled_inner(
770    expr: &CompiledExpr,
771    data: &JValue,
772    vars: Option<&HashMap<&str, &JValue>>,
773    ctx: Option<&Context>,
774    shape: Option<&ShapeCache>,
775) -> Result<JValue, EvaluatorError> {
776    match expr {
777        // ── Leaves ──────────────────────────────────────────────────────
778        CompiledExpr::Literal(v) => Ok(v.clone()),
779
780        // ExplicitNull evaluates to Null, but is flagged at compile-time for
781        // comparison/arithmetic arms to trigger the correct T2010/T2002 errors.
782        CompiledExpr::ExplicitNull => Ok(JValue::Null),
783
784        CompiledExpr::FieldLookup(field) => match data {
785            JValue::Object(obj) => {
786                // Shape-accelerated: use positional index if available
787                if let Some(shape) = shape {
788                    if let Some(&idx) = shape.get(field.as_str()) {
789                        return Ok(obj
790                            .get_index(idx)
791                            .map(|(_, v)| v.clone())
792                            .unwrap_or(JValue::Undefined));
793                    }
794                }
795                Ok(obj
796                    .get(field.as_str())
797                    .cloned()
798                    .unwrap_or(JValue::Undefined))
799            }
800            _ => Ok(JValue::Undefined),
801        },
802
803        CompiledExpr::NestedFieldLookup(outer, inner) => match data {
804            JValue::Object(obj) => {
805                // Shape-accelerated outer lookup
806                let outer_val = if let Some(shape) = shape {
807                    if let Some(&idx) = shape.get(outer.as_str()) {
808                        obj.get_index(idx).map(|(_, v)| v)
809                    } else {
810                        obj.get(outer.as_str())
811                    }
812                } else {
813                    obj.get(outer.as_str())
814                };
815                Ok(outer_val
816                    .and_then(|v| match v {
817                        JValue::Object(nested) => nested.get(inner.as_str()).cloned(),
818                        _ => None,
819                    })
820                    .unwrap_or(JValue::Undefined))
821            }
822            _ => Ok(JValue::Undefined),
823        },
824
825        CompiledExpr::VariableLookup(var) => {
826            if let Some(vars) = vars {
827                if let Some(val) = vars.get(var.as_str()) {
828                    return Ok((*val).clone());
829                }
830            }
831            // $ (empty var name) refers to the current data
832            if var.is_empty() {
833                return Ok(data.clone());
834            }
835            Ok(JValue::Undefined)
836        }
837
838        // ── Comparison ──────────────────────────────────────────────────
839        CompiledExpr::Compare { op, lhs, rhs } => {
840            let lhs_explicit_null = is_compiled_explicit_null(lhs);
841            let rhs_explicit_null = is_compiled_explicit_null(rhs);
842            let left = eval_compiled_inner(lhs, data, vars, ctx, shape)?;
843            let right = eval_compiled_inner(rhs, data, vars, ctx, shape)?;
844            match op {
845                CompiledCmp::Eq => Ok(JValue::Bool(crate::functions::array::values_equal(
846                    &left, &right,
847                ))),
848                CompiledCmp::Ne => Ok(JValue::Bool(!crate::functions::array::values_equal(
849                    &left, &right,
850                ))),
851                CompiledCmp::Lt => compiled_ordered_cmp(
852                    &left,
853                    &right,
854                    lhs_explicit_null,
855                    rhs_explicit_null,
856                    |a, b| a < b,
857                    |a, b| a < b,
858                ),
859                CompiledCmp::Le => compiled_ordered_cmp(
860                    &left,
861                    &right,
862                    lhs_explicit_null,
863                    rhs_explicit_null,
864                    |a, b| a <= b,
865                    |a, b| a <= b,
866                ),
867                CompiledCmp::Gt => compiled_ordered_cmp(
868                    &left,
869                    &right,
870                    lhs_explicit_null,
871                    rhs_explicit_null,
872                    |a, b| a > b,
873                    |a, b| a > b,
874                ),
875                CompiledCmp::Ge => compiled_ordered_cmp(
876                    &left,
877                    &right,
878                    lhs_explicit_null,
879                    rhs_explicit_null,
880                    |a, b| a >= b,
881                    |a, b| a >= b,
882                ),
883            }
884        }
885
886        // ── Arithmetic ──────────────────────────────────────────────────
887        CompiledExpr::Arithmetic { op, lhs, rhs } => {
888            let lhs_explicit_null = is_compiled_explicit_null(lhs);
889            let rhs_explicit_null = is_compiled_explicit_null(rhs);
890            let left = eval_compiled_inner(lhs, data, vars, ctx, shape)?;
891            let right = eval_compiled_inner(rhs, data, vars, ctx, shape)?;
892            compiled_arithmetic(*op, &left, &right, lhs_explicit_null, rhs_explicit_null)
893        }
894
895        // ── String concat ───────────────────────────────────────────────
896        CompiledExpr::Concat(lhs, rhs) => {
897            let left = eval_compiled_inner(lhs, data, vars, ctx, shape)?;
898            let right = eval_compiled_inner(rhs, data, vars, ctx, shape)?;
899            let ls = compiled_to_concat_string(&left)?;
900            let rs = compiled_to_concat_string(&right)?;
901            Ok(JValue::string(format!("{}{}", ls, rs)))
902        }
903
904        // ── Logical ─────────────────────────────────────────────────────
905        CompiledExpr::And(lhs, rhs) => {
906            let left = eval_compiled_inner(lhs, data, vars, ctx, shape)?;
907            if !compiled_is_truthy(&left) {
908                return Ok(JValue::Bool(false));
909            }
910            let right = eval_compiled_inner(rhs, data, vars, ctx, shape)?;
911            Ok(JValue::Bool(compiled_is_truthy(&right)))
912        }
913        CompiledExpr::Or(lhs, rhs) => {
914            let left = eval_compiled_inner(lhs, data, vars, ctx, shape)?;
915            if compiled_is_truthy(&left) {
916                return Ok(JValue::Bool(true));
917            }
918            let right = eval_compiled_inner(rhs, data, vars, ctx, shape)?;
919            Ok(JValue::Bool(compiled_is_truthy(&right)))
920        }
921        CompiledExpr::Not(inner) => {
922            let val = eval_compiled_inner(inner, data, vars, ctx, shape)?;
923            Ok(JValue::Bool(!compiled_is_truthy(&val)))
924        }
925        CompiledExpr::Negate(inner) => {
926            let val = eval_compiled_inner(inner, data, vars, ctx, shape)?;
927            match val {
928                JValue::Number(n) => Ok(JValue::Number(-n)),
929                JValue::Null => Ok(JValue::Null),
930                // Undefined operand propagates through unary minus, matching the tree-walker.
931                v if v.is_undefined() => Ok(JValue::Undefined),
932                _ => Err(EvaluatorError::TypeError(
933                    "D1002: Cannot negate non-number value".to_string(),
934                )),
935            }
936        }
937
938        // ── Conditional ─────────────────────────────────────────────────
939        CompiledExpr::Conditional {
940            condition,
941            then_expr,
942            else_expr,
943        } => {
944            let cond = eval_compiled_inner(condition, data, vars, ctx, shape)?;
945            if compiled_is_truthy(&cond) {
946                eval_compiled_inner(then_expr, data, vars, ctx, shape)
947            } else if let Some(else_e) = else_expr {
948                eval_compiled_inner(else_e, data, vars, ctx, shape)
949            } else {
950                Ok(JValue::Undefined)
951            }
952        }
953
954        // ── Object construction ─────────────────────────────────────────
955        CompiledExpr::ObjectConstruct(fields) => {
956            let mut result = IndexMap::with_capacity(fields.len());
957            for (key, expr) in fields {
958                let value = eval_compiled_inner(expr, data, vars, ctx, shape)?;
959                if !value.is_undefined() {
960                    result.insert(key.clone(), value);
961                }
962            }
963            Ok(JValue::object(result))
964        }
965
966        // ── Array construction ──────────────────────────────────────────
967        CompiledExpr::ArrayConstruct(elems) => {
968            let mut result = Vec::new();
969            for (elem_expr, is_nested) in elems {
970                let value = eval_compiled_inner(elem_expr, data, vars, ctx, shape)?;
971                // Undefined values are excluded from array constructors (tree-walker parity)
972                if value.is_undefined() {
973                    continue;
974                }
975                if *is_nested {
976                    // Explicit array constructor [...] — keep nested even if it's an array
977                    result.push(value);
978                } else if let JValue::Array(arr) = value {
979                    // Non-constructor that evaluated to an array — flatten one level
980                    result.extend(arr.iter().cloned());
981                } else {
982                    result.push(value);
983                }
984            }
985            Ok(JValue::array(result))
986        }
987
988        // ── Phase 2 new variants ─────────────────────────────────────────
989
990        // ContextVar: named variable lookup from context scope.
991        // In top-level mode (ctx=None, no bindings), returns Undefined.
992        // In HOF mode, ctx is None too (HOF call sites pass no ctx), so this
993        // is only ever populated for top-level calls — always Undefined there.
994        CompiledExpr::ContextVar(name) => {
995            // Check vars map first (for lambda params that might shadow context)
996            if let Some(vars) = vars {
997                if let Some(val) = vars.get(name.as_str()) {
998                    return Ok((*val).clone());
999                }
1000            }
1001            // Then check context scope
1002            if let Some(ctx) = ctx {
1003                if let Some(val) = ctx.lookup(name) {
1004                    return Ok(val.clone());
1005                }
1006            }
1007            Ok(JValue::Undefined)
1008        }
1009
1010        // FieldPath: multi-step field access with implicit array mapping.
1011        CompiledExpr::FieldPath(steps) => compiled_eval_field_path(steps, data, vars, ctx, shape),
1012
1013        // BuiltinCall: evaluate all args, dispatch to pure builtin.
1014        CompiledExpr::BuiltinCall { name, args } => {
1015            let mut evaled_args = Vec::with_capacity(args.len());
1016            for arg in args.iter() {
1017                evaled_args.push(eval_compiled_inner(arg, data, vars, ctx, shape)?);
1018            }
1019            call_pure_builtin(name, &evaled_args, data)
1020        }
1021
1022        // Block: evaluate each expression in sequence, return the last value.
1023        CompiledExpr::Block(exprs) => {
1024            let mut result = JValue::Undefined;
1025            for expr in exprs.iter() {
1026                result = eval_compiled_inner(expr, data, vars, ctx, shape)?;
1027            }
1028            Ok(result)
1029        }
1030
1031        // Coalesce (`??`): return lhs unless it is Undefined; null IS a valid value.
1032        // JSONata spec: "returns the RHS operand if the LHS operand evaluates to undefined".
1033        CompiledExpr::Coalesce(lhs, rhs) => {
1034            let left = eval_compiled_inner(lhs, data, vars, ctx, shape)?;
1035            if left.is_undefined() {
1036                eval_compiled_inner(rhs, data, vars, ctx, shape)
1037            } else {
1038                Ok(left)
1039            }
1040        }
1041
1042        // ── Higher-order functions ─────────────────────────────────────────────
1043        //
1044        // These variants are emitted by try_compile_hof_expr when the HOF argument
1045        // is an inline lambda literal with a compilable body. Outer vars are merged
1046        // with the lambda params so that nested HOF can access variables from
1047        // enclosing lambda scopes (e.g. `$map(a, function($x) { $map(b, function($y) { $x + $y }) })`).
1048        CompiledExpr::MapCall {
1049            array,
1050            params,
1051            body,
1052        } => {
1053            let arr_val = eval_compiled_inner(array, data, vars, ctx, shape)?;
1054            let single_holder;
1055            let items: &[JValue] = match &arr_val {
1056                JValue::Array(a) => a.as_slice(),
1057                JValue::Undefined => return Ok(JValue::Undefined),
1058                other => {
1059                    single_holder = [other.clone()];
1060                    &single_holder[..]
1061                }
1062            };
1063            let mut result = Vec::with_capacity(items.len());
1064            let p0 = params.first().map(|s| s.as_str());
1065
1066            if let Some(p1) = params.get(1).map(|s| s.as_str()) {
1067                // 2-param lambda (element + index): build per-iteration because idx_val
1068                // is loop-local and cannot outlive the iteration.
1069                for (idx, item) in items.iter().enumerate() {
1070                    let idx_val = JValue::Number(idx as f64);
1071                    let mut call_vars = clone_outer_vars(vars, 2);
1072                    if let Some(p) = p0 {
1073                        call_vars.insert(p, item);
1074                    }
1075                    call_vars.insert(p1, &idx_val);
1076                    let mapped = eval_compiled_inner(body, data, Some(&call_vars), ctx, shape)?;
1077                    if !mapped.is_undefined() {
1078                        result.push(mapped);
1079                    }
1080                }
1081            } else if let Some(p0) = p0 {
1082                // 1-param lambda (most common): build HashMap once, update element ref each iteration.
1083                let mut call_vars = clone_outer_vars(vars, 1);
1084                for item in items.iter() {
1085                    call_vars.insert(p0, item);
1086                    let mapped = eval_compiled_inner(body, data, Some(&call_vars), ctx, shape)?;
1087                    if !mapped.is_undefined() {
1088                        result.push(mapped);
1089                    }
1090                }
1091            }
1092            Ok(if result.is_empty() {
1093                JValue::Undefined
1094            } else {
1095                JValue::array(result)
1096            })
1097        }
1098
1099        CompiledExpr::FilterCall {
1100            array,
1101            params,
1102            body,
1103        } => {
1104            let arr_val = eval_compiled_inner(array, data, vars, ctx, shape)?;
1105            if arr_val.is_undefined() || arr_val.is_null() {
1106                return Ok(JValue::Undefined);
1107            }
1108            let single_holder;
1109            let (items, was_single) = match &arr_val {
1110                JValue::Array(a) => (a.as_slice(), false),
1111                other => {
1112                    single_holder = [other.clone()];
1113                    (&single_holder[..], true)
1114                }
1115            };
1116            let mut result = Vec::with_capacity(items.len() / 2);
1117            let p0 = params.first().map(|s| s.as_str());
1118
1119            if let Some(p1) = params.get(1).map(|s| s.as_str()) {
1120                for (idx, item) in items.iter().enumerate() {
1121                    let idx_val = JValue::Number(idx as f64);
1122                    let mut call_vars = clone_outer_vars(vars, 2);
1123                    if let Some(p) = p0 {
1124                        call_vars.insert(p, item);
1125                    }
1126                    call_vars.insert(p1, &idx_val);
1127                    let pred = eval_compiled_inner(body, data, Some(&call_vars), ctx, shape)?;
1128                    if compiled_is_truthy(&pred) {
1129                        result.push(item.clone());
1130                    }
1131                }
1132            } else if let Some(p0) = p0 {
1133                let mut call_vars = clone_outer_vars(vars, 1);
1134                for item in items.iter() {
1135                    call_vars.insert(p0, item);
1136                    let pred = eval_compiled_inner(body, data, Some(&call_vars), ctx, shape)?;
1137                    if compiled_is_truthy(&pred) {
1138                        result.push(item.clone());
1139                    }
1140                }
1141            }
1142            if was_single {
1143                Ok(match result.len() {
1144                    0 => JValue::Undefined,
1145                    1 => result.remove(0),
1146                    _ => JValue::array(result),
1147                })
1148            } else {
1149                Ok(JValue::array(result))
1150            }
1151        }
1152
1153        CompiledExpr::ReduceCall {
1154            array,
1155            params,
1156            body,
1157            initial,
1158        } => {
1159            let arr_val = eval_compiled_inner(array, data, vars, ctx, shape)?;
1160            let single_holder;
1161            let items: &[JValue] = match &arr_val {
1162                JValue::Array(a) => a.as_slice(),
1163                JValue::Null => return Ok(JValue::Null),
1164                JValue::Undefined => return Ok(JValue::Undefined),
1165                other => {
1166                    single_holder = [other.clone()];
1167                    &single_holder[..]
1168                }
1169            };
1170            let (start_idx, mut accumulator) = if let Some(init_expr) = initial {
1171                let init_val = eval_compiled_inner(init_expr, data, vars, ctx, shape)?;
1172                if items.is_empty() {
1173                    return Ok(init_val);
1174                }
1175                (0usize, init_val)
1176            } else {
1177                if items.is_empty() {
1178                    return Ok(JValue::Null);
1179                }
1180                (1, items[0].clone())
1181            };
1182            let acc_param = params[0].as_str();
1183            let item_param = params[1].as_str();
1184            for item in items[start_idx..].iter() {
1185                // Per-iteration HashMap: &accumulator borrow must be released before we
1186                // can reassign `accumulator`. `drop(call_vars)` ends the borrow.
1187                let mut call_vars = clone_outer_vars(vars, 2);
1188                call_vars.insert(acc_param, &accumulator);
1189                call_vars.insert(item_param, item);
1190                let new_acc = eval_compiled_inner(body, data, Some(&call_vars), ctx, shape)?;
1191                drop(call_vars);
1192                accumulator = new_acc;
1193            }
1194            Ok(accumulator)
1195        }
1196    }
1197}
1198
1199/// Truthiness check (matches JSONata semantics). Standalone function for compiled path.
1200#[inline]
1201pub(crate) fn compiled_is_truthy(value: &JValue) -> bool {
1202    match value {
1203        JValue::Null | JValue::Undefined => false,
1204        JValue::Bool(b) => *b,
1205        JValue::Number(n) => *n != 0.0,
1206        JValue::String(s) => !s.is_empty(),
1207        JValue::Array(a) => !a.is_empty(),
1208        JValue::Object(o) => !o.is_empty(),
1209        _ => false,
1210    }
1211}
1212
1213/// Returns true if the compiled expression is a literal `null` (from `AstNode::Null`).
1214/// Used to replicate the tree-walker's `explicit_null` flag in comparisons/arithmetic.
1215#[inline]
1216fn is_compiled_explicit_null(expr: &CompiledExpr) -> bool {
1217    matches!(expr, CompiledExpr::ExplicitNull)
1218}
1219
1220/// Ordered comparison for compiled expressions.
1221/// Mirrors the tree-walker's `ordered_compare` including explicit-null semantics.
1222#[inline]
1223pub(crate) fn compiled_ordered_cmp(
1224    left: &JValue,
1225    right: &JValue,
1226    left_is_explicit_null: bool,
1227    right_is_explicit_null: bool,
1228    cmp_num: fn(f64, f64) -> bool,
1229    cmp_str: fn(&str, &str) -> bool,
1230) -> Result<JValue, EvaluatorError> {
1231    match (left, right) {
1232        (JValue::Number(a), JValue::Number(b)) => Ok(JValue::Bool(cmp_num(*a, *b))),
1233        (JValue::String(a), JValue::String(b)) => Ok(JValue::Bool(cmp_str(a, b))),
1234        // Both null/undefined → undefined
1235        (JValue::Null, JValue::Null) | (JValue::Undefined, JValue::Undefined) => Ok(JValue::Null),
1236        (JValue::Undefined, JValue::Null) | (JValue::Null, JValue::Undefined) => Ok(JValue::Null),
1237        // Explicit null literal with any non-null type → T2010 error
1238        (JValue::Null, _) if left_is_explicit_null => Err(EvaluatorError::EvaluationError(
1239            "T2010: Type mismatch in comparison".to_string(),
1240        )),
1241        (_, JValue::Null) if right_is_explicit_null => Err(EvaluatorError::EvaluationError(
1242            "T2010: Type mismatch in comparison".to_string(),
1243        )),
1244        // Boolean with undefined → T2010 error
1245        (JValue::Bool(_), JValue::Null | JValue::Undefined)
1246        | (JValue::Null | JValue::Undefined, JValue::Bool(_)) => Err(
1247            EvaluatorError::EvaluationError("T2010: Type mismatch in comparison".to_string()),
1248        ),
1249        // Number or String with implicit undefined (missing field) → undefined result
1250        (JValue::Number(_) | JValue::String(_), JValue::Null | JValue::Undefined)
1251        | (JValue::Null | JValue::Undefined, JValue::Number(_) | JValue::String(_)) => {
1252            Ok(JValue::Null)
1253        }
1254        // Type mismatch (string vs number)
1255        (JValue::String(_), JValue::Number(_)) | (JValue::Number(_), JValue::String(_)) => {
1256            Err(EvaluatorError::EvaluationError(
1257                "T2009: The expressions on either side of operator must be of the same data type"
1258                    .to_string(),
1259            ))
1260        }
1261        _ => Err(EvaluatorError::EvaluationError(
1262            "T2010: Type mismatch in comparison".to_string(),
1263        )),
1264    }
1265}
1266
1267/// Arithmetic for compiled expressions.
1268/// Mirrors the tree-walker's arithmetic functions including explicit-null semantics.
1269#[inline]
1270pub(crate) fn compiled_arithmetic(
1271    op: CompiledArithOp,
1272    left: &JValue,
1273    right: &JValue,
1274    left_is_explicit_null: bool,
1275    right_is_explicit_null: bool,
1276) -> Result<JValue, EvaluatorError> {
1277    let op_sym = match op {
1278        CompiledArithOp::Add => "+",
1279        CompiledArithOp::Sub => "-",
1280        CompiledArithOp::Mul => "*",
1281        CompiledArithOp::Div => "/",
1282        CompiledArithOp::Mod => "%",
1283    };
1284    match (left, right) {
1285        (JValue::Number(a), JValue::Number(b)) => {
1286            let result = match op {
1287                CompiledArithOp::Add => *a + *b,
1288                CompiledArithOp::Sub => *a - *b,
1289                CompiledArithOp::Mul => {
1290                    let r = *a * *b;
1291                    if r.is_infinite() {
1292                        return Err(EvaluatorError::EvaluationError(
1293                            "D1001: Number out of range".to_string(),
1294                        ));
1295                    }
1296                    r
1297                }
1298                CompiledArithOp::Div => {
1299                    if *b == 0.0 {
1300                        return Err(EvaluatorError::EvaluationError(
1301                            "Division by zero".to_string(),
1302                        ));
1303                    }
1304                    *a / *b
1305                }
1306                CompiledArithOp::Mod => {
1307                    if *b == 0.0 {
1308                        return Err(EvaluatorError::EvaluationError(
1309                            "Division by zero".to_string(),
1310                        ));
1311                    }
1312                    *a % *b
1313                }
1314            };
1315            Ok(JValue::Number(result))
1316        }
1317        // Explicit null literal → T2002 error (matching tree-walker behavior)
1318        (JValue::Null | JValue::Undefined, _) if left_is_explicit_null => {
1319            Err(EvaluatorError::TypeError(format!(
1320                "T2002: The left side of the {} operator must evaluate to a number",
1321                op_sym
1322            )))
1323        }
1324        (_, JValue::Null | JValue::Undefined) if right_is_explicit_null => {
1325            Err(EvaluatorError::TypeError(format!(
1326                "T2002: The right side of the {} operator must evaluate to a number",
1327                op_sym
1328            )))
1329        }
1330        // Implicit undefined propagation (from missing field) → undefined result
1331        (JValue::Null | JValue::Undefined, _) | (_, JValue::Null | JValue::Undefined) => {
1332            Ok(JValue::Null)
1333        }
1334        _ => Err(EvaluatorError::TypeError(format!(
1335            "Cannot apply {} to {:?} and {:?}",
1336            op_sym, left, right
1337        ))),
1338    }
1339}
1340
1341/// Convert a value to string for concatenation in compiled expressions.
1342#[inline]
1343pub(crate) fn compiled_to_concat_string(value: &JValue) -> Result<String, EvaluatorError> {
1344    match value {
1345        JValue::String(s) => Ok(s.to_string()),
1346        JValue::Null | JValue::Undefined => Ok(String::new()),
1347        JValue::Number(_) | JValue::Bool(_) | JValue::Array(_) | JValue::Object(_) => {
1348            match crate::functions::string::string(value, None) {
1349                Ok(JValue::String(s)) => Ok(s.to_string()),
1350                Ok(JValue::Null) => Ok(String::new()),
1351                _ => Err(EvaluatorError::TypeError(
1352                    "Cannot concatenate complex types".to_string(),
1353                )),
1354            }
1355        }
1356        _ => Ok(String::new()),
1357    }
1358}
1359
1360/// Equality comparison for the bytecode VM.
1361#[inline]
1362pub(crate) fn compiled_equal(lhs: &JValue, rhs: &JValue) -> JValue {
1363    JValue::Bool(crate::functions::array::values_equal(lhs, rhs))
1364}
1365
1366/// String concatenation for the bytecode VM.
1367#[inline]
1368pub(crate) fn compiled_concat(lhs: JValue, rhs: JValue) -> Result<JValue, EvaluatorError> {
1369    let l = compiled_to_concat_string(&lhs)?;
1370    let r = compiled_to_concat_string(&rhs)?;
1371    Ok(JValue::string(l + &r))
1372}
1373
1374/// Entry point for the bytecode VM to call pure builtins by name.
1375#[inline]
1376pub(crate) fn call_pure_builtin_by_name(
1377    name: &str,
1378    args: &[JValue],
1379    data: &JValue,
1380) -> Result<JValue, EvaluatorError> {
1381    call_pure_builtin(name, args, data)
1382}
1383
1384// ──────────────────────────────────────────────────────────────────────────────
1385// Phase 2: path compilation, builtin dispatch, and supporting helpers
1386// ──────────────────────────────────────────────────────────────────────────────
1387
1388/// Compile a `Path { steps }` AstNode into a `CompiledExpr`.
1389///
1390/// Handles paths like `a.b.c`, `a[pred].b`, `$var.field`.
1391/// Returns `None` if any step is not compilable (e.g. wildcards, function apps).
1392fn try_compile_path(
1393    steps: &[crate::ast::PathStep],
1394    allowed_vars: Option<&[&str]>,
1395) -> Option<CompiledExpr> {
1396    use crate::ast::{AstNode, Stage};
1397
1398    if steps.is_empty() {
1399        return None;
1400    }
1401
1402    // Determine the start of the path:
1403    //   `$.field...`  → starts from current data (drop the leading `$` step)
1404    //   `$var.field`  → variable-prefixed paths: not compiled yet, fall back to tree-walker
1405    //   `field...`    → starts from current data
1406    let field_steps: &[crate::ast::PathStep] = match &steps[0].node {
1407        AstNode::Variable(var) if var.is_empty() && steps[0].stages.is_empty() => &steps[1..],
1408        AstNode::Variable(_) => return None,
1409        AstNode::Name(_) => steps,
1410        _ => return None,
1411    };
1412
1413    // Compile a boolean filter predicate, rejecting numeric predicates (`[0]`, `[1]`)
1414    // which represent index access in JSONata, not boolean filtering.
1415    let compile_filter = |node: &AstNode| -> Option<CompiledExpr> {
1416        if matches!(node, AstNode::Number(_)) {
1417            return None;
1418        }
1419        try_compile_expr_inner(node, allowed_vars)
1420    };
1421
1422    // Compile each field step.
1423    // Handles:
1424    //   - Name nodes with at most one Stage::Filter attached (from `a.b[pred]` dot-path parsing)
1425    //   - Predicate nodes (from `products[pred]` standalone predicate parsing) — folded into the
1426    //     previous step's filter slot, since both encodings have identical runtime semantics.
1427    let mut compiled_steps = Vec::with_capacity(field_steps.len());
1428    for step in field_steps {
1429        match &step.node {
1430            AstNode::Name(name) => {
1431                let filter = match step.stages.as_slice() {
1432                    [] => None,
1433                    [Stage::Filter(filter_node)] => Some(compile_filter(filter_node)?),
1434                    _ => return None,
1435                };
1436                compiled_steps.push(CompiledStep {
1437                    field: name.clone(),
1438                    filter,
1439                });
1440            }
1441            AstNode::Predicate(filter_node) => {
1442                // Standalone predicate step — fold into the previous Name step's filter slot.
1443                if !step.stages.is_empty() {
1444                    return None;
1445                }
1446                let last = compiled_steps.last_mut()?;
1447                if last.filter.is_some() {
1448                    return None;
1449                }
1450                last.filter = Some(compile_filter(filter_node)?);
1451            }
1452            _ => return None,
1453        }
1454    }
1455
1456    if compiled_steps.is_empty() {
1457        // Bare `$` with no further field steps — current-data reference
1458        return Some(CompiledExpr::VariableLookup(String::new()));
1459    }
1460
1461    // Shape-cache optimizations (FieldLookup / NestedFieldLookup) are only safe
1462    // in HOF mode (allowed_vars=Some), where data is always a single Object element
1463    // from an array. In top-level mode (allowed_vars=None), data can itself be an
1464    // Array, so we must use FieldPath which applies implicit array-mapping semantics.
1465    if allowed_vars.is_some() {
1466        if compiled_steps.len() == 1 && compiled_steps[0].filter.is_none() {
1467            return Some(CompiledExpr::FieldLookup(compiled_steps.remove(0).field));
1468        }
1469        if compiled_steps.len() == 2
1470            && compiled_steps[0].filter.is_none()
1471            && compiled_steps[1].filter.is_none()
1472        {
1473            let outer = compiled_steps.remove(0).field;
1474            let inner = compiled_steps.remove(0).field;
1475            return Some(CompiledExpr::NestedFieldLookup(outer, inner));
1476        }
1477    }
1478
1479    Some(CompiledExpr::FieldPath(compiled_steps))
1480}
1481
1482/// Evaluate a compiled `FieldPath` against `data`.
1483///
1484/// Applies implicit array-mapping semantics at each step (matching the tree-walker).
1485/// Filters are applied as predicates: truthy elements are kept.
1486///
1487/// Singleton unwrapping mirrors the tree-walker's `did_array_mapping` rule:
1488/// - Extracting a field from an *array* sets the mapping flag (unwrap singletons at end).
1489/// - Extracting a field from a *single object* resets the flag (preserve the raw value).
1490fn compiled_eval_field_path(
1491    steps: &[CompiledStep],
1492    data: &JValue,
1493    vars: Option<&HashMap<&str, &JValue>>,
1494    ctx: Option<&Context>,
1495    shape: Option<&ShapeCache>,
1496) -> Result<JValue, EvaluatorError> {
1497    let mut current = data.clone();
1498    // Track whether the most recent field step mapped over an array (like the tree-walker's
1499    // `did_array_mapping` flag). Filters also count as array operations.
1500    let mut did_array_mapping = false;
1501    for step in steps {
1502        // Determine if this step will do array mapping before we overwrite `current`
1503        let is_array = matches!(current, JValue::Array(_));
1504        // Field access with implicit array mapping
1505        current = compiled_field_step(&step.field, &current);
1506        if is_array {
1507            did_array_mapping = true;
1508        } else {
1509            // Extracting from a single object resets the flag (tree-walker parity)
1510            did_array_mapping = false;
1511        }
1512        // Apply filter if present (filter is an array operation — keep the flag set)
1513        if let Some(filter) = &step.filter {
1514            current = compiled_apply_filter(filter, &current, vars, ctx, shape)?;
1515            // Filter always implies we operated on an array
1516            did_array_mapping = true;
1517        }
1518    }
1519    // Singleton unwrapping: only when array-mapping occurred, matching tree-walker.
1520    if did_array_mapping {
1521        Ok(match current {
1522            JValue::Array(ref arr) if arr.len() == 1 => arr[0].clone(),
1523            other => other,
1524        })
1525    } else {
1526        Ok(current)
1527    }
1528}
1529
1530/// Perform a single-field access with implicit array-mapping semantics.
1531///
1532/// - Object: look up `field`, return its value or Undefined
1533/// - Array: map field extraction over each element, flatten nested arrays, skip Undefined
1534/// - Tuple objects (`__tuple__: true`): look up in the `@` inner object
1535/// - Other: Undefined
1536fn compiled_field_step(field: &str, value: &JValue) -> JValue {
1537    match value {
1538        JValue::Object(obj) => {
1539            // Check for tuple: extract from "@" inner object
1540            if obj.get("__tuple__") == Some(&JValue::Bool(true)) {
1541                if let Some(JValue::Object(inner)) = obj.get("@") {
1542                    return inner.get(field).cloned().unwrap_or(JValue::Undefined);
1543                }
1544                return JValue::Undefined;
1545            }
1546            obj.get(field).cloned().unwrap_or(JValue::Undefined)
1547        }
1548        JValue::Array(arr) => {
1549            // Build shape cache from first plain (non-tuple) object for O(1) positional access.
1550            let shape: Option<ShapeCache> = arr.iter().find_map(|v| {
1551                if let JValue::Object(obj) = v {
1552                    if obj.get("__tuple__") != Some(&JValue::Bool(true)) {
1553                        return build_shape_cache(v);
1554                    }
1555                }
1556                None
1557            });
1558            let mut result = Vec::new();
1559            for item in arr.iter() {
1560                let extracted = if let (Some(ref sh), JValue::Object(obj)) = (&shape, item) {
1561                    // Tuple objects need the recursive path for "@" inner lookup.
1562                    if obj.get("__tuple__") == Some(&JValue::Bool(true)) {
1563                        compiled_field_step(field, item)
1564                    } else if let Some(&pos) = sh.get(field) {
1565                        // Positional access with key verification: guards against heterogeneous
1566                        // schemas (objects where the same field is at a different index).
1567                        // On a mismatch, fall back to a regular hash lookup.
1568                        match obj.get_index(pos) {
1569                            Some((k, v)) if k.as_str() == field => v.clone(),
1570                            _ => obj.get(field).cloned().unwrap_or(JValue::Undefined),
1571                        }
1572                    } else {
1573                        // Field not in the first object's schema — fall back to hash lookup
1574                        // so that heterogeneous arrays (e.g. [{a:1},{b:2}]) are handled correctly.
1575                        obj.get(field).cloned().unwrap_or(JValue::Undefined)
1576                    }
1577                } else {
1578                    compiled_field_step(field, item)
1579                };
1580                match extracted {
1581                    JValue::Undefined => {}
1582                    JValue::Array(inner) => result.extend(inner.iter().cloned()),
1583                    other => result.push(other),
1584                }
1585            }
1586            if result.is_empty() {
1587                JValue::Undefined
1588            } else {
1589                JValue::array(result)
1590            }
1591        }
1592        _ => JValue::Undefined,
1593    }
1594}
1595
1596/// Apply a compiled filter predicate to a value.
1597///
1598/// - Array: return elements for which the predicate is truthy
1599/// - Single value: return it if predicate is truthy, else Undefined
1600/// - Numeric predicates (index access) are NOT supported here — fall back via None compilation
1601fn compiled_apply_filter(
1602    filter: &CompiledExpr,
1603    value: &JValue,
1604    vars: Option<&HashMap<&str, &JValue>>,
1605    ctx: Option<&Context>,
1606    shape: Option<&ShapeCache>,
1607) -> Result<JValue, EvaluatorError> {
1608    match value {
1609        JValue::Array(arr) => {
1610            let mut result = Vec::new();
1611            // Auto-build shape cache from first element when not provided.
1612            // Avoids per-element hash lookups in the filter predicate for homogeneous arrays.
1613            let local_shape: Option<ShapeCache> = if shape.is_none() {
1614                arr.first().and_then(build_shape_cache)
1615            } else {
1616                None
1617            };
1618            let effective_shape = shape.or(local_shape.as_ref());
1619            for item in arr.iter() {
1620                let pred = eval_compiled_inner(filter, item, vars, ctx, effective_shape)?;
1621                if compiled_is_truthy(&pred) {
1622                    result.push(item.clone());
1623                }
1624            }
1625            if result.is_empty() {
1626                Ok(JValue::Undefined)
1627            } else if result.len() == 1 {
1628                Ok(result.remove(0))
1629            } else {
1630                Ok(JValue::array(result))
1631            }
1632        }
1633        JValue::Undefined => Ok(JValue::Undefined),
1634        _ => {
1635            let pred = eval_compiled_inner(filter, value, vars, ctx, shape)?;
1636            if compiled_is_truthy(&pred) {
1637                Ok(value.clone())
1638            } else {
1639                Ok(JValue::Undefined)
1640            }
1641        }
1642    }
1643}
1644
1645/// Dispatch a pure builtin function call.
1646///
1647/// Replicates the tree-walker's evaluation for the subset of builtins in
1648/// `COMPILABLE_BUILTINS`: no side effects, no lambdas, no context mutations.
1649/// `data` is the current context value for implicit-argument insertion.
1650fn call_pure_builtin(name: &str, args: &[JValue], data: &JValue) -> Result<JValue, EvaluatorError> {
1651    use crate::functions;
1652
1653    // Apply implicit context insertion matching the tree-walker
1654    let args_storage: Vec<JValue>;
1655    let effective_args: &[JValue] = if args.is_empty() {
1656        match name {
1657            "string" => {
1658                // $string() with a null/undefined context returns undefined, not "null".
1659                // This mirrors the tree-walker's special case at the function-call site.
1660                if data.is_undefined() || data.is_null() {
1661                    return Ok(JValue::Undefined);
1662                }
1663                args_storage = vec![data.clone()];
1664                &args_storage
1665            }
1666            "number" | "boolean" | "uppercase" | "lowercase" => {
1667                args_storage = vec![data.clone()];
1668                &args_storage
1669            }
1670            _ => args,
1671        }
1672    } else if args.len() == 1 {
1673        match name {
1674            "substringBefore" | "substringAfter" | "contains" | "split" => {
1675                if matches!(data, JValue::String(_)) {
1676                    args_storage = std::iter::once(data.clone())
1677                        .chain(args.iter().cloned())
1678                        .collect();
1679                    &args_storage
1680                } else {
1681                    args
1682                }
1683            }
1684            _ => args,
1685        }
1686    } else {
1687        args
1688    };
1689
1690    // Apply undefined propagation: if the first effective argument is Undefined
1691    // and the function propagates undefined, return Undefined immediately.
1692    // This matches the tree-walker's `propagates_undefined` check.
1693    if effective_args.first().is_some_and(JValue::is_undefined) && propagates_undefined(name) {
1694        return Ok(JValue::Undefined);
1695    }
1696
1697    match name {
1698        // ── String functions ────────────────────────────────────────────
1699        "string" => {
1700            // Validate the optional prettify argument: must be a boolean.
1701            let prettify = match effective_args.get(1) {
1702                None => None,
1703                Some(JValue::Bool(b)) => Some(*b),
1704                Some(_) => {
1705                    return Err(EvaluatorError::TypeError(
1706                        "string() prettify parameter must be a boolean".to_string(),
1707                    ))
1708                }
1709            };
1710            let arg = effective_args.first().unwrap_or(&JValue::Null);
1711            Ok(functions::string::string(arg, prettify)?)
1712        }
1713        "length" => match effective_args.first() {
1714            Some(JValue::String(s)) => Ok(functions::string::length(s)?),
1715            // Undefined input propagates (caught above by the undefined-propagation guard).
1716            Some(JValue::Undefined) => Ok(JValue::Undefined),
1717            // No argument: mirrors tree-walker "requires exactly 1 argument" (no error code,
1718            // so the test framework accepts it against any expected T-code).
1719            None => Err(EvaluatorError::EvaluationError(
1720                "length() requires exactly 1 argument".to_string(),
1721            )),
1722            // null and any other non-string type → T0410
1723            _ => Err(EvaluatorError::TypeError(
1724                "T0410: Argument 1 of function length does not match function signature"
1725                    .to_string(),
1726            )),
1727        },
1728        "uppercase" => match effective_args.first() {
1729            Some(JValue::String(s)) => Ok(functions::string::uppercase(s)?),
1730            Some(JValue::Undefined) | None => Ok(JValue::Undefined),
1731            _ => Err(EvaluatorError::TypeError(
1732                "T0410: Argument 1 of function uppercase does not match function signature"
1733                    .to_string(),
1734            )),
1735        },
1736        "lowercase" => match effective_args.first() {
1737            Some(JValue::String(s)) => Ok(functions::string::lowercase(s)?),
1738            Some(JValue::Undefined) | None => Ok(JValue::Undefined),
1739            _ => Err(EvaluatorError::TypeError(
1740                "T0410: Argument 1 of function lowercase does not match function signature"
1741                    .to_string(),
1742            )),
1743        },
1744        "trim" => match effective_args.first() {
1745            None | Some(JValue::Null | JValue::Undefined) => Ok(JValue::Null),
1746            Some(JValue::String(s)) => Ok(functions::string::trim(s)?),
1747            _ => Err(EvaluatorError::TypeError(
1748                "trim() requires a string argument".to_string(),
1749            )),
1750        },
1751        "substring" => {
1752            if effective_args.len() < 2 {
1753                return Err(EvaluatorError::EvaluationError(
1754                    "substring() requires at least 2 arguments".to_string(),
1755                ));
1756            }
1757            match (&effective_args[0], &effective_args[1]) {
1758                (JValue::String(s), JValue::Number(start)) => {
1759                    // Optional 3rd arg (length) must be a number if provided.
1760                    let length = match effective_args.get(2) {
1761                        None => None,
1762                        Some(JValue::Number(l)) => Some(*l as i64),
1763                        Some(_) => {
1764                            return Err(EvaluatorError::TypeError(
1765                                "T0410: Argument 3 of function substring does not match function signature"
1766                                    .to_string(),
1767                            ))
1768                        }
1769                    };
1770                    Ok(functions::string::substring(s, *start as i64, length)?)
1771                }
1772                _ => Err(EvaluatorError::TypeError(
1773                    "T0410: Argument 1 of function substring does not match function signature"
1774                        .to_string(),
1775                )),
1776            }
1777        }
1778        "substringBefore" => {
1779            if effective_args.len() != 2 {
1780                return Err(EvaluatorError::TypeError(
1781                    "T0411: Context value is not a compatible type with argument 2 of function substringBefore".to_string(),
1782                ));
1783            }
1784            match (&effective_args[0], &effective_args[1]) {
1785                (JValue::String(s), JValue::String(sep)) => {
1786                    Ok(functions::string::substring_before(s, sep)?)
1787                }
1788                // Undefined propagates; null is a type error.
1789                (JValue::Undefined, _) => Ok(JValue::Undefined),
1790                _ => Err(EvaluatorError::TypeError(
1791                    "T0410: Argument 1 of function substringBefore does not match function signature".to_string(),
1792                )),
1793            }
1794        }
1795        "substringAfter" => {
1796            if effective_args.len() != 2 {
1797                return Err(EvaluatorError::TypeError(
1798                    "T0411: Context value is not a compatible type with argument 2 of function substringAfter".to_string(),
1799                ));
1800            }
1801            match (&effective_args[0], &effective_args[1]) {
1802                (JValue::String(s), JValue::String(sep)) => {
1803                    Ok(functions::string::substring_after(s, sep)?)
1804                }
1805                // Undefined propagates; null is a type error.
1806                (JValue::Undefined, _) => Ok(JValue::Undefined),
1807                _ => Err(EvaluatorError::TypeError(
1808                    "T0410: Argument 1 of function substringAfter does not match function signature".to_string(),
1809                )),
1810            }
1811        }
1812        "contains" => {
1813            if effective_args.len() != 2 {
1814                return Err(EvaluatorError::EvaluationError(
1815                    "contains() requires exactly 2 arguments".to_string(),
1816                ));
1817            }
1818            match &effective_args[0] {
1819                JValue::Null | JValue::Undefined => Ok(JValue::Null),
1820                JValue::String(s) => Ok(functions::string::contains(s, &effective_args[1])?),
1821                _ => Err(EvaluatorError::TypeError(
1822                    "contains() requires a string as the first argument".to_string(),
1823                )),
1824            }
1825        }
1826        "split" => {
1827            if effective_args.len() < 2 {
1828                return Err(EvaluatorError::EvaluationError(
1829                    "split() requires at least 2 arguments".to_string(),
1830                ));
1831            }
1832            match &effective_args[0] {
1833                JValue::Null | JValue::Undefined => Ok(JValue::Null),
1834                JValue::String(s) => {
1835                    // Validate the optional limit argument — must be a positive number.
1836                    let limit = match effective_args.get(2) {
1837                        None => None,
1838                        Some(JValue::Number(n)) => {
1839                            if *n < 0.0 {
1840                                return Err(EvaluatorError::EvaluationError(
1841                                    "D3020: Third argument of split function must be a positive number"
1842                                        .to_string(),
1843                                ));
1844                            }
1845                            Some(n.floor() as usize)
1846                        }
1847                        Some(_) => {
1848                            return Err(EvaluatorError::TypeError(
1849                                "split() limit must be a number".to_string(),
1850                            ))
1851                        }
1852                    };
1853                    Ok(functions::string::split(s, &effective_args[1], limit)?)
1854                }
1855                _ => Err(EvaluatorError::TypeError(
1856                    "split() requires a string as the first argument".to_string(),
1857                )),
1858            }
1859        }
1860        "join" => {
1861            if effective_args.is_empty() {
1862                return Err(EvaluatorError::TypeError(
1863                    "T0410: Argument 1 of function $join does not match function signature"
1864                        .to_string(),
1865                ));
1866            }
1867            match &effective_args[0] {
1868                JValue::Null | JValue::Undefined => Ok(JValue::Null),
1869                // Signature: <a<s>s?:s> — first arg must be an array of strings.
1870                JValue::Bool(_) | JValue::Number(_) | JValue::Object(_) => {
1871                    Err(EvaluatorError::TypeError(
1872                        "T0412: Argument 1 of function $join must be an array of String"
1873                            .to_string(),
1874                    ))
1875                }
1876                JValue::Array(arr) => {
1877                    // All elements must be strings.
1878                    for item in arr.iter() {
1879                        if !matches!(item, JValue::String(_)) {
1880                            return Err(EvaluatorError::TypeError(
1881                                "T0412: Argument 1 of function $join must be an array of String"
1882                                    .to_string(),
1883                            ));
1884                        }
1885                    }
1886                    // Validate separator: must be a string if provided.
1887                    let separator = match effective_args.get(1) {
1888                        None | Some(JValue::Undefined) => None,
1889                        Some(JValue::String(s)) => Some(&**s),
1890                        Some(_) => {
1891                            return Err(EvaluatorError::TypeError(
1892                                "T0410: Argument 2 of function $join does not match function signature (expected String)"
1893                                    .to_string(),
1894                            ))
1895                        }
1896                    };
1897                    Ok(functions::string::join(arr, separator)?)
1898                }
1899                JValue::String(s) => Ok(JValue::String(s.clone())),
1900                _ => Err(EvaluatorError::TypeError(
1901                    "T0412: Argument 1 of function $join must be an array of String".to_string(),
1902                )),
1903            }
1904        }
1905
1906        // ── Numeric functions ───────────────────────────────────────────
1907        "number" => match effective_args.first() {
1908            Some(v) => Ok(functions::numeric::number(v)?),
1909            None => Err(EvaluatorError::EvaluationError(
1910                "number() requires at least 1 argument".to_string(),
1911            )),
1912        },
1913        "floor" => match effective_args.first() {
1914            Some(JValue::Null | JValue::Undefined) | None => Ok(JValue::Null),
1915            Some(JValue::Number(n)) => Ok(functions::numeric::floor(*n)?),
1916            _ => Err(EvaluatorError::TypeError(
1917                "floor() requires a number argument".to_string(),
1918            )),
1919        },
1920        "ceil" => match effective_args.first() {
1921            Some(JValue::Null | JValue::Undefined) | None => Ok(JValue::Null),
1922            Some(JValue::Number(n)) => Ok(functions::numeric::ceil(*n)?),
1923            _ => Err(EvaluatorError::TypeError(
1924                "ceil() requires a number argument".to_string(),
1925            )),
1926        },
1927        "round" => match effective_args.first() {
1928            Some(JValue::Null | JValue::Undefined) | None => Ok(JValue::Null),
1929            Some(JValue::Number(n)) => {
1930                let precision = effective_args.get(1).and_then(|v| {
1931                    if let JValue::Number(p) = v {
1932                        Some(*p as i32)
1933                    } else {
1934                        None
1935                    }
1936                });
1937                Ok(functions::numeric::round(*n, precision)?)
1938            }
1939            _ => Err(EvaluatorError::TypeError(
1940                "round() requires a number argument".to_string(),
1941            )),
1942        },
1943        "abs" => match effective_args.first() {
1944            Some(JValue::Null | JValue::Undefined) | None => Ok(JValue::Null),
1945            Some(JValue::Number(n)) => Ok(functions::numeric::abs(*n)?),
1946            _ => Err(EvaluatorError::TypeError(
1947                "abs() requires a number argument".to_string(),
1948            )),
1949        },
1950        "sqrt" => match effective_args.first() {
1951            Some(JValue::Null | JValue::Undefined) | None => Ok(JValue::Null),
1952            Some(JValue::Number(n)) => Ok(functions::numeric::sqrt(*n)?),
1953            _ => Err(EvaluatorError::TypeError(
1954                "sqrt() requires a number argument".to_string(),
1955            )),
1956        },
1957
1958        // ── Aggregation functions ───────────────────────────────────────
1959        "sum" => match effective_args.first() {
1960            Some(v) if v.is_undefined() => Ok(JValue::Undefined),
1961            None => Err(EvaluatorError::EvaluationError(
1962                "sum() requires exactly 1 argument".to_string(),
1963            )),
1964            Some(JValue::Null) => Ok(JValue::Null),
1965            Some(JValue::Array(arr)) => Ok(aggregation::sum(arr)?),
1966            Some(JValue::Number(n)) => Ok(JValue::Number(*n)),
1967            Some(other) => Ok(functions::numeric::sum(&[other.clone()])?),
1968        },
1969        "max" => match effective_args.first() {
1970            Some(v) if v.is_undefined() => Ok(JValue::Undefined),
1971            Some(JValue::Null) | None => Ok(JValue::Null),
1972            Some(JValue::Array(arr)) => Ok(aggregation::max(arr)?),
1973            Some(v @ JValue::Number(_)) => Ok(v.clone()),
1974            _ => Err(EvaluatorError::TypeError(
1975                "max() requires an array or number argument".to_string(),
1976            )),
1977        },
1978        "min" => match effective_args.first() {
1979            Some(v) if v.is_undefined() => Ok(JValue::Undefined),
1980            Some(JValue::Null) | None => Ok(JValue::Null),
1981            Some(JValue::Array(arr)) => Ok(aggregation::min(arr)?),
1982            Some(v @ JValue::Number(_)) => Ok(v.clone()),
1983            _ => Err(EvaluatorError::TypeError(
1984                "min() requires an array or number argument".to_string(),
1985            )),
1986        },
1987        "average" => match effective_args.first() {
1988            Some(v) if v.is_undefined() => Ok(JValue::Undefined),
1989            Some(JValue::Null) | None => Ok(JValue::Null),
1990            Some(JValue::Array(arr)) => Ok(aggregation::average(arr)?),
1991            Some(v @ JValue::Number(_)) => Ok(v.clone()),
1992            _ => Err(EvaluatorError::TypeError(
1993                "average() requires an array or number argument".to_string(),
1994            )),
1995        },
1996        "count" => match effective_args.first() {
1997            Some(v) if v.is_undefined() => Ok(JValue::from(0i64)),
1998            Some(JValue::Null) | None => Ok(JValue::from(0i64)),
1999            Some(JValue::Array(arr)) => Ok(functions::array::count(arr)?),
2000            _ => Ok(JValue::from(1i64)),
2001        },
2002
2003        // ── Boolean / logic ─────────────────────────────────────────────
2004        "boolean" => match effective_args.first() {
2005            Some(v) => Ok(functions::boolean::boolean(v)?),
2006            None => Err(EvaluatorError::EvaluationError(
2007                "boolean() requires 1 argument".to_string(),
2008            )),
2009        },
2010        "not" => match effective_args.first() {
2011            Some(v) => Ok(JValue::Bool(!compiled_is_truthy(v))),
2012            None => Err(EvaluatorError::EvaluationError(
2013                "not() requires 1 argument".to_string(),
2014            )),
2015        },
2016
2017        // ── Array functions ─────────────────────────────────────────────
2018        "append" => {
2019            if effective_args.len() != 2 {
2020                return Err(EvaluatorError::EvaluationError(
2021                    "append() requires exactly 2 arguments".to_string(),
2022                ));
2023            }
2024            let first = &effective_args[0];
2025            let second = &effective_args[1];
2026            if matches!(second, JValue::Null | JValue::Undefined) {
2027                return Ok(first.clone());
2028            }
2029            if matches!(first, JValue::Null | JValue::Undefined) {
2030                return Ok(second.clone());
2031            }
2032            let arr = match first {
2033                JValue::Array(a) => a.to_vec(),
2034                other => vec![other.clone()],
2035            };
2036            Ok(functions::array::append(&arr, second)?)
2037        }
2038        "reverse" => match effective_args.first() {
2039            Some(JValue::Null | JValue::Undefined) | None => Ok(JValue::Null),
2040            Some(JValue::Array(arr)) => Ok(functions::array::reverse(arr)?),
2041            _ => Err(EvaluatorError::TypeError(
2042                "reverse() requires an array argument".to_string(),
2043            )),
2044        },
2045        "distinct" => match effective_args.first() {
2046            Some(JValue::Null | JValue::Undefined) | None => Ok(JValue::Null),
2047            Some(JValue::Array(arr)) => Ok(functions::array::distinct(arr)?),
2048            _ => Err(EvaluatorError::TypeError(
2049                "distinct() requires an array argument".to_string(),
2050            )),
2051        },
2052
2053        // ── Object functions ────────────────────────────────────────────
2054        "keys" => match effective_args.first() {
2055            Some(JValue::Null | JValue::Undefined) | None => Ok(JValue::Null),
2056            Some(JValue::Lambda { .. } | JValue::Builtin { .. }) => Ok(JValue::Null),
2057            Some(JValue::Object(obj)) => {
2058                if obj.is_empty() {
2059                    Ok(JValue::Null)
2060                } else {
2061                    let keys: Vec<JValue> = obj.keys().map(|k| JValue::string(k.clone())).collect();
2062                    if keys.len() == 1 {
2063                        Ok(keys.into_iter().next().unwrap())
2064                    } else {
2065                        Ok(JValue::array(keys))
2066                    }
2067                }
2068            }
2069            Some(JValue::Array(arr)) => {
2070                let mut all_keys: Vec<JValue> = Vec::new();
2071                for item in arr.iter() {
2072                    if let JValue::Object(obj) = item {
2073                        for key in obj.keys() {
2074                            let k = JValue::string(key.clone());
2075                            if !all_keys.contains(&k) {
2076                                all_keys.push(k);
2077                            }
2078                        }
2079                    }
2080                }
2081                if all_keys.is_empty() {
2082                    Ok(JValue::Null)
2083                } else if all_keys.len() == 1 {
2084                    Ok(all_keys.into_iter().next().unwrap())
2085                } else {
2086                    Ok(JValue::array(all_keys))
2087                }
2088            }
2089            _ => Ok(JValue::Null),
2090        },
2091        "merge" => match effective_args.len() {
2092            0 => Err(EvaluatorError::EvaluationError(
2093                "merge() requires at least 1 argument".to_string(),
2094            )),
2095            1 => match &effective_args[0] {
2096                JValue::Array(arr) => Ok(functions::object::merge(arr)?),
2097                JValue::Null | JValue::Undefined => Ok(JValue::Null),
2098                JValue::Object(_) => Ok(effective_args[0].clone()),
2099                _ => Err(EvaluatorError::TypeError(
2100                    "merge() requires objects or an array of objects".to_string(),
2101                )),
2102            },
2103            _ => Ok(functions::object::merge(effective_args)?),
2104        },
2105
2106        _ => unreachable!(
2107            "call_pure_builtin called with non-compilable builtin: {}",
2108            name
2109        ),
2110    }
2111}
2112
2113// ──────────────────────────────────────────────────────────────────────────────
2114// End of CompiledExpr framework
2115// ──────────────────────────────────────────────────────────────────────────────
2116
2117/// Functions that propagate undefined (return undefined when given an undefined argument).
2118/// These functions should return null/undefined when their input path doesn't exist,
2119/// rather than throwing a type error.
2120const UNDEFINED_PROPAGATING_FUNCTIONS: &[&str] = &[
2121    "not",
2122    "boolean",
2123    "length",
2124    "number",
2125    "uppercase",
2126    "lowercase",
2127    "substring",
2128    "substringBefore",
2129    "substringAfter",
2130    "string",
2131];
2132
2133/// Check whether a function propagates undefined values
2134fn propagates_undefined(name: &str) -> bool {
2135    UNDEFINED_PROPAGATING_FUNCTIONS.contains(&name)
2136}
2137
2138/// Iterator-based numeric aggregation helpers.
2139/// These avoid cloning values by iterating over references and extracting f64 values directly.
2140mod aggregation {
2141    use super::*;
2142
2143    /// Iterate over all numeric values in a potentially nested array, yielding f64 values.
2144    /// Returns Err if any non-numeric value is encountered.
2145    fn for_each_numeric(
2146        arr: &[JValue],
2147        func_name: &str,
2148        mut f: impl FnMut(f64),
2149    ) -> Result<(), EvaluatorError> {
2150        fn recurse(
2151            arr: &[JValue],
2152            func_name: &str,
2153            f: &mut dyn FnMut(f64),
2154        ) -> Result<(), EvaluatorError> {
2155            for value in arr.iter() {
2156                match value {
2157                    JValue::Array(inner) => recurse(inner, func_name, f)?,
2158                    JValue::Number(n) => {
2159                        f(*n);
2160                    }
2161                    _ => {
2162                        return Err(EvaluatorError::TypeError(format!(
2163                            "{}() requires all array elements to be numbers",
2164                            func_name
2165                        )));
2166                    }
2167                }
2168            }
2169            Ok(())
2170        }
2171        recurse(arr, func_name, &mut f)
2172    }
2173
2174    /// Count elements in a potentially nested array without cloning.
2175    fn count_numeric(arr: &[JValue], func_name: &str) -> Result<usize, EvaluatorError> {
2176        let mut count = 0usize;
2177        for_each_numeric(arr, func_name, |_| count += 1)?;
2178        Ok(count)
2179    }
2180
2181    pub fn sum(arr: &[JValue]) -> Result<JValue, EvaluatorError> {
2182        if arr.is_empty() {
2183            return Ok(JValue::from(0i64));
2184        }
2185        let mut total = 0.0f64;
2186        for_each_numeric(arr, "sum", |n| total += n)?;
2187        Ok(JValue::Number(total))
2188    }
2189
2190    pub fn max(arr: &[JValue]) -> Result<JValue, EvaluatorError> {
2191        if arr.is_empty() {
2192            return Ok(JValue::Null);
2193        }
2194        let mut max_val = f64::NEG_INFINITY;
2195        for_each_numeric(arr, "max", |n| {
2196            if n > max_val {
2197                max_val = n;
2198            }
2199        })?;
2200        Ok(JValue::Number(max_val))
2201    }
2202
2203    pub fn min(arr: &[JValue]) -> Result<JValue, EvaluatorError> {
2204        if arr.is_empty() {
2205            return Ok(JValue::Null);
2206        }
2207        let mut min_val = f64::INFINITY;
2208        for_each_numeric(arr, "min", |n| {
2209            if n < min_val {
2210                min_val = n;
2211            }
2212        })?;
2213        Ok(JValue::Number(min_val))
2214    }
2215
2216    pub fn average(arr: &[JValue]) -> Result<JValue, EvaluatorError> {
2217        if arr.is_empty() {
2218            return Ok(JValue::Null);
2219        }
2220        let mut total = 0.0f64;
2221        let count = count_numeric(arr, "average")?;
2222        for_each_numeric(arr, "average", |n| total += n)?;
2223        Ok(JValue::Number(total / count as f64))
2224    }
2225}
2226
2227/// Evaluator errors
2228#[derive(Error, Debug)]
2229pub enum EvaluatorError {
2230    #[error("Type error: {0}")]
2231    TypeError(String),
2232
2233    #[error("Reference error: {0}")]
2234    ReferenceError(String),
2235
2236    #[error("Evaluation error: {0}")]
2237    EvaluationError(String),
2238}
2239
2240impl From<crate::functions::FunctionError> for EvaluatorError {
2241    fn from(e: crate::functions::FunctionError) -> Self {
2242        EvaluatorError::EvaluationError(e.to_string())
2243    }
2244}
2245
2246impl From<crate::datetime::DateTimeError> for EvaluatorError {
2247    fn from(e: crate::datetime::DateTimeError) -> Self {
2248        EvaluatorError::EvaluationError(e.to_string())
2249    }
2250}
2251
2252/// Result of evaluating a lambda body that may be a tail call
2253/// Used for trampoline-based tail call optimization
2254enum LambdaResult {
2255    /// Final value - evaluation is complete
2256    JValue(JValue),
2257    /// Tail call - need to continue with another lambda invocation
2258    TailCall {
2259        /// The lambda to call (boxed to reduce enum size)
2260        lambda: Box<StoredLambda>,
2261        /// Arguments for the call
2262        args: Vec<JValue>,
2263        /// Data context for the call
2264        data: JValue,
2265    },
2266}
2267
2268/// Lambda storage
2269/// Stores the AST of a lambda function along with its parameters, optional signature,
2270/// and captured environment for closures
2271#[derive(Clone, Debug)]
2272pub struct StoredLambda {
2273    pub params: Vec<String>,
2274    pub body: AstNode,
2275    /// Pre-compiled body for use in tight inner loops (HOF fast path).
2276    /// `None` if the body is not compilable (transform, partial-app, thunk, etc.).
2277    pub(crate) compiled_body: Option<CompiledExpr>,
2278    pub signature: Option<String>,
2279    /// Captured environment bindings for closures
2280    pub captured_env: HashMap<String, JValue>,
2281    /// Captured data context for lexical scoping of bare field names
2282    pub captured_data: Option<JValue>,
2283    /// Whether this lambda's body contains tail calls that can be optimized
2284    pub thunk: bool,
2285}
2286
2287/// A single scope in the scope stack
2288struct Scope {
2289    bindings: HashMap<String, JValue>,
2290    lambdas: HashMap<String, StoredLambda>,
2291}
2292
2293impl Scope {
2294    fn new() -> Self {
2295        Scope {
2296            bindings: HashMap::new(),
2297            lambdas: HashMap::new(),
2298        }
2299    }
2300}
2301
2302/// Evaluation context
2303///
2304/// Holds variable bindings and other state needed during evaluation.
2305/// Uses a scope stack for efficient push/pop instead of clone/restore.
2306pub struct Context {
2307    scope_stack: Vec<Scope>,
2308    parent_data: Option<JValue>,
2309}
2310
2311impl Context {
2312    pub fn new() -> Self {
2313        Context {
2314            scope_stack: vec![Scope::new()],
2315            parent_data: None,
2316        }
2317    }
2318
2319    /// Push a new scope onto the stack
2320    fn push_scope(&mut self) {
2321        self.scope_stack.push(Scope::new());
2322    }
2323
2324    /// Pop the top scope from the stack
2325    fn pop_scope(&mut self) {
2326        if self.scope_stack.len() > 1 {
2327            self.scope_stack.pop();
2328        }
2329    }
2330
2331    /// Pop scope but preserve specified lambdas by moving them to the current top scope
2332    fn pop_scope_preserving_lambdas(&mut self, lambda_ids: &[String]) {
2333        if self.scope_stack.len() > 1 {
2334            let popped = self.scope_stack.pop().unwrap();
2335            if !lambda_ids.is_empty() {
2336                let top = self.scope_stack.last_mut().unwrap();
2337                for id in lambda_ids {
2338                    if let Some(stored) = popped.lambdas.get(id) {
2339                        top.lambdas.insert(id.clone(), stored.clone());
2340                    }
2341                }
2342            }
2343        }
2344    }
2345
2346    /// Clear all bindings and lambdas in the top scope without deallocating
2347    fn clear_current_scope(&mut self) {
2348        let top = self.scope_stack.last_mut().unwrap();
2349        top.bindings.clear();
2350        top.lambdas.clear();
2351    }
2352
2353    pub fn bind(&mut self, name: String, value: JValue) {
2354        self.scope_stack
2355            .last_mut()
2356            .unwrap()
2357            .bindings
2358            .insert(name, value);
2359    }
2360
2361    pub fn bind_lambda(&mut self, name: String, lambda: StoredLambda) {
2362        self.scope_stack
2363            .last_mut()
2364            .unwrap()
2365            .lambdas
2366            .insert(name, lambda);
2367    }
2368
2369    pub fn unbind(&mut self, name: &str) {
2370        // Remove from top scope only
2371        let top = self.scope_stack.last_mut().unwrap();
2372        top.bindings.remove(name);
2373        top.lambdas.remove(name);
2374    }
2375
2376    pub fn lookup(&self, name: &str) -> Option<&JValue> {
2377        // Walk scope stack from top to bottom
2378        for scope in self.scope_stack.iter().rev() {
2379            if let Some(value) = scope.bindings.get(name) {
2380                return Some(value);
2381            }
2382        }
2383        None
2384    }
2385
2386    pub fn lookup_lambda(&self, name: &str) -> Option<&StoredLambda> {
2387        // Walk scope stack from top to bottom
2388        for scope in self.scope_stack.iter().rev() {
2389            if let Some(lambda) = scope.lambdas.get(name) {
2390                return Some(lambda);
2391            }
2392        }
2393        None
2394    }
2395
2396    pub fn set_parent(&mut self, data: JValue) {
2397        self.parent_data = Some(data);
2398    }
2399
2400    pub fn get_parent(&self) -> Option<&JValue> {
2401        self.parent_data.as_ref()
2402    }
2403
2404    /// Collect all bindings across all scopes (for environment capture).
2405    /// Higher scopes shadow lower scopes.
2406    fn all_bindings(&self) -> HashMap<String, JValue> {
2407        let mut result = HashMap::new();
2408        for scope in &self.scope_stack {
2409            for (k, v) in &scope.bindings {
2410                result.insert(k.clone(), v.clone());
2411            }
2412        }
2413        result
2414    }
2415}
2416
2417impl Default for Context {
2418    fn default() -> Self {
2419        Self::new()
2420    }
2421}
2422
2423/// Evaluator for JSONata expressions
2424pub struct Evaluator {
2425    context: Context,
2426    recursion_depth: usize,
2427    max_recursion_depth: usize,
2428    /// Monotonic counter for generating unique lambda IDs. Each evaluation of a
2429    /// Lambda AST node creates a new closure *instance* and must get a fresh ID -
2430    /// using the AST node's pointer address (as before) collided whenever the same
2431    /// lambda expression was evaluated more than once (e.g. each level of Y-combinator
2432    /// or other repeated recursion), aliasing unrelated closures that shared an id.
2433    next_lambda_id: u64,
2434}
2435
2436impl Evaluator {
2437    pub fn new() -> Self {
2438        Evaluator {
2439            context: Context::new(),
2440            recursion_depth: 0,
2441            // Limit recursion depth to prevent stack overflow
2442            // True TCO would allow deeper recursion but requires parser-level thunk marking
2443            max_recursion_depth: 302,
2444            next_lambda_id: 0,
2445        }
2446    }
2447
2448    pub fn with_context(context: Context) -> Self {
2449        Evaluator {
2450            context,
2451            recursion_depth: 0,
2452            max_recursion_depth: 302,
2453            next_lambda_id: 0,
2454        }
2455    }
2456
2457    /// Allocate a fresh, process-unique-per-Evaluator id for a new lambda instance.
2458    fn fresh_lambda_id(&mut self) -> u64 {
2459        let id = self.next_lambda_id;
2460        self.next_lambda_id += 1;
2461        id
2462    }
2463
2464    /// Invoke a stored lambda with its captured environment and data.
2465    /// This is the standard way to call a StoredLambda, handling the
2466    /// captured_env and captured_data extraction boilerplate.
2467    fn invoke_stored_lambda(
2468        &mut self,
2469        stored: &StoredLambda,
2470        args: &[JValue],
2471        data: &JValue,
2472    ) -> Result<JValue, EvaluatorError> {
2473        // Compiled fast path: skip scope push/pop and tree-walking for simple lambdas.
2474        // Conditions: has compiled body, no signature (can't skip validation), no thunk,
2475        // and no captured lambda/builtin values (those require Context for runtime lookup).
2476        if let Some(ref ce) = stored.compiled_body {
2477            if stored.signature.is_none()
2478                && !stored.thunk
2479                && !stored
2480                    .captured_env
2481                    .values()
2482                    .any(|v| matches!(v, JValue::Lambda { .. } | JValue::Builtin { .. }))
2483            {
2484                let call_data = stored.captured_data.as_ref().unwrap_or(data);
2485                let vars: HashMap<&str, &JValue> = stored
2486                    .params
2487                    .iter()
2488                    .zip(args.iter())
2489                    .map(|(p, v)| (p.as_str(), v))
2490                    .chain(stored.captured_env.iter().map(|(k, v)| (k.as_str(), v)))
2491                    .collect();
2492                return eval_compiled(ce, call_data, Some(&vars));
2493            }
2494        }
2495
2496        let captured_env = if stored.captured_env.is_empty() {
2497            None
2498        } else {
2499            Some(&stored.captured_env)
2500        };
2501        let captured_data = stored.captured_data.as_ref();
2502        self.invoke_lambda_with_env(
2503            &stored.params,
2504            &stored.body,
2505            stored.signature.as_ref(),
2506            args,
2507            data,
2508            captured_env,
2509            captured_data,
2510            stored.thunk,
2511        )
2512    }
2513
2514    /// Look up a StoredLambda from a JValue that may be a lambda marker.
2515    /// Returns the cloned StoredLambda if the value is a JValue::Lambda variant
2516    /// with a valid lambda_id that references a stored lambda.
2517    fn lookup_lambda_from_value(&self, value: &JValue) -> Option<StoredLambda> {
2518        if let JValue::Lambda { lambda_id, .. } = value {
2519            return self.context.lookup_lambda(lambda_id).cloned();
2520        }
2521        None
2522    }
2523
2524    /// Get the number of parameters a callback function expects by inspecting its AST.
2525    /// This is used to avoid passing unnecessary arguments to callbacks in HOF functions.
2526    /// Returns the parameter count, or usize::MAX if unable to determine (meaning pass all args).
2527    fn get_callback_param_count(&self, func_node: &AstNode) -> usize {
2528        match func_node {
2529            AstNode::Lambda { params, .. } => params.len(),
2530            AstNode::Variable(var_name) => {
2531                // Check if this variable holds a stored lambda
2532                if let Some(stored_lambda) = self.context.lookup_lambda(var_name) {
2533                    return stored_lambda.params.len();
2534                }
2535                // Also check if it's a lambda value in bindings (e.g., from partial application)
2536                if let Some(value) = self.context.lookup(var_name) {
2537                    if let Some(stored_lambda) = self.lookup_lambda_from_value(value) {
2538                        return stored_lambda.params.len();
2539                    }
2540                }
2541                // Unknown, return max to be safe
2542                usize::MAX
2543            }
2544            AstNode::Function { .. } => {
2545                // For function references, we can't easily determine param count
2546                // Return max to be safe
2547                usize::MAX
2548            }
2549            _ => usize::MAX,
2550        }
2551    }
2552
2553    /// Specialized sort using pre-extracted keys (Schwartzian transform).
2554    /// Extracts sort keys once (N lookups), then sorts by comparing keys directly,
2555    /// avoiding O(N log N) hash lookups during comparisons.
2556    fn merge_sort_specialized(arr: &mut [JValue], spec: &SpecializedSortComparator) {
2557        if arr.len() <= 1 {
2558            return;
2559        }
2560
2561        // Phase 1: Extract sort keys -- one IndexMap lookup per element
2562        let keys: Vec<SortKey> = arr
2563            .iter()
2564            .map(|item| match item {
2565                JValue::Object(obj) => match obj.get(&spec.field) {
2566                    Some(JValue::Number(n)) => SortKey::Num(*n),
2567                    Some(JValue::String(s)) => SortKey::Str(s.clone()),
2568                    _ => SortKey::None,
2569                },
2570                _ => SortKey::None,
2571            })
2572            .collect();
2573
2574        // Phase 2: Build index permutation sorted by pre-extracted keys
2575        let mut perm: Vec<usize> = (0..arr.len()).collect();
2576        perm.sort_by(|&a, &b| compare_sort_keys(&keys[a], &keys[b], spec.descending));
2577
2578        // Phase 3: Apply permutation in-place via cycle-following
2579        let mut placed = vec![false; arr.len()];
2580        for i in 0..arr.len() {
2581            if placed[i] || perm[i] == i {
2582                continue;
2583            }
2584            let mut j = i;
2585            loop {
2586                let target = perm[j];
2587                placed[j] = true;
2588                if target == i {
2589                    break;
2590                }
2591                arr.swap(j, target);
2592                j = target;
2593            }
2594        }
2595    }
2596
2597    /// Merge sort implementation using a comparator function.
2598    /// This replaces the O(n²) bubble sort for better performance on large arrays.
2599    /// The comparator returns true if the first element should come AFTER the second.
2600    fn merge_sort_with_comparator(
2601        &mut self,
2602        arr: &mut [JValue],
2603        comparator: &AstNode,
2604        data: &JValue,
2605    ) -> Result<(), EvaluatorError> {
2606        if arr.len() <= 1 {
2607            return Ok(());
2608        }
2609
2610        // Try specialized fast path for simple field comparisons like
2611        // function($l, $r) { $l.price > $r.price }
2612        if let AstNode::Lambda { params, body, .. } = comparator {
2613            if params.len() >= 2 {
2614                if let Some(spec) = try_specialize_sort_comparator(body, &params[0], &params[1]) {
2615                    Self::merge_sort_specialized(arr, &spec);
2616                    return Ok(());
2617                }
2618            }
2619        }
2620
2621        let mid = arr.len() / 2;
2622
2623        // Sort left half
2624        self.merge_sort_with_comparator(&mut arr[..mid], comparator, data)?;
2625
2626        // Sort right half
2627        self.merge_sort_with_comparator(&mut arr[mid..], comparator, data)?;
2628
2629        // Merge the sorted halves
2630        let mut temp = Vec::with_capacity(arr.len());
2631        let (left, right) = arr.split_at(mid);
2632
2633        let mut i = 0;
2634        let mut j = 0;
2635
2636        // For lambda comparators, use a reusable scope to avoid
2637        // push_scope/pop_scope per comparison (~n log n total comparisons)
2638        if let AstNode::Lambda { params, body, .. } = comparator {
2639            if params.len() >= 2 {
2640                // Pre-clone param names once outside the loop
2641                let param0 = params[0].clone();
2642                let param1 = params[1].clone();
2643                self.context.push_scope();
2644                while i < left.len() && j < right.len() {
2645                    // Reuse scope: clear and rebind instead of push/pop
2646                    self.context.clear_current_scope();
2647                    self.context.bind(param0.clone(), left[i].clone());
2648                    self.context.bind(param1.clone(), right[j].clone());
2649
2650                    let cmp_result = self.evaluate_internal(body, data)?;
2651
2652                    if self.is_truthy(&cmp_result) {
2653                        temp.push(right[j].clone());
2654                        j += 1;
2655                    } else {
2656                        temp.push(left[i].clone());
2657                        i += 1;
2658                    }
2659                }
2660                self.context.pop_scope();
2661            } else {
2662                // Unexpected param count - fall back to generic path
2663                while i < left.len() && j < right.len() {
2664                    let cmp_result = self.apply_function(
2665                        comparator,
2666                        &[left[i].clone(), right[j].clone()],
2667                        data,
2668                    )?;
2669                    if self.is_truthy(&cmp_result) {
2670                        temp.push(right[j].clone());
2671                        j += 1;
2672                    } else {
2673                        temp.push(left[i].clone());
2674                        i += 1;
2675                    }
2676                }
2677            }
2678        } else {
2679            // Non-lambda comparator: use generic apply_function path
2680            while i < left.len() && j < right.len() {
2681                let cmp_result =
2682                    self.apply_function(comparator, &[left[i].clone(), right[j].clone()], data)?;
2683                if self.is_truthy(&cmp_result) {
2684                    temp.push(right[j].clone());
2685                    j += 1;
2686                } else {
2687                    temp.push(left[i].clone());
2688                    i += 1;
2689                }
2690            }
2691        }
2692
2693        // Copy remaining elements
2694        temp.extend_from_slice(&left[i..]);
2695        temp.extend_from_slice(&right[j..]);
2696
2697        // Copy back to original array (can't use copy_from_slice since JValue is not Copy)
2698        for (i, val) in temp.into_iter().enumerate() {
2699            arr[i] = val;
2700        }
2701
2702        Ok(())
2703    }
2704
2705    /// Evaluate an AST node against data
2706    ///
2707    /// This is the main entry point for evaluation. It sets up the parent context
2708    /// to be the root data if not already set.
2709    pub fn evaluate(&mut self, node: &AstNode, data: &JValue) -> Result<JValue, EvaluatorError> {
2710        // Set parent context to root data if not already set
2711        if self.context.get_parent().is_none() {
2712            self.context.set_parent(data.clone());
2713        }
2714
2715        self.evaluate_internal(node, data)
2716    }
2717
2718    /// Fast evaluation for leaf nodes that don't need recursion tracking.
2719    /// Returns Some for literals, simple field access on objects, and simple variable lookups.
2720    /// Returns None for anything requiring the full evaluator.
2721    #[inline(always)]
2722    fn evaluate_leaf(
2723        &mut self,
2724        node: &AstNode,
2725        data: &JValue,
2726    ) -> Option<Result<JValue, EvaluatorError>> {
2727        match node {
2728            AstNode::String(s) => Some(Ok(JValue::string(s.clone()))),
2729            AstNode::Number(n) => {
2730                if n.fract() == 0.0 && n.is_finite() && n.abs() < (1i64 << 53) as f64 {
2731                    Some(Ok(JValue::from(*n as i64)))
2732                } else {
2733                    Some(Ok(JValue::Number(*n)))
2734                }
2735            }
2736            AstNode::Boolean(b) => Some(Ok(JValue::Bool(*b))),
2737            AstNode::Null => Some(Ok(JValue::Null)),
2738            AstNode::Undefined => Some(Ok(JValue::Undefined)),
2739            AstNode::Name(field_name) => match data {
2740                // Array mapping and other cases need full evaluator
2741                JValue::Object(obj) => Some(Ok(obj
2742                    .get(field_name)
2743                    .cloned()
2744                    .unwrap_or(JValue::Undefined))),
2745                _ => None,
2746            },
2747            AstNode::Variable(name) if !name.is_empty() => {
2748                // Simple variable lookup — only fast-path when no tuple data
2749                if let JValue::Object(obj) = data {
2750                    if obj.get("__tuple__") == Some(&JValue::Bool(true)) {
2751                        return None; // Tuple data needs full evaluator
2752                    }
2753                }
2754                // May be a lambda/builtin — needs full evaluator if None
2755                self.context.lookup(name).map(|value| Ok(value.clone()))
2756            }
2757            _ => None,
2758        }
2759    }
2760
2761    /// Internal evaluation method
2762    fn evaluate_internal(
2763        &mut self,
2764        node: &AstNode,
2765        data: &JValue,
2766    ) -> Result<JValue, EvaluatorError> {
2767        // Fast path for leaf nodes — skip recursion tracking overhead
2768        if let Some(result) = self.evaluate_leaf(node, data) {
2769            return result;
2770        }
2771
2772        // Check recursion depth to prevent stack overflow
2773        self.recursion_depth += 1;
2774        if self.recursion_depth > self.max_recursion_depth {
2775            self.recursion_depth -= 1;
2776            return Err(EvaluatorError::EvaluationError(format!(
2777                "U1001: Stack overflow - maximum recursion depth ({}) exceeded",
2778                self.max_recursion_depth
2779            )));
2780        }
2781
2782        // The soft depth counter above is calibrated against a comfortably
2783        // large native stack. Hosts with a much smaller default thread stack
2784        // (notably Windows, ~1MB vs Linux's ~8MB) can exhaust the *real*
2785        // stack well before this counter trips, crashing the process instead
2786        // of returning U1001 (see GitHub issue #34). stacker::maybe_grow
2787        // transparently swaps in a bigger stack segment when headroom is
2788        // low, so this stays a no-op cost on the common shallow path.
2789        const RED_ZONE: usize = 128 * 1024;
2790        const GROW_STACK_SIZE: usize = 8 * 1024 * 1024;
2791        let result = stacker::maybe_grow(RED_ZONE, GROW_STACK_SIZE, || {
2792            self.evaluate_internal_impl(node, data)
2793        });
2794
2795        self.recursion_depth -= 1;
2796        result
2797    }
2798
2799    /// Internal evaluation implementation (separated to allow depth tracking)
2800    fn evaluate_internal_impl(
2801        &mut self,
2802        node: &AstNode,
2803        data: &JValue,
2804    ) -> Result<JValue, EvaluatorError> {
2805        match node {
2806            AstNode::String(s) => Ok(JValue::string(s.clone())),
2807
2808            // Name nodes represent field access on the current data
2809            AstNode::Name(field_name) => {
2810                match data {
2811                    JValue::Object(obj) => {
2812                        Ok(obj.get(field_name).cloned().unwrap_or(JValue::Undefined))
2813                    }
2814                    JValue::Array(arr) => {
2815                        // Map over array
2816                        let mut result = Vec::new();
2817                        for item in arr.iter() {
2818                            if let JValue::Object(obj) = item {
2819                                if let Some(val) = obj.get(field_name) {
2820                                    result.push(val.clone());
2821                                }
2822                            }
2823                        }
2824                        if result.is_empty() {
2825                            Ok(JValue::Undefined)
2826                        } else if result.len() == 1 {
2827                            Ok(result.into_iter().next().unwrap())
2828                        } else {
2829                            Ok(JValue::array(result))
2830                        }
2831                    }
2832                    _ => Ok(JValue::Undefined),
2833                }
2834            }
2835
2836            AstNode::Number(n) => {
2837                // Preserve integer-ness: if the number is a whole number, create an integer JValue
2838                if n.fract() == 0.0 && n.is_finite() && n.abs() < (1i64 << 53) as f64 {
2839                    // It's a whole number that can be represented as i64
2840                    Ok(JValue::from(*n as i64))
2841                } else {
2842                    Ok(JValue::Number(*n))
2843                }
2844            }
2845            AstNode::Boolean(b) => Ok(JValue::Bool(*b)),
2846            AstNode::Null => Ok(JValue::Null),
2847            AstNode::Undefined => Ok(JValue::Undefined),
2848            AstNode::Placeholder => {
2849                // Placeholders should only appear as function arguments
2850                // If we reach here, it's an error
2851                Err(EvaluatorError::EvaluationError(
2852                    "Placeholder '?' can only be used as a function argument".to_string(),
2853                ))
2854            }
2855            AstNode::Regex { pattern, flags } => {
2856                // Return a regex object as a special JSON value
2857                // This will be recognized by functions like $split, $match, $replace
2858                Ok(JValue::regex(pattern.as_str(), flags.as_str()))
2859            }
2860
2861            AstNode::Variable(name) => {
2862                // Special case: $ alone (empty name) refers to current context
2863                // First check if $ is bound in the context (for closures that captured $)
2864                // Otherwise, use the data parameter
2865                if name.is_empty() {
2866                    if let Some(value) = self.context.lookup("$") {
2867                        return Ok(value.clone());
2868                    }
2869                    // If data is a tuple, return the @ value
2870                    if let JValue::Object(obj) = data {
2871                        if obj.get("__tuple__") == Some(&JValue::Bool(true)) {
2872                            if let Some(inner) = obj.get("@") {
2873                                return Ok(inner.clone());
2874                            }
2875                        }
2876                    }
2877                    return Ok(data.clone());
2878                }
2879
2880                // Check variable bindings FIRST
2881                // This allows function parameters to shadow outer lambdas with the same name
2882                // Critical for Y-combinator pattern: function($g){$g($g)} where $g shadows outer $g
2883                if let Some(value) = self.context.lookup(name) {
2884                    return Ok(value.clone());
2885                }
2886
2887                // Check tuple bindings in data (for index binding operator #$var)
2888                // When iterating over a tuple stream, $var can reference the bound index
2889                if let JValue::Object(obj) = data {
2890                    if obj.get("__tuple__") == Some(&JValue::Bool(true)) {
2891                        // Check for the variable in tuple bindings (stored as "$name")
2892                        let binding_key = format!("${}", name);
2893                        if let Some(binding_value) = obj.get(&binding_key) {
2894                            return Ok(binding_value.clone());
2895                        }
2896                    }
2897                }
2898
2899                // Then check if this is a stored lambda (user-defined functions)
2900                if let Some(stored_lambda) = self.context.lookup_lambda(name) {
2901                    // Return a lambda representation that can be passed to higher-order functions
2902                    // Include _lambda_id pointing to the stored lambda so it can be found
2903                    // when captured in closures
2904                    let lambda_repr = JValue::lambda(
2905                        name.as_str(),
2906                        stored_lambda.params.clone(),
2907                        Some(name.to_string()),
2908                        stored_lambda.signature.clone(),
2909                    );
2910                    return Ok(lambda_repr);
2911                }
2912
2913                // Check if this is a built-in function reference (only if not shadowed)
2914                if self.is_builtin_function(name) {
2915                    // Return a marker for built-in functions
2916                    // This allows built-in functions to be passed to higher-order functions
2917                    let builtin_repr = JValue::builtin(name.as_str());
2918                    return Ok(builtin_repr);
2919                }
2920
2921                // Undefined variable - return null (undefined in JSONata semantics)
2922                // This allows expressions like `$not(undefined_var)` to return undefined
2923                // and comparisons like `3 > $undefined` to return undefined
2924                Ok(JValue::Null)
2925            }
2926
2927            AstNode::ParentVariable(name) => {
2928                // Special case: $$ alone (empty name) refers to parent/root context
2929                if name.is_empty() {
2930                    return self.context.get_parent().cloned().ok_or_else(|| {
2931                        EvaluatorError::ReferenceError("Parent context not available".to_string())
2932                    });
2933                }
2934
2935                // For $$name, we need to evaluate name against parent context
2936                // This is similar to $.name but using parent data
2937                let parent_data = self.context.get_parent().ok_or_else(|| {
2938                    EvaluatorError::ReferenceError("Parent context not available".to_string())
2939                })?;
2940
2941                // Access field on parent context
2942                match parent_data {
2943                    JValue::Object(obj) => Ok(obj.get(name).cloned().unwrap_or(JValue::Null)),
2944                    _ => Ok(JValue::Null),
2945                }
2946            }
2947
2948            AstNode::Path { steps } => self.evaluate_path(steps, data),
2949
2950            AstNode::Binary { op, lhs, rhs } => self.evaluate_binary_op(*op, lhs, rhs, data),
2951
2952            AstNode::Unary { op, operand } => self.evaluate_unary_op(*op, operand, data),
2953
2954            // Array constructor - JSONata semantics:
2955            AstNode::Array(elements) => {
2956                // - If element is itself an array constructor [...], keep it nested
2957                // - Otherwise, if element evaluates to an array, flatten it
2958                // - Undefined values are excluded
2959                let mut result = Vec::with_capacity(elements.len());
2960                for element in elements {
2961                    // Check if this element is itself an explicit array constructor
2962                    let is_array_constructor = matches!(element, AstNode::Array(_));
2963
2964                    let value = self.evaluate_internal(element, data)?;
2965
2966                    // Skip undefined values in array constructors
2967                    // Note: explicit null is preserved, only undefined (no value) is filtered
2968                    if value.is_undefined() {
2969                        continue;
2970                    }
2971
2972                    if is_array_constructor {
2973                        // Explicit array constructor - keep nested
2974                        result.push(value);
2975                    } else if let JValue::Array(arr) = value {
2976                        // Non-array-constructor that evaluated to array - flatten it
2977                        result.extend(arr.iter().cloned());
2978                    } else {
2979                        // Non-array value - add as-is
2980                        result.push(value);
2981                    }
2982                }
2983                Ok(JValue::array(result))
2984            }
2985
2986            AstNode::Object(pairs) => {
2987                let mut result = IndexMap::with_capacity(pairs.len());
2988
2989                // Check if all keys are string literals — can skip D1009 HashMap
2990                let all_literal_keys = pairs.iter().all(|(k, _)| matches!(k, AstNode::String(_)));
2991
2992                if all_literal_keys {
2993                    // Fast path: literal keys, no need for D1009 tracking
2994                    for (key_node, value_node) in pairs.iter() {
2995                        let key = match key_node {
2996                            AstNode::String(s) => s,
2997                            _ => unreachable!(),
2998                        };
2999                        let value = self.evaluate_internal(value_node, data)?;
3000                        if value.is_undefined() {
3001                            continue;
3002                        }
3003                        result.insert(key.clone(), value);
3004                    }
3005                } else {
3006                    let mut key_sources: HashMap<String, usize> = HashMap::new();
3007                    for (pair_index, (key_node, value_node)) in pairs.iter().enumerate() {
3008                        let key = match self.evaluate_internal(key_node, data)? {
3009                            JValue::String(s) => s,
3010                            JValue::Null => continue,
3011                            other => {
3012                                if other.is_undefined() {
3013                                    continue;
3014                                }
3015                                return Err(EvaluatorError::TypeError(format!(
3016                                    "Object key must be a string, got: {:?}",
3017                                    other
3018                                )));
3019                            }
3020                        };
3021
3022                        if let Some(&existing_idx) = key_sources.get(&*key) {
3023                            if existing_idx != pair_index {
3024                                return Err(EvaluatorError::EvaluationError(format!(
3025                                    "D1009: Multiple key expressions evaluate to same key: {}",
3026                                    key
3027                                )));
3028                            }
3029                        }
3030                        key_sources.insert(key.to_string(), pair_index);
3031
3032                        let value = self.evaluate_internal(value_node, data)?;
3033                        if value.is_undefined() {
3034                            continue;
3035                        }
3036                        result.insert(key.to_string(), value);
3037                    }
3038                }
3039                Ok(JValue::object(result))
3040            }
3041
3042            // Object transform: group items by key, then evaluate value once per group
3043            AstNode::ObjectTransform { input, pattern } => {
3044                // Evaluate the input expression
3045                let input_value = self.evaluate_internal(input, data)?;
3046
3047                // If input is undefined, return undefined (not empty object)
3048                if input_value.is_undefined() {
3049                    return Ok(JValue::Undefined);
3050                }
3051
3052                // Handle array input - process each item
3053                let items: Vec<JValue> = match input_value {
3054                    JValue::Array(ref arr) => (**arr).clone(),
3055                    JValue::Null => return Ok(JValue::Null),
3056                    other => vec![other],
3057                };
3058
3059                // If array is empty, add undefined to enable literal JSON object generation
3060                let items = if items.is_empty() {
3061                    vec![JValue::Undefined]
3062                } else {
3063                    items
3064                };
3065
3066                // Phase 1: Group items by key expression
3067                // groups maps key -> (grouped_data, expr_index)
3068                // When multiple items have same key, their data is appended together
3069                let mut groups: HashMap<String, (Vec<JValue>, usize)> = HashMap::new();
3070
3071                // Save the current $ binding to restore later
3072                let saved_dollar = self.context.lookup("$").cloned();
3073
3074                for item in &items {
3075                    // Bind $ to the current item for key evaluation
3076                    self.context.bind("$".to_string(), item.clone());
3077
3078                    for (pair_index, (key_node, _value_node)) in pattern.iter().enumerate() {
3079                        // Evaluate key with current item as context
3080                        let key = match self.evaluate_internal(key_node, item)? {
3081                            JValue::String(s) => s,
3082                            JValue::Null => continue, // Skip null keys
3083                            other => {
3084                                // Skip undefined keys
3085                                if other.is_undefined() {
3086                                    continue;
3087                                }
3088                                return Err(EvaluatorError::TypeError(format!(
3089                                    "T1003: Object key must be a string, got: {:?}",
3090                                    other
3091                                )));
3092                            }
3093                        };
3094
3095                        // Group items by key
3096                        if let Some((existing_data, existing_idx)) = groups.get_mut(&*key) {
3097                            // Key already exists - check if from same expression index
3098                            if *existing_idx != pair_index {
3099                                // D1009: multiple key expressions evaluate to same key
3100                                return Err(EvaluatorError::EvaluationError(format!(
3101                                    "D1009: Multiple key expressions evaluate to same key: {}",
3102                                    key
3103                                )));
3104                            }
3105                            // Append item to the group
3106                            existing_data.push(item.clone());
3107                        } else {
3108                            // New key - create new group
3109                            groups.insert(key.to_string(), (vec![item.clone()], pair_index));
3110                        }
3111                    }
3112                }
3113
3114                // Phase 2: Evaluate value expression for each group
3115                let mut result = IndexMap::new();
3116
3117                for (key, (grouped_data, expr_index)) in groups {
3118                    // Get the value expression for this group
3119                    let (_key_node, value_node) = &pattern[expr_index];
3120
3121                    // Determine the context for value evaluation:
3122                    // - If single item, use that item directly
3123                    // - If multiple items, use the array of items
3124                    let context = if grouped_data.len() == 1 {
3125                        grouped_data.into_iter().next().unwrap()
3126                    } else {
3127                        JValue::array(grouped_data)
3128                    };
3129
3130                    // Bind $ to the context for value evaluation
3131                    self.context.bind("$".to_string(), context.clone());
3132
3133                    // Evaluate value expression with grouped context
3134                    let value = self.evaluate_internal(value_node, &context)?;
3135
3136                    // Skip undefined values
3137                    if !value.is_undefined() {
3138                        result.insert(key, value);
3139                    }
3140                }
3141
3142                // Restore the previous $ binding
3143                if let Some(saved) = saved_dollar {
3144                    self.context.bind("$".to_string(), saved);
3145                } else {
3146                    self.context.unbind("$");
3147                }
3148
3149                Ok(JValue::object(result))
3150            }
3151
3152            AstNode::Function {
3153                name,
3154                args,
3155                is_builtin,
3156            } => self.evaluate_function_call(name, args, *is_builtin, data),
3157
3158            // Call: invoke an arbitrary expression as a function
3159            // Used for IIFE patterns like (function($x){...})(5) or chained calls
3160            AstNode::Call { procedure, args } => {
3161                // Evaluate the procedure to get the callable value
3162                let callable = self.evaluate_internal(procedure, data)?;
3163
3164                // Check if it's a lambda value
3165                if let Some(stored_lambda) = self.lookup_lambda_from_value(&callable) {
3166                    let mut evaluated_args = Vec::with_capacity(args.len());
3167                    for arg in args.iter() {
3168                        evaluated_args.push(self.evaluate_internal(arg, data)?);
3169                    }
3170                    return self.invoke_stored_lambda(&stored_lambda, &evaluated_args, data);
3171                }
3172
3173                // Not a callable value
3174                Err(EvaluatorError::TypeError(format!(
3175                    "Cannot call non-function value: {:?}",
3176                    callable
3177                )))
3178            }
3179
3180            AstNode::Conditional {
3181                condition,
3182                then_branch,
3183                else_branch,
3184            } => {
3185                let condition_value = self.evaluate_internal(condition, data)?;
3186                if self.is_truthy(&condition_value) {
3187                    self.evaluate_internal(then_branch, data)
3188                } else if let Some(else_branch) = else_branch {
3189                    self.evaluate_internal(else_branch, data)
3190                } else {
3191                    // No else branch - return undefined (not null)
3192                    // This allows $map to filter out results from conditionals without else
3193                    Ok(JValue::Undefined)
3194                }
3195            }
3196
3197            AstNode::Block(expressions) => {
3198                // Blocks create a new scope - push scope instead of clone/restore
3199                self.context.push_scope();
3200
3201                let mut result = JValue::Null;
3202                for expr in expressions {
3203                    result = self.evaluate_internal(expr, data)?;
3204                }
3205
3206                // Before popping, preserve any lambdas referenced by the result
3207                // This is essential for closures returned from blocks (IIFE pattern)
3208                let lambdas_to_keep = self.extract_lambda_ids(&result);
3209                self.context.pop_scope_preserving_lambdas(&lambdas_to_keep);
3210
3211                Ok(result)
3212            }
3213
3214            // Lambda: capture current environment for closure support
3215            AstNode::Lambda {
3216                params,
3217                body,
3218                signature,
3219                thunk,
3220            } => {
3221                let lambda_id = format!("__lambda_{}_{}", params.len(), self.fresh_lambda_id());
3222
3223                let compiled_body = if !thunk {
3224                    let var_refs: Vec<&str> = params.iter().map(|s| s.as_str()).collect();
3225                    try_compile_expr_with_allowed_vars(body, &var_refs)
3226                } else {
3227                    None
3228                };
3229                let stored_lambda = StoredLambda {
3230                    params: params.clone(),
3231                    body: (**body).clone(),
3232                    compiled_body,
3233                    signature: signature.clone(),
3234                    captured_env: self.capture_environment_for(body, params),
3235                    captured_data: Some(data.clone()),
3236                    thunk: *thunk,
3237                };
3238                self.context.bind_lambda(lambda_id.clone(), stored_lambda);
3239
3240                let lambda_obj = JValue::lambda(
3241                    lambda_id.as_str(),
3242                    params.clone(),
3243                    None::<String>,
3244                    signature.clone(),
3245                );
3246
3247                Ok(lambda_obj)
3248            }
3249
3250            // Wildcard: collect all values from current object
3251            AstNode::Wildcard => {
3252                match data {
3253                    JValue::Object(obj) => {
3254                        let mut result = Vec::new();
3255                        for value in obj.values() {
3256                            // Flatten arrays into the result
3257                            match value {
3258                                JValue::Array(arr) => result.extend(arr.iter().cloned()),
3259                                _ => result.push(value.clone()),
3260                            }
3261                        }
3262                        Ok(JValue::array(result))
3263                    }
3264                    JValue::Array(arr) => {
3265                        // For arrays, wildcard returns all elements
3266                        Ok(JValue::Array(arr.clone()))
3267                    }
3268                    _ => Ok(JValue::Null),
3269                }
3270            }
3271
3272            // Descendant: recursively traverse all nested values
3273            AstNode::Descendant => {
3274                let descendants = self.collect_descendants(data);
3275                if descendants.is_empty() {
3276                    Ok(JValue::Null) // No descendants means undefined
3277                } else {
3278                    Ok(JValue::array(descendants))
3279                }
3280            }
3281
3282            AstNode::Predicate(_) => Err(EvaluatorError::EvaluationError(
3283                "Predicate can only be used in path expressions".to_string(),
3284            )),
3285
3286            // Array grouping: same as Array but prevents flattening in path contexts
3287            AstNode::ArrayGroup(elements) => {
3288                let mut result = Vec::new();
3289                for element in elements {
3290                    let value = self.evaluate_internal(element, data)?;
3291                    result.push(value);
3292                }
3293                Ok(JValue::array(result))
3294            }
3295
3296            AstNode::FunctionApplication(_) => Err(EvaluatorError::EvaluationError(
3297                "Function application can only be used in path expressions".to_string(),
3298            )),
3299
3300            AstNode::Sort { input, terms } => {
3301                let value = self.evaluate_internal(input, data)?;
3302                self.evaluate_sort(&value, terms)
3303            }
3304
3305            // Index binding: evaluates input and creates tuple stream with index variable
3306            AstNode::IndexBind { input, variable } => {
3307                let value = self.evaluate_internal(input, data)?;
3308
3309                // Store the variable name and create indexed results
3310                // This is a simplified implementation - full tuple stream would require more work
3311                match value {
3312                    JValue::Array(arr) => {
3313                        // Store the index binding metadata in a special wrapper
3314                        let mut result = Vec::new();
3315                        for (idx, item) in arr.iter().enumerate() {
3316                            // Create wrapper object with value and index
3317                            let mut wrapper = IndexMap::new();
3318                            wrapper.insert("@".to_string(), item.clone());
3319                            wrapper.insert(format!("${}", variable), JValue::Number(idx as f64));
3320                            wrapper.insert("__tuple__".to_string(), JValue::Bool(true));
3321                            result.push(JValue::object(wrapper));
3322                        }
3323                        Ok(JValue::array(result))
3324                    }
3325                    // Single value: just return as-is with index 0
3326                    other => {
3327                        let mut wrapper = IndexMap::new();
3328                        wrapper.insert("@".to_string(), other);
3329                        wrapper.insert(format!("${}", variable), JValue::from(0i64));
3330                        wrapper.insert("__tuple__".to_string(), JValue::Bool(true));
3331                        Ok(JValue::object(wrapper))
3332                    }
3333                }
3334            }
3335
3336            // Transform: |location|update[,delete]|
3337            AstNode::Transform {
3338                location,
3339                update,
3340                delete,
3341            } => {
3342                // Check if $ is bound (meaning we're being invoked as a lambda)
3343                if self.context.lookup("$").is_some() {
3344                    // Execute the transformation
3345                    self.execute_transform(location, update, delete.as_deref(), data)
3346                } else {
3347                    // Return a lambda representation
3348                    // The transform will be executed when the lambda is invoked
3349                    let transform_lambda = StoredLambda {
3350                        params: vec!["$".to_string()],
3351                        body: AstNode::Transform {
3352                            location: location.clone(),
3353                            update: update.clone(),
3354                            delete: delete.clone(),
3355                        },
3356                        compiled_body: None, // Transform is not a pure compilable expr
3357                        signature: None,
3358                        captured_env: HashMap::new(),
3359                        captured_data: None, // Transform takes $ as parameter
3360                        thunk: false,
3361                    };
3362
3363                    // Store with a generated unique name
3364                    let lambda_name = format!("__transform_{}", self.fresh_lambda_id());
3365                    self.context.bind_lambda(lambda_name, transform_lambda);
3366
3367                    // Return lambda marker
3368                    Ok(JValue::string("<lambda>"))
3369                }
3370            }
3371        }
3372    }
3373
3374    /// Apply stages (filters/predicates) to a value during field extraction
3375    /// Non-array values are wrapped in an array before filtering (JSONata semantics)
3376    /// This matches the JavaScript reference where stages apply to sequences
3377    fn apply_stages(&mut self, value: JValue, stages: &[Stage]) -> Result<JValue, EvaluatorError> {
3378        // Wrap non-arrays in an array for filtering (JSONata semantics)
3379        let mut result = match value {
3380            JValue::Null => return Ok(JValue::Null), // Null passes through unchanged
3381            JValue::Array(_) => value,
3382            other => JValue::array(vec![other]),
3383        };
3384
3385        for stage in stages {
3386            match stage {
3387                Stage::Filter(predicate_expr) => {
3388                    // When applying stages, use stage-specific predicate logic
3389                    result = self.evaluate_predicate_as_stage(&result, predicate_expr)?;
3390                }
3391            }
3392        }
3393        Ok(result)
3394    }
3395
3396    /// Check if an AST node is definitely a filter expression (comparison/logical)
3397    /// rather than a potential numeric index. When true, we skip speculative numeric evaluation.
3398    fn is_filter_predicate(predicate: &AstNode) -> bool {
3399        match predicate {
3400            AstNode::Binary { op, .. } => matches!(
3401                op,
3402                BinaryOp::GreaterThan
3403                    | BinaryOp::GreaterThanOrEqual
3404                    | BinaryOp::LessThan
3405                    | BinaryOp::LessThanOrEqual
3406                    | BinaryOp::Equal
3407                    | BinaryOp::NotEqual
3408                    | BinaryOp::And
3409                    | BinaryOp::Or
3410                    | BinaryOp::In
3411            ),
3412            AstNode::Unary {
3413                op: crate::ast::UnaryOp::Not,
3414                ..
3415            } => true,
3416            _ => false,
3417        }
3418    }
3419
3420    /// Evaluate a predicate as a stage during field extraction
3421    /// This has different semantics than standalone predicates:
3422    /// - Maps index operations over arrays of extracted values
3423    fn evaluate_predicate_as_stage(
3424        &mut self,
3425        current: &JValue,
3426        predicate: &AstNode,
3427    ) -> Result<JValue, EvaluatorError> {
3428        // Special case: empty brackets [] (represented as Boolean(true))
3429        if matches!(predicate, AstNode::Boolean(true)) {
3430            return match current {
3431                JValue::Array(arr) => Ok(JValue::Array(arr.clone())),
3432                JValue::Null => Ok(JValue::Null),
3433                other => Ok(JValue::array(vec![other.clone()])),
3434            };
3435        }
3436
3437        match current {
3438            JValue::Array(arr) => {
3439                // For stages: if we have an array of values (from field extraction),
3440                // apply the predicate to each value if appropriate
3441
3442                // Check if predicate is a numeric index
3443                if let AstNode::Number(n) = predicate {
3444                    // Check if this is an array of arrays (extracted array fields)
3445                    let is_array_of_arrays =
3446                        arr.iter().any(|item| matches!(item, JValue::Array(_)));
3447
3448                    if !is_array_of_arrays {
3449                        // Simple values: just index normally
3450                        return self.array_index(current, &JValue::Number(*n));
3451                    }
3452
3453                    // Array of arrays: map index access over each extracted array
3454                    let mut result = Vec::new();
3455                    for item in arr.iter() {
3456                        match item {
3457                            JValue::Array(_) => {
3458                                let indexed = self.array_index(item, &JValue::Number(*n))?;
3459                                if !indexed.is_null() && !indexed.is_undefined() {
3460                                    result.push(indexed);
3461                                }
3462                            }
3463                            _ => {
3464                                if *n == 0.0 {
3465                                    result.push(item.clone());
3466                                }
3467                            }
3468                        }
3469                    }
3470                    return Ok(JValue::array(result));
3471                }
3472
3473                // Short-circuit: if predicate is definitely a comparison/logical expression,
3474                // skip speculative numeric evaluation and go directly to filter logic
3475                if Self::is_filter_predicate(predicate) {
3476                    // Try CompiledExpr fast path (handles compound predicates, arithmetic, etc.)
3477                    if let Some(compiled) = try_compile_expr(predicate) {
3478                        let shape = arr.first().and_then(build_shape_cache);
3479                        let mut filtered = Vec::with_capacity(arr.len());
3480                        for item in arr.iter() {
3481                            let result = if let Some(ref s) = shape {
3482                                eval_compiled_shaped(&compiled, item, None, s)?
3483                            } else {
3484                                eval_compiled(&compiled, item, None)?
3485                            };
3486                            if compiled_is_truthy(&result) {
3487                                filtered.push(item.clone());
3488                            }
3489                        }
3490                        return Ok(JValue::array(filtered));
3491                    }
3492                    // Fallback: full AST evaluation
3493                    let mut filtered = Vec::new();
3494                    for item in arr.iter() {
3495                        let item_result = self.evaluate_internal(predicate, item)?;
3496                        if self.is_truthy(&item_result) {
3497                            filtered.push(item.clone());
3498                        }
3499                    }
3500                    return Ok(JValue::array(filtered));
3501                }
3502
3503                // Try to evaluate the predicate to see if it's a numeric index or array of indices
3504                // If evaluation succeeds and yields a number, use it as an index
3505                // If it yields an array of numbers, use them as multiple indices
3506                // If evaluation fails (e.g., comparison error), treat as filter
3507                match self.evaluate_internal(predicate, current) {
3508                    Ok(JValue::Number(n)) => {
3509                        let n_val = n;
3510                        let is_array_of_arrays =
3511                            arr.iter().any(|item| matches!(item, JValue::Array(_)));
3512
3513                        if !is_array_of_arrays {
3514                            let pred_result = JValue::Number(n_val);
3515                            return self.array_index(current, &pred_result);
3516                        }
3517
3518                        // Array of arrays: map index access
3519                        let mut result = Vec::new();
3520                        let pred_result = JValue::Number(n_val);
3521                        for item in arr.iter() {
3522                            match item {
3523                                JValue::Array(_) => {
3524                                    let indexed = self.array_index(item, &pred_result)?;
3525                                    if !indexed.is_null() && !indexed.is_undefined() {
3526                                        result.push(indexed);
3527                                    }
3528                                }
3529                                _ => {
3530                                    if n_val == 0.0 {
3531                                        result.push(item.clone());
3532                                    }
3533                                }
3534                            }
3535                        }
3536                        return Ok(JValue::array(result));
3537                    }
3538                    Ok(JValue::Array(indices)) => {
3539                        // Array of values - could be indices or filter results
3540                        // Check if all values are numeric
3541                        let has_non_numeric =
3542                            indices.iter().any(|v| !matches!(v, JValue::Number(_)));
3543
3544                        if has_non_numeric {
3545                            // Non-numeric values - treat as filter, fall through
3546                        } else {
3547                            // All numeric - use as indices
3548                            let arr_len = arr.len() as i64;
3549                            let mut resolved_indices: Vec<i64> = indices
3550                                .iter()
3551                                .filter_map(|v| {
3552                                    if let JValue::Number(n) = v {
3553                                        let idx = *n as i64;
3554                                        // Resolve negative indices
3555                                        let actual_idx = if idx < 0 { arr_len + idx } else { idx };
3556                                        // Only include valid indices
3557                                        if actual_idx >= 0 && actual_idx < arr_len {
3558                                            Some(actual_idx)
3559                                        } else {
3560                                            None
3561                                        }
3562                                    } else {
3563                                        None
3564                                    }
3565                                })
3566                                .collect();
3567
3568                            // Sort and deduplicate indices
3569                            resolved_indices.sort();
3570                            resolved_indices.dedup();
3571
3572                            // Select elements at each sorted index
3573                            let result: Vec<JValue> = resolved_indices
3574                                .iter()
3575                                .map(|&idx| arr[idx as usize].clone())
3576                                .collect();
3577
3578                            return Ok(JValue::array(result));
3579                        }
3580                    }
3581                    Ok(_) => {
3582                        // Evaluated successfully but not a number or array - might be a filter
3583                        // Fall through to filter logic
3584                    }
3585                    Err(_) => {
3586                        // Evaluation failed - it's likely a filter expression
3587                        // Fall through to filter logic
3588                    }
3589                }
3590
3591                // It's a filter expression
3592                let mut filtered = Vec::new();
3593                for item in arr.iter() {
3594                    let item_result = self.evaluate_internal(predicate, item)?;
3595                    if self.is_truthy(&item_result) {
3596                        filtered.push(item.clone());
3597                    }
3598                }
3599                Ok(JValue::array(filtered))
3600            }
3601            JValue::Null => {
3602                // Null: return null
3603                Ok(JValue::Null)
3604            }
3605            other => {
3606                // Non-array values: treat as single-element conceptual array
3607                // For numeric predicates: index 0 returns the value, other indices return null
3608                // For boolean predicates: if truthy, return value; if falsy, return null
3609
3610                // Check if predicate is a numeric index
3611                if let AstNode::Number(n) = predicate {
3612                    // Index 0 returns the value, other indices return null
3613                    if *n == 0.0 {
3614                        return Ok(other.clone());
3615                    } else {
3616                        return Ok(JValue::Null);
3617                    }
3618                }
3619
3620                // Try to evaluate the predicate to see if it's a numeric index
3621                match self.evaluate_internal(predicate, other) {
3622                    Ok(JValue::Number(n)) => {
3623                        // Index 0 returns the value, other indices return null
3624                        if n == 0.0 {
3625                            Ok(other.clone())
3626                        } else {
3627                            Ok(JValue::Null)
3628                        }
3629                    }
3630                    Ok(pred_result) => {
3631                        // Boolean filter: return value if truthy, null if falsy
3632                        if self.is_truthy(&pred_result) {
3633                            Ok(other.clone())
3634                        } else {
3635                            Ok(JValue::Null)
3636                        }
3637                    }
3638                    Err(e) => Err(e),
3639                }
3640            }
3641        }
3642    }
3643
3644    /// Evaluate a path expression (e.g., foo.bar.baz)
3645    fn evaluate_path(
3646        &mut self,
3647        steps: &[PathStep],
3648        data: &JValue,
3649    ) -> Result<JValue, EvaluatorError> {
3650        // Avoid cloning by using references and only cloning when necessary
3651        if steps.is_empty() {
3652            return Ok(data.clone());
3653        }
3654
3655        // Fast path: single field access on object
3656        // This is a very common pattern, so optimize it
3657        if steps.len() == 1 {
3658            if let AstNode::Name(field_name) = &steps[0].node {
3659                return match data {
3660                    JValue::Object(obj) => {
3661                        // Check if this is a tuple - extract '@' value
3662                        if obj.get("__tuple__") == Some(&JValue::Bool(true)) {
3663                            if let Some(JValue::Object(inner)) = obj.get("@") {
3664                                Ok(inner.get(field_name).cloned().unwrap_or(JValue::Undefined))
3665                            } else {
3666                                Ok(JValue::Undefined)
3667                            }
3668                        } else {
3669                            Ok(obj.get(field_name).cloned().unwrap_or(JValue::Undefined))
3670                        }
3671                    }
3672                    JValue::Array(arr) => {
3673                        // Array mapping: extract field from each element
3674                        // Optimized: use references to access fields without cloning entire objects
3675                        // Check first element for tuple-ness (tuples are all-or-nothing)
3676                        let has_tuples = arr.first().is_some_and(|item| {
3677                            matches!(item, JValue::Object(obj) if obj.get("__tuple__") == Some(&JValue::Bool(true)))
3678                        });
3679
3680                        if !has_tuples {
3681                            // Fast path: no tuples, just direct field lookups
3682                            let mut result = Vec::with_capacity(arr.len());
3683                            for item in arr.iter() {
3684                                if let JValue::Object(obj) = item {
3685                                    if let Some(val) = obj.get(field_name) {
3686                                        if !val.is_null() {
3687                                            match val {
3688                                                JValue::Array(arr_val) => {
3689                                                    result.extend(arr_val.iter().cloned());
3690                                                }
3691                                                other => result.push(other.clone()),
3692                                            }
3693                                        }
3694                                    }
3695                                } else if let JValue::Array(inner_arr) = item {
3696                                    let nested_result = self.evaluate_path(
3697                                        &[PathStep::new(AstNode::Name(field_name.clone()))],
3698                                        &JValue::Array(inner_arr.clone()),
3699                                    )?;
3700                                    match nested_result {
3701                                        JValue::Array(nested) => {
3702                                            result.extend(nested.iter().cloned());
3703                                        }
3704                                        JValue::Null => {}
3705                                        other => result.push(other),
3706                                    }
3707                                }
3708                            }
3709
3710                            if result.is_empty() {
3711                                Ok(JValue::Null)
3712                            } else if result.len() == 1 {
3713                                Ok(result.into_iter().next().unwrap())
3714                            } else {
3715                                Ok(JValue::array(result))
3716                            }
3717                        } else {
3718                            // Tuple path: per-element tuple handling
3719                            let mut result = Vec::new();
3720                            for item in arr.iter() {
3721                                match item {
3722                                    JValue::Object(obj) => {
3723                                        let is_tuple =
3724                                            obj.get("__tuple__") == Some(&JValue::Bool(true));
3725
3726                                        if is_tuple {
3727                                            let inner = match obj.get("@") {
3728                                                Some(JValue::Object(inner)) => inner,
3729                                                _ => continue,
3730                                            };
3731
3732                                            if let Some(val) = inner.get(field_name) {
3733                                                if !val.is_null() {
3734                                                    // Build tuple wrapper - only clone bindings when needed
3735                                                    let wrap = |v: JValue| -> JValue {
3736                                                        let mut wrapper = IndexMap::new();
3737                                                        wrapper.insert("@".to_string(), v);
3738                                                        wrapper.insert(
3739                                                            "__tuple__".to_string(),
3740                                                            JValue::Bool(true),
3741                                                        );
3742                                                        for (k, v) in obj.iter() {
3743                                                            if k.starts_with('$') {
3744                                                                wrapper
3745                                                                    .insert(k.clone(), v.clone());
3746                                                            }
3747                                                        }
3748                                                        JValue::object(wrapper)
3749                                                    };
3750
3751                                                    match val {
3752                                                        JValue::Array(arr_val) => {
3753                                                            for item in arr_val.iter() {
3754                                                                result.push(wrap(item.clone()));
3755                                                            }
3756                                                        }
3757                                                        other => result.push(wrap(other.clone())),
3758                                                    }
3759                                                }
3760                                            }
3761                                        } else {
3762                                            // Non-tuple: access field directly by reference, only clone the field value
3763                                            if let Some(val) = obj.get(field_name) {
3764                                                if !val.is_null() {
3765                                                    match val {
3766                                                        JValue::Array(arr_val) => {
3767                                                            for item in arr_val.iter() {
3768                                                                result.push(item.clone());
3769                                                            }
3770                                                        }
3771                                                        other => result.push(other.clone()),
3772                                                    }
3773                                                }
3774                                            }
3775                                        }
3776                                    }
3777                                    JValue::Array(inner_arr) => {
3778                                        // Recursively map over nested array
3779                                        let nested_result = self.evaluate_path(
3780                                            &[PathStep::new(AstNode::Name(field_name.clone()))],
3781                                            &JValue::Array(inner_arr.clone()),
3782                                        )?;
3783                                        // Add nested result to our results
3784                                        match nested_result {
3785                                            JValue::Array(nested) => {
3786                                                // Flatten nested arrays from recursive mapping
3787                                                result.extend(nested.iter().cloned());
3788                                            }
3789                                            JValue::Null => {} // Skip nulls from nested arrays
3790                                            other => result.push(other),
3791                                        }
3792                                    }
3793                                    _ => {} // Skip non-object items
3794                                }
3795                            }
3796
3797                            // Return array result
3798                            // JSONata singleton unwrapping: if we have exactly one result,
3799                            // unwrap it (even if it's an array)
3800                            if result.is_empty() {
3801                                Ok(JValue::Null)
3802                            } else if result.len() == 1 {
3803                                Ok(result.into_iter().next().unwrap())
3804                            } else {
3805                                Ok(JValue::array(result))
3806                            }
3807                        } // end else (tuple path)
3808                    }
3809                    _ => Ok(JValue::Null),
3810                };
3811            }
3812        }
3813
3814        // Fast path: 2-step $variable.field with no stages
3815        // Handles common patterns like $l.rating, $item.price in sort/HOF bodies
3816        if steps.len() == 2 && steps[0].stages.is_empty() && steps[1].stages.is_empty() {
3817            if let (AstNode::Variable(var_name), AstNode::Name(field_name)) =
3818                (&steps[0].node, &steps[1].node)
3819            {
3820                if !var_name.is_empty() {
3821                    if let Some(value) = self.context.lookup(var_name) {
3822                        match value {
3823                            JValue::Object(obj) => {
3824                                return Ok(obj.get(field_name).cloned().unwrap_or(JValue::Null));
3825                            }
3826                            JValue::Array(arr) => {
3827                                // Map field extraction over array (same as single-step Name on Array)
3828                                let mut result = Vec::with_capacity(arr.len());
3829                                for item in arr.iter() {
3830                                    if let JValue::Object(obj) = item {
3831                                        if let Some(val) = obj.get(field_name) {
3832                                            if !val.is_null() {
3833                                                match val {
3834                                                    JValue::Array(inner) => {
3835                                                        result.extend(inner.iter().cloned());
3836                                                    }
3837                                                    other => result.push(other.clone()),
3838                                                }
3839                                            }
3840                                        }
3841                                    }
3842                                }
3843                                return match result.len() {
3844                                    0 => Ok(JValue::Null),
3845                                    1 => Ok(result.pop().unwrap()),
3846                                    _ => Ok(JValue::array(result)),
3847                                };
3848                            }
3849                            _ => {} // Fall through to general path evaluation
3850                        }
3851                    }
3852                }
3853            }
3854        }
3855
3856        // Track whether we did array mapping (for singleton unwrapping)
3857        let mut did_array_mapping = false;
3858
3859        // For the first step, work with a reference
3860        let mut current: JValue = match &steps[0].node {
3861            AstNode::Wildcard => {
3862                // Wildcard as first step
3863                match data {
3864                    JValue::Object(obj) => {
3865                        let mut result = Vec::new();
3866                        for value in obj.values() {
3867                            // Flatten arrays into the result
3868                            match value {
3869                                JValue::Array(arr) => result.extend(arr.iter().cloned()),
3870                                _ => result.push(value.clone()),
3871                            }
3872                        }
3873                        JValue::array(result)
3874                    }
3875                    JValue::Array(arr) => JValue::Array(arr.clone()),
3876                    _ => JValue::Null,
3877                }
3878            }
3879            AstNode::Descendant => {
3880                // Descendant as first step
3881                let descendants = self.collect_descendants(data);
3882                JValue::array(descendants)
3883            }
3884            AstNode::ParentVariable(name) => {
3885                // Parent variable as first step
3886                let parent_data = self.context.get_parent().ok_or_else(|| {
3887                    EvaluatorError::ReferenceError("Parent context not available".to_string())
3888                })?;
3889
3890                if name.is_empty() {
3891                    // $$ alone returns parent context
3892                    parent_data.clone()
3893                } else {
3894                    // $$field accesses field on parent
3895                    match parent_data {
3896                        JValue::Object(obj) => obj.get(name).cloned().unwrap_or(JValue::Null),
3897                        _ => JValue::Null,
3898                    }
3899                }
3900            }
3901            AstNode::Name(field_name) => {
3902                // Field/property access - get the stages for this step
3903                let stages = &steps[0].stages;
3904
3905                match data {
3906                    JValue::Object(obj) => {
3907                        let val = obj.get(field_name).cloned().unwrap_or(JValue::Undefined);
3908                        // Apply any stages to the extracted value
3909                        if !stages.is_empty() {
3910                            self.apply_stages(val, stages)?
3911                        } else {
3912                            val
3913                        }
3914                    }
3915                    JValue::Array(arr) => {
3916                        // Array mapping: extract field from each element and apply stages
3917                        let mut result = Vec::new();
3918                        for item in arr.iter() {
3919                            match item {
3920                                JValue::Object(obj) => {
3921                                    let val =
3922                                        obj.get(field_name).cloned().unwrap_or(JValue::Undefined);
3923                                    if !val.is_null() && !val.is_undefined() {
3924                                        if !stages.is_empty() {
3925                                            // Apply stages to the extracted value
3926                                            let processed_val = self.apply_stages(val, stages)?;
3927                                            // Stages always return an array (or null); extend results
3928                                            match processed_val {
3929                                                JValue::Array(arr) => {
3930                                                    result.extend(arr.iter().cloned())
3931                                                }
3932                                                JValue::Null => {} // Skip nulls from stage application
3933                                                other => result.push(other), // Shouldn't happen, but handle it
3934                                            }
3935                                        } else {
3936                                            // No stages: flatten arrays, push scalars
3937                                            match val {
3938                                                JValue::Array(arr) => {
3939                                                    result.extend(arr.iter().cloned())
3940                                                }
3941                                                other => result.push(other),
3942                                            }
3943                                        }
3944                                    }
3945                                }
3946                                JValue::Array(inner_arr) => {
3947                                    // Recursively map over nested array
3948                                    let nested_result = self.evaluate_path(
3949                                        &[steps[0].clone()],
3950                                        &JValue::Array(inner_arr.clone()),
3951                                    )?;
3952                                    match nested_result {
3953                                        JValue::Array(nested) => {
3954                                            result.extend(nested.iter().cloned())
3955                                        }
3956                                        JValue::Null => {} // Skip nulls from nested arrays
3957                                        other => result.push(other),
3958                                    }
3959                                }
3960                                _ => {} // Skip non-object items
3961                            }
3962                        }
3963                        JValue::array(result)
3964                    }
3965                    JValue::Null => JValue::Null,
3966                    // Accessing field on non-object returns undefined (not an error)
3967                    _ => JValue::Undefined,
3968                }
3969            }
3970            AstNode::String(string_literal) => {
3971                // String literal in path context - evaluate as literal and apply stages
3972                // This handles cases like "Red"[true] where "Red" is a literal, not a field access
3973                let stages = &steps[0].stages;
3974                let val = JValue::string(string_literal.clone());
3975
3976                if !stages.is_empty() {
3977                    // Apply stages (predicates) to the string literal
3978                    let result = self.apply_stages(val, stages)?;
3979                    // Unwrap single-element arrays back to scalar
3980                    // (string literals with predicates should return scalar or null, not arrays)
3981                    match result {
3982                        JValue::Array(arr) if arr.len() == 1 => arr[0].clone(),
3983                        JValue::Array(arr) if arr.is_empty() => JValue::Null,
3984                        other => other,
3985                    }
3986                } else {
3987                    val
3988                }
3989            }
3990            AstNode::Predicate(pred_expr) => {
3991                // Predicate as first step
3992                self.evaluate_predicate(data, pred_expr)?
3993            }
3994            AstNode::IndexBind { .. } => {
3995                // Index binding as first step - evaluate the IndexBind to create tuple array
3996                self.evaluate_internal(&steps[0].node, data)?
3997            }
3998            _ => {
3999                // Complex first step - evaluate it
4000                self.evaluate_path_step(&steps[0].node, data, data)?
4001            }
4002        };
4003
4004        // Process remaining steps
4005        for step in steps[1..].iter() {
4006            // Early return if current is null/undefined - no point continuing
4007            // This handles cases like `blah.{}` where blah doesn't exist
4008            if current.is_null() {
4009                return Ok(JValue::Null);
4010            }
4011            if current.is_undefined() {
4012                return Ok(JValue::Undefined);
4013            }
4014
4015            // Check if current is a tuple array - if so, we need to bind tuple variables
4016            // to context so they're available in nested expressions (like predicates)
4017            let is_tuple_array = if let JValue::Array(arr) = &current {
4018                arr.first().is_some_and(|first| {
4019                    if let JValue::Object(obj) = first {
4020                        obj.get("__tuple__") == Some(&JValue::Bool(true))
4021                    } else {
4022                        false
4023                    }
4024                })
4025            } else {
4026                false
4027            };
4028
4029            // For tuple arrays with certain step types, we need special handling to bind
4030            // tuple variables to context so they're available in nested expressions.
4031            // This is needed for:
4032            // - Object constructors: {"label": $$.items[$i]} needs $i in context
4033            // - Function applications: .($$.items[$i]) needs $i in context
4034            // - Variable lookups: .$i needs to find the tuple binding
4035            //
4036            // Steps like Name (field access) already have proper tuple handling in their
4037            // specific cases, so we don't intercept those here.
4038            let needs_tuple_context_binding = is_tuple_array
4039                && matches!(
4040                    &step.node,
4041                    AstNode::Object(_) | AstNode::FunctionApplication(_) | AstNode::Variable(_)
4042                );
4043
4044            if needs_tuple_context_binding {
4045                if let JValue::Array(arr) = &current {
4046                    let mut results = Vec::new();
4047
4048                    for tuple in arr.iter() {
4049                        if let JValue::Object(tuple_obj) = tuple {
4050                            // Extract tuple bindings (variables starting with $)
4051                            let bindings: Vec<(String, JValue)> = tuple_obj
4052                                .iter()
4053                                .filter(|(k, _)| k.starts_with('$') && k.len() > 1) // $i, $j, etc.
4054                                .map(|(k, v)| (k[1..].to_string(), v.clone())) // Remove $ prefix for context binding
4055                                .collect();
4056
4057                            // Save current bindings
4058                            let saved_bindings: Vec<(String, Option<JValue>)> = bindings
4059                                .iter()
4060                                .map(|(name, _)| (name.clone(), self.context.lookup(name).cloned()))
4061                                .collect();
4062
4063                            // Bind tuple variables to context
4064                            for (name, value) in &bindings {
4065                                self.context.bind(name.clone(), value.clone());
4066                            }
4067
4068                            // Get the actual value from the tuple (@ field)
4069                            let actual_data = tuple_obj.get("@").cloned().unwrap_or(JValue::Null);
4070
4071                            // Evaluate the step
4072                            let step_result = match &step.node {
4073                                AstNode::Variable(_) => {
4074                                    // Variable lookup - check context (which now has bindings)
4075                                    self.evaluate_internal(&step.node, tuple)?
4076                                }
4077                                AstNode::Object(_) | AstNode::FunctionApplication(_) => {
4078                                    // Object constructor or function application - evaluate on actual data
4079                                    self.evaluate_internal(&step.node, &actual_data)?
4080                                }
4081                                _ => unreachable!(), // We only match specific types above
4082                            };
4083
4084                            // Restore previous bindings
4085                            for (name, saved_value) in &saved_bindings {
4086                                if let Some(value) = saved_value {
4087                                    self.context.bind(name.clone(), value.clone());
4088                                } else {
4089                                    self.context.unbind(name);
4090                                }
4091                            }
4092
4093                            // Collect result
4094                            if !step_result.is_null() && !step_result.is_undefined() {
4095                                // For object constructors, collect results directly
4096                                // For other steps, handle arrays
4097                                if matches!(&step.node, AstNode::Object(_)) {
4098                                    results.push(step_result);
4099                                } else if matches!(step_result, JValue::Array(_)) {
4100                                    if let JValue::Array(arr) = step_result {
4101                                        results.extend(arr.iter().cloned());
4102                                    }
4103                                } else {
4104                                    results.push(step_result);
4105                                }
4106                            }
4107                        }
4108                    }
4109
4110                    current = JValue::array(results);
4111                    continue; // Skip the regular step processing
4112                }
4113            }
4114
4115            current = match &step.node {
4116                AstNode::Wildcard => {
4117                    // Wildcard in path
4118                    let stages = &step.stages;
4119                    let wildcard_result = match &current {
4120                        JValue::Object(obj) => {
4121                            let mut result = Vec::new();
4122                            for value in obj.values() {
4123                                // Flatten arrays into the result
4124                                match value {
4125                                    JValue::Array(arr) => result.extend(arr.iter().cloned()),
4126                                    _ => result.push(value.clone()),
4127                                }
4128                            }
4129                            JValue::array(result)
4130                        }
4131                        JValue::Array(arr) => {
4132                            // Map wildcard over array
4133                            let mut all_values = Vec::new();
4134                            for item in arr.iter() {
4135                                match item {
4136                                    JValue::Object(obj) => {
4137                                        for value in obj.values() {
4138                                            // Flatten arrays
4139                                            match value {
4140                                                JValue::Array(arr) => {
4141                                                    all_values.extend(arr.iter().cloned())
4142                                                }
4143                                                _ => all_values.push(value.clone()),
4144                                            }
4145                                        }
4146                                    }
4147                                    JValue::Array(inner) => {
4148                                        all_values.extend(inner.iter().cloned());
4149                                    }
4150                                    _ => {}
4151                                }
4152                            }
4153                            JValue::array(all_values)
4154                        }
4155                        _ => JValue::Null,
4156                    };
4157
4158                    // Apply stages (predicates) if present
4159                    if !stages.is_empty() {
4160                        self.apply_stages(wildcard_result, stages)?
4161                    } else {
4162                        wildcard_result
4163                    }
4164                }
4165                AstNode::Descendant => {
4166                    // Descendant in path
4167                    match &current {
4168                        JValue::Array(arr) => {
4169                            // Collect descendants from all array elements
4170                            let mut all_descendants = Vec::new();
4171                            for item in arr.iter() {
4172                                all_descendants.extend(self.collect_descendants(item));
4173                            }
4174                            JValue::array(all_descendants)
4175                        }
4176                        _ => {
4177                            // Collect descendants from current value
4178                            let descendants = self.collect_descendants(&current);
4179                            JValue::array(descendants)
4180                        }
4181                    }
4182                }
4183                AstNode::Name(field_name) => {
4184                    // Navigate into object field or map over array, applying stages
4185                    let stages = &step.stages;
4186
4187                    match &current {
4188                        JValue::Object(obj) => {
4189                            // Single object field extraction - NOT array mapping
4190                            // This resets did_array_mapping because we're extracting from
4191                            // a single value, not mapping over an array. The field's value
4192                            // (even if it's an array) should be preserved as-is.
4193                            did_array_mapping = false;
4194                            let val = obj.get(field_name).cloned().unwrap_or(JValue::Undefined);
4195                            // Apply stages if present
4196                            if !stages.is_empty() {
4197                                self.apply_stages(val, stages)?
4198                            } else {
4199                                val
4200                            }
4201                        }
4202                        JValue::Array(arr) => {
4203                            // Array mapping: extract field from each element and apply stages
4204                            did_array_mapping = true; // Track that we did array mapping
4205
4206                            // Fast path: if no elements are tuples and no stages,
4207                            // skip all tuple checking overhead (common case for products.price etc.)
4208                            // Tuples are all-or-nothing (created by index binding #$i),
4209                            // so checking only the first element is sufficient.
4210                            let has_tuples = arr.first().is_some_and(|item| {
4211                                matches!(item, JValue::Object(obj) if obj.get("__tuple__") == Some(&JValue::Bool(true)))
4212                            });
4213
4214                            if !has_tuples && stages.is_empty() {
4215                                let mut result = Vec::with_capacity(arr.len());
4216                                for item in arr.iter() {
4217                                    match item {
4218                                        JValue::Object(obj) => {
4219                                            if let Some(val) = obj.get(field_name) {
4220                                                if !val.is_null() {
4221                                                    match val {
4222                                                        JValue::Array(arr_val) => {
4223                                                            result.extend(arr_val.iter().cloned())
4224                                                        }
4225                                                        other => result.push(other.clone()),
4226                                                    }
4227                                                }
4228                                            }
4229                                        }
4230                                        JValue::Array(_) => {
4231                                            let nested_result =
4232                                                self.evaluate_path(&[step.clone()], item)?;
4233                                            match nested_result {
4234                                                JValue::Array(nested) => {
4235                                                    result.extend(nested.iter().cloned())
4236                                                }
4237                                                JValue::Null => {}
4238                                                other => result.push(other),
4239                                            }
4240                                        }
4241                                        _ => {}
4242                                    }
4243                                }
4244                                JValue::array(result)
4245                            } else {
4246                                // Full path with tuple support and stages
4247                                let mut result = Vec::new();
4248
4249                                for item in arr.iter() {
4250                                    match item {
4251                                        JValue::Object(obj) => {
4252                                            // Check if this is a tuple stream element
4253                                            let (actual_obj, tuple_bindings) = if obj
4254                                                .get("__tuple__")
4255                                                == Some(&JValue::Bool(true))
4256                                            {
4257                                                // This is a tuple - extract '@' value and preserve bindings
4258                                                if let Some(JValue::Object(inner)) = obj.get("@") {
4259                                                    // Collect index bindings (variables starting with $)
4260                                                    let bindings: Vec<(String, JValue)> = obj
4261                                                        .iter()
4262                                                        .filter(|(k, _)| k.starts_with('$'))
4263                                                        .map(|(k, v)| (k.clone(), v.clone()))
4264                                                        .collect();
4265                                                    (inner.clone(), Some(bindings))
4266                                                } else {
4267                                                    continue; // Invalid tuple
4268                                                }
4269                                            } else {
4270                                                (obj.clone(), None)
4271                                            };
4272
4273                                            let val = actual_obj
4274                                                .get(field_name)
4275                                                .cloned()
4276                                                .unwrap_or(JValue::Null);
4277
4278                                            if !val.is_null() {
4279                                                // Helper to wrap value in tuple if we have bindings
4280                                                let wrap_in_tuple = |v: JValue, bindings: &Option<Vec<(String, JValue)>>| -> JValue {
4281                                                    if let Some(b) = bindings {
4282                                                        let mut wrapper = IndexMap::new();
4283                                                        wrapper.insert("@".to_string(), v);
4284                                                        wrapper.insert("__tuple__".to_string(), JValue::Bool(true));
4285                                                        for (k, val) in b {
4286                                                            wrapper.insert(k.clone(), val.clone());
4287                                                        }
4288                                                        JValue::object(wrapper)
4289                                                    } else {
4290                                                        v
4291                                                    }
4292                                                };
4293
4294                                                if !stages.is_empty() {
4295                                                    // Apply stages to the extracted value
4296                                                    let processed_val =
4297                                                        self.apply_stages(val, stages)?;
4298                                                    // Stages always return an array (or null); extend results
4299                                                    match processed_val {
4300                                                        JValue::Array(arr) => {
4301                                                            for item in arr.iter() {
4302                                                                result.push(wrap_in_tuple(
4303                                                                    item.clone(),
4304                                                                    &tuple_bindings,
4305                                                                ));
4306                                                            }
4307                                                        }
4308                                                        JValue::Null => {} // Skip nulls from stage application
4309                                                        other => result.push(wrap_in_tuple(
4310                                                            other,
4311                                                            &tuple_bindings,
4312                                                        )),
4313                                                    }
4314                                                } else {
4315                                                    // No stages: flatten arrays, push scalars
4316                                                    // But preserve tuple bindings!
4317                                                    match val {
4318                                                        JValue::Array(arr) => {
4319                                                            for item in arr.iter() {
4320                                                                result.push(wrap_in_tuple(
4321                                                                    item.clone(),
4322                                                                    &tuple_bindings,
4323                                                                ));
4324                                                            }
4325                                                        }
4326                                                        other => result.push(wrap_in_tuple(
4327                                                            other,
4328                                                            &tuple_bindings,
4329                                                        )),
4330                                                    }
4331                                                }
4332                                            }
4333                                        }
4334                                        JValue::Array(_) => {
4335                                            // Recursively map over nested array
4336                                            let nested_result =
4337                                                self.evaluate_path(&[step.clone()], item)?;
4338                                            match nested_result {
4339                                                JValue::Array(nested) => {
4340                                                    result.extend(nested.iter().cloned())
4341                                                }
4342                                                JValue::Null => {}
4343                                                other => result.push(other),
4344                                            }
4345                                        }
4346                                        _ => {}
4347                                    }
4348                                }
4349
4350                                JValue::array(result)
4351                            }
4352                        }
4353                        JValue::Null => JValue::Null,
4354                        // Accessing field on non-object returns undefined (not an error)
4355                        _ => JValue::Undefined,
4356                    }
4357                }
4358                AstNode::String(string_literal) => {
4359                    // String literal as a path step - evaluate as literal and apply stages
4360                    let stages = &step.stages;
4361                    let val = JValue::string(string_literal.clone());
4362
4363                    if !stages.is_empty() {
4364                        // Apply stages (predicates) to the string literal
4365                        let result = self.apply_stages(val, stages)?;
4366                        // Unwrap single-element arrays back to scalar
4367                        match result {
4368                            JValue::Array(arr) if arr.len() == 1 => arr[0].clone(),
4369                            JValue::Array(arr) if arr.is_empty() => JValue::Null,
4370                            other => other,
4371                        }
4372                    } else {
4373                        val
4374                    }
4375                }
4376                AstNode::Predicate(pred_expr) => {
4377                    // Predicate in path - filter or index into current value
4378                    self.evaluate_predicate(&current, pred_expr)?
4379                }
4380                AstNode::ArrayGroup(elements) => {
4381                    // Array grouping: map expression over array but keep results grouped
4382                    // .[expr] means evaluate expr for each array element
4383                    match &current {
4384                        JValue::Array(arr) => {
4385                            let mut result = Vec::new();
4386                            for item in arr.iter() {
4387                                // For each array item, evaluate all elements and collect results
4388                                let mut group_values = Vec::new();
4389                                for element in elements {
4390                                    let value = self.evaluate_internal(element, item)?;
4391                                    // If the element is an Array/ArrayGroup, preserve its structure (don't flatten)
4392                                    // This ensures [[expr]] produces properly nested arrays
4393                                    let should_preserve_array = matches!(
4394                                        element,
4395                                        AstNode::Array(_) | AstNode::ArrayGroup(_)
4396                                    );
4397
4398                                    if should_preserve_array {
4399                                        // Keep the array as a single element to preserve nesting
4400                                        group_values.push(value);
4401                                    } else {
4402                                        // Flatten the value into group_values
4403                                        match value {
4404                                            JValue::Array(arr) => {
4405                                                group_values.extend(arr.iter().cloned())
4406                                            }
4407                                            other => group_values.push(other),
4408                                        }
4409                                    }
4410                                }
4411                                // Each array element gets its own sub-array with all values
4412                                result.push(JValue::array(group_values));
4413                            }
4414                            JValue::array(result)
4415                        }
4416                        _ => {
4417                            // For non-arrays, just evaluate the array constructor normally
4418                            let mut result = Vec::new();
4419                            for element in elements {
4420                                let value = self.evaluate_internal(element, &current)?;
4421                                result.push(value);
4422                            }
4423                            JValue::array(result)
4424                        }
4425                    }
4426                }
4427                AstNode::FunctionApplication(expr) => {
4428                    // Function application: map expr over the current value
4429                    // .(expr) means evaluate expr for each element, with $ bound to that element
4430                    // Null/undefined results are filtered out
4431                    match &current {
4432                        JValue::Array(arr) => {
4433                            // Produce the mapped result (compiled fast path or tree-walker fallback).
4434                            // Do NOT return early — singleton unwrapping is applied by the outer
4435                            // path evaluation code after all steps are processed.
4436                            let mapped: Vec<JValue> = if let Some(compiled) = try_compile_expr(expr)
4437                            {
4438                                let shape = arr.first().and_then(build_shape_cache);
4439                                let mut result = Vec::with_capacity(arr.len());
4440                                for item in arr.iter() {
4441                                    let value = if let Some(ref s) = shape {
4442                                        eval_compiled_shaped(&compiled, item, None, s)?
4443                                    } else {
4444                                        eval_compiled(&compiled, item, None)?
4445                                    };
4446                                    if !value.is_null() && !value.is_undefined() {
4447                                        result.push(value);
4448                                    }
4449                                }
4450                                result
4451                            } else {
4452                                let mut result = Vec::new();
4453                                for item in arr.iter() {
4454                                    // Save the current $ binding
4455                                    let saved_dollar = self.context.lookup("$").cloned();
4456
4457                                    // Bind $ to the current item
4458                                    self.context.bind("$".to_string(), item.clone());
4459
4460                                    // Evaluate the expression in the context of this item
4461                                    let value = self.evaluate_internal(expr, item)?;
4462
4463                                    // Restore the previous $ binding
4464                                    if let Some(saved) = saved_dollar {
4465                                        self.context.bind("$".to_string(), saved);
4466                                    } else {
4467                                        self.context.unbind("$");
4468                                    }
4469
4470                                    // Only include non-null/undefined values
4471                                    if !value.is_null() && !value.is_undefined() {
4472                                        result.push(value);
4473                                    }
4474                                }
4475                                result
4476                            };
4477                            // Don't do singleton unwrapping here - let the path result
4478                            // handling deal with it, which respects has_explicit_array_keep
4479                            JValue::array(mapped)
4480                        }
4481                        _ => {
4482                            // For non-arrays, bind $ and evaluate
4483                            let saved_dollar = self.context.lookup("$").cloned();
4484                            self.context.bind("$".to_string(), current.clone());
4485
4486                            let value = self.evaluate_internal(expr, &current)?;
4487
4488                            if let Some(saved) = saved_dollar {
4489                                self.context.bind("$".to_string(), saved);
4490                            } else {
4491                                self.context.unbind("$");
4492                            }
4493
4494                            value
4495                        }
4496                    }
4497                }
4498                AstNode::Sort { terms, .. } => {
4499                    // Sort as a path step - sort 'current' by the terms
4500                    self.evaluate_sort(&current, terms)?
4501                }
4502                AstNode::IndexBind { variable, .. } => {
4503                    // Index binding as a path step - creates tuple stream from current
4504                    // This wraps each element with an index binding
4505                    match &current {
4506                        JValue::Array(arr) => {
4507                            let mut result = Vec::new();
4508                            for (idx, item) in arr.iter().enumerate() {
4509                                let mut wrapper = IndexMap::new();
4510                                wrapper.insert("@".to_string(), item.clone());
4511                                wrapper
4512                                    .insert(format!("${}", variable), JValue::Number(idx as f64));
4513                                wrapper.insert("__tuple__".to_string(), JValue::Bool(true));
4514                                result.push(JValue::object(wrapper));
4515                            }
4516                            JValue::array(result)
4517                        }
4518                        other => {
4519                            // Single value: wrap with index 0
4520                            let mut wrapper = IndexMap::new();
4521                            wrapper.insert("@".to_string(), other.clone());
4522                            wrapper.insert(format!("${}", variable), JValue::from(0i64));
4523                            wrapper.insert("__tuple__".to_string(), JValue::Bool(true));
4524                            JValue::object(wrapper)
4525                        }
4526                    }
4527                }
4528                // Handle complex path steps (e.g., computed properties, object construction)
4529                _ => self.evaluate_path_step(&step.node, &current, data)?,
4530            };
4531        }
4532
4533        // JSONata singleton unwrapping: singleton results are unwrapped when we did array operations
4534        // BUT NOT when there's an explicit array-keeping operation like [] (empty predicate)
4535
4536        // Check for explicit array-keeping operations
4537        // Empty predicate [] can be represented as:
4538        // 1. Predicate(Boolean(true)) as a path step node
4539        // 2. Stage::Filter(Boolean(true)) as a stage
4540        let has_explicit_array_keep = steps.iter().any(|step| {
4541            // Check if the step itself is an empty predicate
4542            if let AstNode::Predicate(pred) = &step.node {
4543                if matches!(**pred, AstNode::Boolean(true)) {
4544                    return true;
4545                }
4546            }
4547            // Check if any stage is an empty predicate
4548            step.stages.iter().any(|stage| {
4549                let crate::ast::Stage::Filter(pred) = stage;
4550                matches!(**pred, AstNode::Boolean(true))
4551            })
4552        });
4553
4554        // Unwrap when:
4555        // 1. Any step has stages (predicates, sorts, etc.) which are array operations, OR
4556        // 2. We did array mapping during step evaluation (tracked via did_array_mapping flag)
4557        //    Note: did_array_mapping is reset to false when extracting from a single object,
4558        //    so a[0].b where a[0] returns a single object and .b extracts a field will NOT unwrap.
4559        // BUT NOT when there's an explicit array-keeping operation
4560        //
4561        // Important: We DON'T unwrap just because original data was an array - what matters is
4562        // whether the final extraction was from an array mapping context or a single object.
4563        let should_unwrap = !has_explicit_array_keep
4564            && (steps.iter().any(|step| !step.stages.is_empty()) || did_array_mapping);
4565
4566        let result = match &current {
4567            // Empty arrays become null/undefined
4568            JValue::Array(arr) if arr.is_empty() => JValue::Null,
4569            // Unwrap singleton arrays when appropriate
4570            JValue::Array(arr) if arr.len() == 1 && should_unwrap => arr[0].clone(),
4571            // Keep arrays otherwise
4572            _ => current,
4573        };
4574
4575        Ok(result)
4576    }
4577
4578    /// Helper to evaluate a complex path step
4579    fn evaluate_path_step(
4580        &mut self,
4581        step: &AstNode,
4582        current: &JValue,
4583        original_data: &JValue,
4584    ) -> Result<JValue, EvaluatorError> {
4585        // Special case: array mapping with object construction
4586        // e.g., items.{"name": name, "price": price}
4587        if matches!(current, JValue::Array(_)) && matches!(step, AstNode::Object(_)) {
4588            match (current, step) {
4589                (JValue::Array(arr), AstNode::Object(pairs)) => {
4590                    // Try CompiledExpr for object construction (handles arithmetic, conditionals, etc.)
4591                    if let Some(compiled) = try_compile_expr(&AstNode::Object(pairs.clone())) {
4592                        let shape = arr.first().and_then(build_shape_cache);
4593                        let mut mapped = Vec::with_capacity(arr.len());
4594                        for item in arr.iter() {
4595                            let result = if let Some(ref s) = shape {
4596                                eval_compiled_shaped(&compiled, item, None, s)?
4597                            } else {
4598                                eval_compiled(&compiled, item, None)?
4599                            };
4600                            if !result.is_undefined() {
4601                                mapped.push(result);
4602                            }
4603                        }
4604                        return Ok(JValue::array(mapped));
4605                    }
4606                    // Fallback: full AST evaluation per element
4607                    let mapped: Result<Vec<JValue>, EvaluatorError> = arr
4608                        .iter()
4609                        .map(|item| self.evaluate_internal(step, item))
4610                        .collect();
4611                    Ok(JValue::array(mapped?))
4612                }
4613                _ => unreachable!(),
4614            }
4615        } else {
4616            // Special case: array.$ should map $ over the array, returning each element
4617            // e.g., [1, 2, 3].$ returns [1, 2, 3]
4618            if let AstNode::Variable(name) = step {
4619                if name.is_empty() {
4620                    // Bare $ - map over array if current is an array
4621                    if let JValue::Array(arr) = current {
4622                        // Map $ over each element - $ refers to each element in turn
4623                        return Ok(JValue::Array(arr.clone()));
4624                    } else {
4625                        // For non-arrays, $ refers to the current value
4626                        return Ok(current.clone());
4627                    }
4628                }
4629            }
4630
4631            // Special case: Variable access on tuple arrays (from index binding #$var)
4632            // When current is a tuple array, we need to evaluate the variable against each tuple
4633            // so that tuple bindings ($i, etc.) can be found
4634            if matches!(step, AstNode::Variable(_)) {
4635                if let JValue::Array(arr) = current {
4636                    // Check if this is a tuple array
4637                    let is_tuple_array = arr.first().is_some_and(|first| {
4638                        if let JValue::Object(obj) = first {
4639                            obj.get("__tuple__") == Some(&JValue::Bool(true))
4640                        } else {
4641                            false
4642                        }
4643                    });
4644
4645                    if is_tuple_array {
4646                        // Map the variable lookup over each tuple
4647                        let mut results = Vec::new();
4648                        for tuple in arr.iter() {
4649                            // Evaluate the variable in the context of this tuple
4650                            // This allows tuple bindings ($i, etc.) to be found
4651                            let val = self.evaluate_internal(step, tuple)?;
4652                            if !val.is_null() && !val.is_undefined() {
4653                                results.push(val);
4654                            }
4655                        }
4656                        return Ok(JValue::array(results));
4657                    }
4658                }
4659            }
4660
4661            // For certain operations (Binary, Function calls, Variables, ParentVariables, Arrays, Objects, Sort, Blocks), the step evaluates to a new value
4662            // rather than being used to index/access the current value
4663            // e.g., items[price > 50] where [price > 50] is a filter operation
4664            // or $x.price where $x is a variable binding
4665            // or $$.field where $$ is the parent context
4666            // or [0..9] where it's an array constructor
4667            // or $^(field) where it's a sort operator
4668            // or (expr).field where (expr) is a block that evaluates to a value
4669            if matches!(
4670                step,
4671                AstNode::Binary { .. }
4672                    | AstNode::Function { .. }
4673                    | AstNode::Variable(_)
4674                    | AstNode::ParentVariable(_)
4675                    | AstNode::Array(_)
4676                    | AstNode::Object(_)
4677                    | AstNode::Sort { .. }
4678                    | AstNode::Block(_)
4679            ) {
4680                // Evaluate the step in the context of original_data and return the result directly
4681                return self.evaluate_internal(step, original_data);
4682            }
4683
4684            // Standard path step evaluation for indexing/accessing current value
4685            let step_value = self.evaluate_internal(step, original_data)?;
4686            Ok(match (current, &step_value) {
4687                (JValue::Object(obj), JValue::String(key)) => {
4688                    obj.get(&**key).cloned().unwrap_or(JValue::Undefined)
4689                }
4690                (JValue::Array(arr), JValue::Number(n)) => {
4691                    let index = *n as i64;
4692                    let len = arr.len() as i64;
4693
4694                    // Handle negative indexing (offset from end)
4695                    let actual_idx = if index < 0 { len + index } else { index };
4696
4697                    if actual_idx < 0 || actual_idx >= len {
4698                        JValue::Undefined
4699                    } else {
4700                        arr[actual_idx as usize].clone()
4701                    }
4702                }
4703                _ => JValue::Undefined,
4704            })
4705        }
4706    }
4707
4708    /// Evaluate a binary operation
4709    fn evaluate_binary_op(
4710        &mut self,
4711        op: crate::ast::BinaryOp,
4712        lhs: &AstNode,
4713        rhs: &AstNode,
4714        data: &JValue,
4715    ) -> Result<JValue, EvaluatorError> {
4716        use crate::ast::BinaryOp;
4717
4718        // Special handling for coalescing operator (??)
4719        // Returns right side if left is undefined (produces no value)
4720        // Note: literal null is a value, so it's NOT replaced
4721        if op == BinaryOp::Coalesce {
4722            // Try to evaluate the left side
4723            return match self.evaluate_internal(lhs, data) {
4724                Ok(value) => {
4725                    // Successfully evaluated to a value (even if it's null)
4726                    // Check if LHS is a literal null - keep it (null is a value, not undefined)
4727                    if matches!(lhs, AstNode::Null) {
4728                        Ok(value)
4729                    }
4730                    // For paths and variables, undefined (no match/unbound) - use RHS
4731                    else if value.is_undefined()
4732                        && (matches!(lhs, AstNode::Path { .. })
4733                            || matches!(lhs, AstNode::String(_))
4734                            || matches!(lhs, AstNode::Variable(_)))
4735                    {
4736                        self.evaluate_internal(rhs, data)
4737                    } else {
4738                        Ok(value)
4739                    }
4740                }
4741                Err(_) => {
4742                    // Evaluation failed (e.g., undefined variable) - use RHS
4743                    self.evaluate_internal(rhs, data)
4744                }
4745            };
4746        }
4747
4748        // Special handling for default operator (?:)
4749        // Returns right side if left is falsy or a non-value (like a function)
4750        if op == BinaryOp::Default {
4751            let left = self.evaluate_internal(lhs, data)?;
4752            if self.is_truthy_for_default(&left) {
4753                return Ok(left);
4754            }
4755            return self.evaluate_internal(rhs, data);
4756        }
4757
4758        // Special handling for chain/pipe operator (~>)
4759        // Pipes the LHS result to the RHS function as the first argument
4760        // e.g., expr ~> func(arg2) becomes func(expr, arg2)
4761        if op == BinaryOp::ChainPipe {
4762            // Handle regex on RHS - treat as $match(lhs, regex)
4763            if let AstNode::Regex { pattern, flags } = rhs {
4764                // Evaluate LHS
4765                let lhs_value = self.evaluate_internal(lhs, data)?;
4766                // Do regex match inline
4767                return match lhs_value {
4768                    JValue::String(s) => {
4769                        // Build the regex
4770                        let case_insensitive = flags.contains('i');
4771                        let regex_pattern = if case_insensitive {
4772                            format!("(?i){}", pattern)
4773                        } else {
4774                            pattern.clone()
4775                        };
4776                        match regex::Regex::new(&regex_pattern) {
4777                            Ok(re) => {
4778                                if let Some(m) = re.find(&s) {
4779                                    // Return match object
4780                                    let mut result = IndexMap::new();
4781                                    result.insert(
4782                                        "match".to_string(),
4783                                        JValue::string(m.as_str().to_string()),
4784                                    );
4785                                    result.insert(
4786                                        "start".to_string(),
4787                                        JValue::Number(m.start() as f64),
4788                                    );
4789                                    result
4790                                        .insert("end".to_string(), JValue::Number(m.end() as f64));
4791
4792                                    // Capture groups
4793                                    let mut groups = Vec::new();
4794                                    for cap in re.captures_iter(&s).take(1) {
4795                                        for i in 1..cap.len() {
4796                                            if let Some(c) = cap.get(i) {
4797                                                groups.push(JValue::string(c.as_str().to_string()));
4798                                            }
4799                                        }
4800                                    }
4801                                    if !groups.is_empty() {
4802                                        result.insert("groups".to_string(), JValue::array(groups));
4803                                    }
4804
4805                                    Ok(JValue::object(result))
4806                                } else {
4807                                    Ok(JValue::Null)
4808                                }
4809                            }
4810                            Err(e) => Err(EvaluatorError::EvaluationError(format!(
4811                                "Invalid regex: {}",
4812                                e
4813                            ))),
4814                        }
4815                    }
4816                    JValue::Null => Ok(JValue::Null),
4817                    _ => Err(EvaluatorError::TypeError(
4818                        "Left side of ~> /regex/ must be a string".to_string(),
4819                    )),
4820                };
4821            }
4822
4823            // Early check: if LHS evaluates to undefined, return undefined
4824            // This matches JSONata behavior where undefined ~> anyFunc returns undefined
4825            let lhs_value_for_check = self.evaluate_internal(lhs, data)?;
4826            if lhs_value_for_check.is_undefined() || lhs_value_for_check.is_null() {
4827                return Ok(JValue::Undefined);
4828            }
4829
4830            // Handle different RHS types
4831            match rhs {
4832                AstNode::Function {
4833                    name,
4834                    args,
4835                    is_builtin,
4836                } => {
4837                    // RHS is a function call
4838                    // Check if the function call has placeholder arguments (partial application)
4839                    let has_placeholder =
4840                        args.iter().any(|arg| matches!(arg, AstNode::Placeholder));
4841
4842                    if has_placeholder {
4843                        // Partial application: replace the first placeholder with LHS value
4844                        let lhs_value = self.evaluate_internal(lhs, data)?;
4845                        let mut filled_args = Vec::new();
4846                        let mut lhs_used = false;
4847
4848                        for arg in args.iter() {
4849                            if matches!(arg, AstNode::Placeholder) && !lhs_used {
4850                                // Replace first placeholder with evaluated LHS
4851                                // We need to create a temporary binding to pass the value
4852                                let temp_name = format!("__pipe_arg_{}", filled_args.len());
4853                                self.context.bind(temp_name.clone(), lhs_value.clone());
4854                                filled_args.push(AstNode::Variable(temp_name));
4855                                lhs_used = true;
4856                            } else {
4857                                filled_args.push(arg.clone());
4858                            }
4859                        }
4860
4861                        // Evaluate the function with filled args
4862                        let result =
4863                            self.evaluate_function_call(name, &filled_args, *is_builtin, data);
4864
4865                        // Clean up temp bindings
4866                        for (i, arg) in args.iter().enumerate() {
4867                            if matches!(arg, AstNode::Placeholder) {
4868                                self.context.unbind(&format!("__pipe_arg_{}", i));
4869                            }
4870                        }
4871
4872                        // Unwrap singleton results from chain operator
4873                        return result.map(|v| self.unwrap_singleton(v));
4874                    } else {
4875                        // No placeholders: build args list with LHS as first argument
4876                        let mut all_args = vec![lhs.clone()];
4877                        all_args.extend_from_slice(args);
4878                        // Unwrap singleton results from chain operator
4879                        return self
4880                            .evaluate_function_call(name, &all_args, *is_builtin, data)
4881                            .map(|v| self.unwrap_singleton(v));
4882                    }
4883                }
4884                AstNode::Variable(var_name) => {
4885                    // RHS is a function reference (no parens)
4886                    // e.g., $average($tempReadings) ~> $round
4887                    let all_args = vec![lhs.clone()];
4888                    // Unwrap singleton results from chain operator
4889                    return self
4890                        .evaluate_function_call(var_name, &all_args, true, data)
4891                        .map(|v| self.unwrap_singleton(v));
4892                }
4893                AstNode::Binary {
4894                    op: BinaryOp::ChainPipe,
4895                    ..
4896                } => {
4897                    // RHS is another chain pipe - evaluate LHS first, then pipe through RHS
4898                    // e.g., x ~> (f1 ~> f2) => (x ~> f1) ~> f2
4899                    let lhs_value = self.evaluate_internal(lhs, data)?;
4900                    return self.evaluate_internal(rhs, &lhs_value);
4901                }
4902                AstNode::Transform { .. } => {
4903                    // RHS is a transform - invoke it with LHS as input
4904                    // Evaluate LHS first
4905                    let lhs_value = self.evaluate_internal(lhs, data)?;
4906
4907                    // Bind $ to the LHS value, then evaluate the transform
4908                    let saved_binding = self.context.lookup("$").cloned();
4909                    self.context.bind("$".to_string(), lhs_value.clone());
4910
4911                    let result = self.evaluate_internal(rhs, data);
4912
4913                    // Restore $ binding
4914                    if let Some(saved) = saved_binding {
4915                        self.context.bind("$".to_string(), saved);
4916                    } else {
4917                        self.context.unbind("$");
4918                    }
4919
4920                    // Unwrap singleton results from chain operator
4921                    return result.map(|v| self.unwrap_singleton(v));
4922                }
4923                AstNode::Lambda {
4924                    params,
4925                    body,
4926                    signature,
4927                    thunk,
4928                } => {
4929                    // RHS is a lambda - invoke it with LHS as argument
4930                    let lhs_value = self.evaluate_internal(lhs, data)?;
4931                    // Unwrap singleton results from chain operator
4932                    return self
4933                        .invoke_lambda(params, body, signature.as_ref(), &[lhs_value], data, *thunk)
4934                        .map(|v| self.unwrap_singleton(v));
4935                }
4936                AstNode::Path { steps } => {
4937                    // RHS is a path expression (e.g., function call with predicate: $map($f)[])
4938                    // If the first step is a function call, we need to add LHS as first argument
4939                    if let Some(first_step) = steps.first() {
4940                        match &first_step.node {
4941                            AstNode::Function {
4942                                name,
4943                                args,
4944                                is_builtin,
4945                            } => {
4946                                // Prepend LHS to the function arguments
4947                                let mut all_args = vec![lhs.clone()];
4948                                all_args.extend_from_slice(args);
4949
4950                                // Call the function
4951                                let mut result = self.evaluate_function_call(
4952                                    name,
4953                                    &all_args,
4954                                    *is_builtin,
4955                                    data,
4956                                )?;
4957
4958                                // Apply stages from the first step (e.g., predicates)
4959                                for stage in &first_step.stages {
4960                                    match stage {
4961                                        Stage::Filter(filter_expr) => {
4962                                            result = self.evaluate_predicate_as_stage(
4963                                                &result,
4964                                                filter_expr,
4965                                            )?;
4966                                        }
4967                                    }
4968                                }
4969
4970                                // Apply remaining path steps if any
4971                                if steps.len() > 1 {
4972                                    let remaining_path = AstNode::Path {
4973                                        steps: steps[1..].to_vec(),
4974                                    };
4975                                    result = self.evaluate_internal(&remaining_path, &result)?;
4976                                }
4977
4978                                // Unwrap singleton results from chain operator, unless there are stages
4979                                // Stages (like predicates) indicate we want to preserve array structure
4980                                if !first_step.stages.is_empty() || steps.len() > 1 {
4981                                    return Ok(result);
4982                                } else {
4983                                    return Ok(self.unwrap_singleton(result));
4984                                }
4985                            }
4986                            AstNode::Variable(var_name) => {
4987                                // Variable that should resolve to a function
4988                                let all_args = vec![lhs.clone()];
4989                                let mut result =
4990                                    self.evaluate_function_call(var_name, &all_args, true, data)?;
4991
4992                                // Apply stages from the first step
4993                                for stage in &first_step.stages {
4994                                    match stage {
4995                                        Stage::Filter(filter_expr) => {
4996                                            result = self.evaluate_predicate_as_stage(
4997                                                &result,
4998                                                filter_expr,
4999                                            )?;
5000                                        }
5001                                    }
5002                                }
5003
5004                                // Apply remaining path steps if any
5005                                if steps.len() > 1 {
5006                                    let remaining_path = AstNode::Path {
5007                                        steps: steps[1..].to_vec(),
5008                                    };
5009                                    result = self.evaluate_internal(&remaining_path, &result)?;
5010                                }
5011
5012                                // Unwrap singleton results from chain operator, unless there are stages
5013                                // Stages (like predicates) indicate we want to preserve array structure
5014                                if !first_step.stages.is_empty() || steps.len() > 1 {
5015                                    return Ok(result);
5016                                } else {
5017                                    return Ok(self.unwrap_singleton(result));
5018                                }
5019                            }
5020                            _ => {
5021                                // Other path types - just evaluate normally with LHS as context
5022                                let lhs_value = self.evaluate_internal(lhs, data)?;
5023                                return self
5024                                    .evaluate_internal(rhs, &lhs_value)
5025                                    .map(|v| self.unwrap_singleton(v));
5026                            }
5027                        }
5028                    }
5029
5030                    // Empty path? Shouldn't happen, but handle it
5031                    let lhs_value = self.evaluate_internal(lhs, data)?;
5032                    return self
5033                        .evaluate_internal(rhs, &lhs_value)
5034                        .map(|v| self.unwrap_singleton(v));
5035                }
5036                _ => {
5037                    return Err(EvaluatorError::TypeError(
5038                        "Right side of ~> must be a function call or function reference"
5039                            .to_string(),
5040                    ));
5041                }
5042            }
5043        }
5044
5045        // Special handling for variable binding (:=)
5046        if op == BinaryOp::ColonEqual {
5047            // Extract variable name from LHS
5048            let var_name = match lhs {
5049                AstNode::Variable(name) => name.clone(),
5050                _ => {
5051                    return Err(EvaluatorError::TypeError(
5052                        "Left side of := must be a variable".to_string(),
5053                    ))
5054                }
5055            };
5056
5057            // Check if RHS is a lambda - store it specially
5058            if let AstNode::Lambda {
5059                params,
5060                body,
5061                signature,
5062                thunk,
5063            } = rhs
5064            {
5065                // Store the lambda AST for later invocation
5066                // Capture only the free variables referenced by the lambda body
5067                let captured_env = self.capture_environment_for(body, params);
5068                let compiled_body = if !thunk {
5069                    let var_refs: Vec<&str> = params.iter().map(|s| s.as_str()).collect();
5070                    try_compile_expr_with_allowed_vars(body, &var_refs)
5071                } else {
5072                    None
5073                };
5074                let stored_lambda = StoredLambda {
5075                    params: params.clone(),
5076                    body: (**body).clone(),
5077                    compiled_body,
5078                    signature: signature.clone(),
5079                    captured_env,
5080                    captured_data: Some(data.clone()),
5081                    thunk: *thunk,
5082                };
5083                let lambda_params = stored_lambda.params.clone();
5084                let lambda_sig = stored_lambda.signature.clone();
5085                self.context.bind_lambda(var_name.clone(), stored_lambda);
5086
5087                // Return a lambda marker value (include _lambda_id so it can be found later)
5088                let lambda_repr = JValue::lambda(
5089                    var_name.as_str(),
5090                    lambda_params,
5091                    Some(var_name.clone()),
5092                    lambda_sig,
5093                );
5094                return Ok(lambda_repr);
5095            }
5096
5097            // Check if RHS is a pure function composition (ChainPipe between function references)
5098            // e.g., $uppertrim := $trim ~> $uppercase
5099            // This creates a lambda that composes the functions.
5100            // But NOT for data ~> function, which should be evaluated immediately.
5101            // e.g., $result := data ~> $map($fn) should evaluate the pipe
5102            if let AstNode::Binary {
5103                op: BinaryOp::ChainPipe,
5104                lhs: chain_lhs,
5105                rhs: chain_rhs,
5106            } = rhs
5107            {
5108                // Only wrap in lambda if LHS is a function reference (Variable pointing to a function)
5109                // If LHS is data (array, object, function call result, etc.), evaluate the pipe
5110                let is_function_composition = match chain_lhs.as_ref() {
5111                    // LHS is a function reference like $trim or $sum
5112                    AstNode::Variable(name)
5113                        if self.is_builtin_function(name)
5114                            || self.context.lookup_lambda(name).is_some() =>
5115                    {
5116                        true
5117                    }
5118                    // LHS is another ChainPipe (nested composition like $f ~> $g ~> $h)
5119                    AstNode::Binary {
5120                        op: BinaryOp::ChainPipe,
5121                        ..
5122                    } => true,
5123                    // A function call with placeholder creates a partial application
5124                    // e.g., $substringAfter(?, "@") ~> $substringBefore(?, ".")
5125                    AstNode::Function { args, .. }
5126                        if args.iter().any(|a| matches!(a, AstNode::Placeholder)) =>
5127                    {
5128                        true
5129                    }
5130                    // Anything else (data, function calls, arrays, etc.) is not pure composition
5131                    _ => false,
5132                };
5133
5134                if is_function_composition {
5135                    // Create a lambda: function($) { ($ ~> firstFunc) ~> restOfChain }
5136                    // The original chain is $trim ~> $uppercase (left-associative)
5137                    // We want to create: ($ ~> $trim) ~> $uppercase
5138                    let param_name = "$".to_string();
5139
5140                    // First create $ ~> $trim
5141                    let first_pipe = AstNode::Binary {
5142                        op: BinaryOp::ChainPipe,
5143                        lhs: Box::new(AstNode::Variable(param_name.clone())),
5144                        rhs: chain_lhs.clone(),
5145                    };
5146
5147                    // Then wrap with ~> $uppercase (or the rest of the chain)
5148                    let composed_body = AstNode::Binary {
5149                        op: BinaryOp::ChainPipe,
5150                        lhs: Box::new(first_pipe),
5151                        rhs: chain_rhs.clone(),
5152                    };
5153
5154                    let stored_lambda = StoredLambda {
5155                        params: vec![param_name],
5156                        body: composed_body,
5157                        compiled_body: None, // ChainPipe body is not compilable
5158                        signature: None,
5159                        captured_env: self.capture_current_environment(),
5160                        captured_data: Some(data.clone()),
5161                        thunk: false,
5162                    };
5163                    self.context.bind_lambda(var_name.clone(), stored_lambda);
5164
5165                    // Return a lambda marker value (include _lambda_id for later lookup)
5166                    let lambda_repr = JValue::lambda(
5167                        var_name.as_str(),
5168                        vec!["$".to_string()],
5169                        Some(var_name.clone()),
5170                        None::<String>,
5171                    );
5172                    return Ok(lambda_repr);
5173                }
5174                // If not function composition, fall through to normal evaluation below
5175            }
5176
5177            // Evaluate the RHS
5178            let value = self.evaluate_internal(rhs, data)?;
5179
5180            // If the value is a lambda, copy the stored lambda to the new variable name
5181            if let Some(stored) = self.lookup_lambda_from_value(&value) {
5182                self.context.bind_lambda(var_name.clone(), stored);
5183            }
5184
5185            // Bind even if undefined (null) so inner scopes can shadow outer variables
5186            self.context.bind(var_name, value.clone());
5187            return Ok(value);
5188        }
5189
5190        // Special handling for 'In' operator - check for array filtering
5191        // Must evaluate lhs first to determine if this is array filtering
5192        if op == BinaryOp::In {
5193            let left = self.evaluate_internal(lhs, data)?;
5194
5195            // Check if this is array filtering: array[predicate]
5196            if matches!(left, JValue::Array(_)) {
5197                // Try evaluating rhs in current context to see if it's a simple index
5198                let right_result = self.evaluate_internal(rhs, data);
5199
5200                if let Ok(JValue::Number(_)) = right_result {
5201                    // Simple numeric index: array[n]
5202                    return self.array_index(&left, &right_result.unwrap());
5203                } else {
5204                    // This is array filtering: array[predicate]
5205                    // Evaluate the predicate for each array item
5206                    return self.array_filter(lhs, rhs, &left, data);
5207                }
5208            }
5209        }
5210
5211        // Special handling for logical operators (short-circuit evaluation)
5212        if op == BinaryOp::And {
5213            let left = self.evaluate_internal(lhs, data)?;
5214            if !self.is_truthy(&left) {
5215                // Short-circuit: if left is falsy, return false without evaluating right
5216                return Ok(JValue::Bool(false));
5217            }
5218            let right = self.evaluate_internal(rhs, data)?;
5219            return Ok(JValue::Bool(self.is_truthy(&right)));
5220        }
5221
5222        if op == BinaryOp::Or {
5223            let left = self.evaluate_internal(lhs, data)?;
5224            if self.is_truthy(&left) {
5225                // Short-circuit: if left is truthy, return true without evaluating right
5226                return Ok(JValue::Bool(true));
5227            }
5228            let right = self.evaluate_internal(rhs, data)?;
5229            return Ok(JValue::Bool(self.is_truthy(&right)));
5230        }
5231
5232        // Check if operands are explicit null literals (vs undefined from variables)
5233        let left_is_explicit_null = matches!(lhs, AstNode::Null);
5234        let right_is_explicit_null = matches!(rhs, AstNode::Null);
5235
5236        // Standard evaluation: evaluate both operands
5237        let left = self.evaluate_internal(lhs, data)?;
5238        let right = self.evaluate_internal(rhs, data)?;
5239
5240        match op {
5241            BinaryOp::Add => self.add(&left, &right, left_is_explicit_null, right_is_explicit_null),
5242            BinaryOp::Subtract => {
5243                self.subtract(&left, &right, left_is_explicit_null, right_is_explicit_null)
5244            }
5245            BinaryOp::Multiply => {
5246                self.multiply(&left, &right, left_is_explicit_null, right_is_explicit_null)
5247            }
5248            BinaryOp::Divide => {
5249                self.divide(&left, &right, left_is_explicit_null, right_is_explicit_null)
5250            }
5251            BinaryOp::Modulo => {
5252                self.modulo(&left, &right, left_is_explicit_null, right_is_explicit_null)
5253            }
5254
5255            BinaryOp::Equal => Ok(JValue::Bool(self.equals(&left, &right))),
5256            BinaryOp::NotEqual => Ok(JValue::Bool(!self.equals(&left, &right))),
5257            BinaryOp::LessThan => {
5258                self.less_than(&left, &right, left_is_explicit_null, right_is_explicit_null)
5259            }
5260            BinaryOp::LessThanOrEqual => self.less_than_or_equal(
5261                &left,
5262                &right,
5263                left_is_explicit_null,
5264                right_is_explicit_null,
5265            ),
5266            BinaryOp::GreaterThan => {
5267                self.greater_than(&left, &right, left_is_explicit_null, right_is_explicit_null)
5268            }
5269            BinaryOp::GreaterThanOrEqual => self.greater_than_or_equal(
5270                &left,
5271                &right,
5272                left_is_explicit_null,
5273                right_is_explicit_null,
5274            ),
5275
5276            // And/Or handled above with short-circuit evaluation
5277            BinaryOp::And | BinaryOp::Or => unreachable!(),
5278
5279            BinaryOp::Concatenate => self.concatenate(&left, &right),
5280            BinaryOp::Range => self.range(&left, &right),
5281            BinaryOp::In => self.in_operator(&left, &right),
5282
5283            // These operators are all handled as special cases earlier in evaluate_binary_op
5284            BinaryOp::ColonEqual | BinaryOp::Coalesce | BinaryOp::Default | BinaryOp::ChainPipe => {
5285                unreachable!()
5286            }
5287        }
5288    }
5289
5290    /// Evaluate a unary operation
5291    fn evaluate_unary_op(
5292        &mut self,
5293        op: crate::ast::UnaryOp,
5294        operand: &AstNode,
5295        data: &JValue,
5296    ) -> Result<JValue, EvaluatorError> {
5297        use crate::ast::UnaryOp;
5298
5299        let value = self.evaluate_internal(operand, data)?;
5300
5301        match op {
5302            UnaryOp::Negate => match value {
5303                // undefined returns undefined
5304                JValue::Null | JValue::Undefined => Ok(JValue::Null),
5305                JValue::Number(n) => Ok(JValue::Number(-n)),
5306                _ => Err(EvaluatorError::TypeError(
5307                    "D1002: Cannot negate non-number value".to_string(),
5308                )),
5309            },
5310            UnaryOp::Not => Ok(JValue::Bool(!self.is_truthy(&value))),
5311        }
5312    }
5313
5314    /// Try to fuse an aggregate function call with its Path argument.
5315    /// Handles patterns like:
5316    /// - $sum(arr.field) → iterate arr, extract field, accumulate
5317    /// - $sum(arr[pred].field) → iterate arr, filter, extract, accumulate
5318    ///
5319    /// Returns None if the pattern doesn't match (falls back to normal evaluation).
5320    fn try_fused_aggregate(
5321        &mut self,
5322        name: &str,
5323        arg: &AstNode,
5324        data: &JValue,
5325    ) -> Result<Option<JValue>, EvaluatorError> {
5326        // Only applies to numeric aggregates
5327        if !matches!(name, "sum" | "max" | "min" | "average") {
5328            return Ok(None);
5329        }
5330
5331        // Argument must be a Path
5332        let AstNode::Path { steps } = arg else {
5333            return Ok(None);
5334        };
5335
5336        // Pattern: Name(arr).Name(field) — extract field from array, aggregate
5337        // Pattern: Name(arr)[filter].Name(field) — filter, extract, aggregate
5338        if steps.len() != 2 {
5339            return Ok(None);
5340        }
5341
5342        // Last step must be a simple Name (the field to extract)
5343        let field_step = &steps[1];
5344        if !field_step.stages.is_empty() {
5345            return Ok(None);
5346        }
5347        let AstNode::Name(extract_field) = &field_step.node else {
5348            return Ok(None);
5349        };
5350
5351        // First step: Name with optional filter stage
5352        let arr_step = &steps[0];
5353        let AstNode::Name(arr_name) = &arr_step.node else {
5354            return Ok(None);
5355        };
5356
5357        // Get the source array from data
5358        let arr = match data {
5359            JValue::Object(obj) => match obj.get(arr_name) {
5360                Some(JValue::Array(arr)) => arr,
5361                _ => return Ok(None),
5362            },
5363            _ => return Ok(None),
5364        };
5365
5366        // Check for filter stage — try CompiledExpr for the predicate
5367        let filter_compiled = match arr_step.stages.as_slice() {
5368            [] => None,
5369            [Stage::Filter(pred)] => try_compile_expr(pred),
5370            _ => return Ok(None),
5371        };
5372        // If filter stage exists but wasn't compilable, bail out
5373        if !arr_step.stages.is_empty() && filter_compiled.is_none() {
5374            return Ok(None);
5375        }
5376
5377        // Build shape cache for the array
5378        let shape = arr.first().and_then(build_shape_cache);
5379
5380        // Fused iteration: filter (optional) + extract + aggregate
5381        let mut total = 0.0f64;
5382        let mut count = 0usize;
5383        let mut max_val = f64::NEG_INFINITY;
5384        let mut min_val = f64::INFINITY;
5385        let mut has_any = false;
5386
5387        for item in arr.iter() {
5388            // Apply compiled filter if present
5389            if let Some(ref compiled) = filter_compiled {
5390                let result = if let Some(ref s) = shape {
5391                    eval_compiled_shaped(compiled, item, None, s)?
5392                } else {
5393                    eval_compiled(compiled, item, None)?
5394                };
5395                if !compiled_is_truthy(&result) {
5396                    continue;
5397                }
5398            }
5399
5400            // Extract field value
5401            let val = match item {
5402                JValue::Object(obj) => match obj.get(extract_field) {
5403                    Some(JValue::Number(n)) => *n,
5404                    Some(_) | None => continue, // Skip non-numeric / missing
5405                },
5406                _ => continue,
5407            };
5408
5409            has_any = true;
5410            match name {
5411                "sum" => total += val,
5412                "max" => max_val = max_val.max(val),
5413                "min" => min_val = min_val.min(val),
5414                "average" => {
5415                    total += val;
5416                    count += 1;
5417                }
5418                _ => unreachable!(),
5419            }
5420        }
5421
5422        if !has_any {
5423            return Ok(Some(match name {
5424                "sum" => JValue::from(0i64),
5425                "average" | "max" | "min" => JValue::Null,
5426                _ => unreachable!(),
5427            }));
5428        }
5429
5430        Ok(Some(match name {
5431            "sum" => JValue::Number(total),
5432            "max" => JValue::Number(max_val),
5433            "min" => JValue::Number(min_val),
5434            "average" => JValue::Number(total / count as f64),
5435            _ => unreachable!(),
5436        }))
5437    }
5438
5439    /// Evaluate a function call
5440    fn evaluate_function_call(
5441        &mut self,
5442        name: &str,
5443        args: &[AstNode],
5444        is_builtin: bool,
5445        data: &JValue,
5446    ) -> Result<JValue, EvaluatorError> {
5447        use crate::functions;
5448
5449        // Check for partial application (any argument is a Placeholder)
5450        let has_placeholder = args.iter().any(|arg| matches!(arg, AstNode::Placeholder));
5451        if has_placeholder {
5452            return self.create_partial_application(name, args, is_builtin, data);
5453        }
5454
5455        // FIRST check if this variable holds a function value (lambda or builtin reference)
5456        // This is critical for:
5457        // 1. Allowing function parameters to shadow stored lambdas
5458        //    (e.g., Y-combinator pattern: function($g){$g($g)} where parameter $g shadows outer $g)
5459        // 2. Calling built-in functions passed as parameters
5460        //    (e.g., λ($f){$f(5)}($sum) where $f is bound to $sum reference)
5461        if let Some(value) = self.context.lookup(name).cloned() {
5462            if let Some(stored_lambda) = self.lookup_lambda_from_value(&value) {
5463                let mut evaluated_args = Vec::with_capacity(args.len());
5464                for arg in args {
5465                    evaluated_args.push(self.evaluate_internal(arg, data)?);
5466                }
5467                return self.invoke_stored_lambda(&stored_lambda, &evaluated_args, data);
5468            }
5469            if let JValue::Builtin { name: builtin_name } = &value {
5470                // This is a built-in function reference (e.g., $f bound to $sum)
5471                let mut evaluated_args = Vec::with_capacity(args.len());
5472                for arg in args {
5473                    evaluated_args.push(self.evaluate_internal(arg, data)?);
5474                }
5475                return self.call_builtin_with_values(builtin_name, &evaluated_args);
5476            }
5477        }
5478
5479        // THEN check if this is a stored lambda (user-defined function by name)
5480        // This only applies if not shadowed by a binding above
5481        if let Some(stored_lambda) = self.context.lookup_lambda(name).cloned() {
5482            let mut evaluated_args = Vec::with_capacity(args.len());
5483            for arg in args {
5484                evaluated_args.push(self.evaluate_internal(arg, data)?);
5485            }
5486            return self.invoke_stored_lambda(&stored_lambda, &evaluated_args, data);
5487        }
5488
5489        // If the function was called without $ prefix and it's not a stored lambda,
5490        // it's an error (unknown function without $ prefix)
5491        if !is_builtin && name != "__lambda__" {
5492            return Err(EvaluatorError::ReferenceError(format!(
5493                "Unknown function: {}",
5494                name
5495            )));
5496        }
5497
5498        // Special handling for $exists function
5499        // It needs to know if the argument is explicit null vs undefined
5500        if name == "exists" && args.len() == 1 {
5501            let arg = &args[0];
5502
5503            // Check if it's an explicit null literal
5504            if matches!(arg, AstNode::Null) {
5505                return Ok(JValue::Bool(true)); // Explicit null exists
5506            }
5507
5508            // Check if it's a function reference
5509            if let AstNode::Variable(var_name) = arg {
5510                if self.is_builtin_function(var_name) {
5511                    return Ok(JValue::Bool(true)); // Built-in function exists
5512                }
5513
5514                // Check if it's a stored lambda
5515                if self.context.lookup_lambda(var_name).is_some() {
5516                    return Ok(JValue::Bool(true)); // Lambda exists
5517                }
5518
5519                // Check if the variable is defined
5520                if let Some(val) = self.context.lookup(var_name) {
5521                    // A variable bound to the undefined marker doesn't "exist"
5522                    if val.is_undefined() {
5523                        return Ok(JValue::Bool(false));
5524                    }
5525                    return Ok(JValue::Bool(true)); // Variable is defined (even if null)
5526                } else {
5527                    return Ok(JValue::Bool(false)); // Variable is undefined
5528                }
5529            }
5530
5531            // For other expressions, evaluate and check if non-null/non-undefined
5532            let value = self.evaluate_internal(arg, data)?;
5533            return Ok(JValue::Bool(!value.is_null() && !value.is_undefined()));
5534        }
5535
5536        // Check if any arguments are undefined variables or undefined paths
5537        // Functions like $not() should return undefined when given undefined values
5538        for arg in args {
5539            // Check for undefined variable (e.g., $undefined_var)
5540            if let AstNode::Variable(var_name) = arg {
5541                // Skip built-in function names - they're function references, not undefined variables
5542                if !var_name.is_empty()
5543                    && !self.is_builtin_function(var_name)
5544                    && self.context.lookup(var_name).is_none()
5545                {
5546                    // Undefined variable - for functions that should propagate undefined
5547                    if propagates_undefined(name) {
5548                        return Ok(JValue::Null); // Return undefined
5549                    }
5550                }
5551            }
5552            // Check for simple field name (e.g., blah) that evaluates to undefined
5553            if let AstNode::Name(field_name) = arg {
5554                let field_exists =
5555                    matches!(data, JValue::Object(obj) if obj.contains_key(field_name));
5556                if !field_exists && propagates_undefined(name) {
5557                    return Ok(JValue::Null);
5558                }
5559            }
5560            // Note: AstNode::String represents string literals (e.g., "hello"), not field accesses.
5561            // Field accesses are represented as AstNode::Path. String literals should never
5562            // be checked for undefined propagation.
5563            // Check for Path expressions that evaluate to undefined
5564            if let AstNode::Path { steps } = arg {
5565                // For paths that evaluate to null, we need to determine if it's because:
5566                // 1. A field doesn't exist (undefined) - should propagate as undefined
5567                // 2. A field exists with value null - should throw T0410
5568                //
5569                // We can distinguish these by checking if the path is accessing a field
5570                // that doesn't exist on an object vs one that has an explicit null value.
5571                if let Ok(JValue::Null) = self.evaluate_internal(arg, data) {
5572                    // Path evaluated to null - now check if it's truly undefined
5573                    // For single-step paths, check if the field exists
5574                    if steps.len() == 1 {
5575                        // Get field name - could be Name (identifier) or String (quoted)
5576                        let field_name = match &steps[0].node {
5577                            AstNode::Name(n) => Some(n.as_str()),
5578                            AstNode::String(s) => Some(s.as_str()),
5579                            _ => None,
5580                        };
5581                        if let Some(field) = field_name {
5582                            match data {
5583                                JValue::Object(obj) => {
5584                                    if !obj.contains_key(field) {
5585                                        // Field doesn't exist - return undefined
5586                                        if propagates_undefined(name) {
5587                                            return Ok(JValue::Null);
5588                                        }
5589                                    }
5590                                    // Field exists with value null - continue to throw T0410
5591                                }
5592                                // Trying to access field on null data - return undefined
5593                                JValue::Null if propagates_undefined(name) => {
5594                                    return Ok(JValue::Null);
5595                                }
5596                                _ => {}
5597                            }
5598                        }
5599                    }
5600                    // For multi-step paths, check if any intermediate step failed
5601                    else if steps.len() > 1 {
5602                        // Evaluate each step to find where it breaks
5603                        let mut current = data;
5604                        let mut failed_due_to_missing_field = false;
5605
5606                        for (i, step) in steps.iter().enumerate() {
5607                            if let AstNode::Name(field_name) = &step.node {
5608                                match current {
5609                                    JValue::Object(obj) => {
5610                                        if let Some(val) = obj.get(field_name) {
5611                                            current = val;
5612                                        } else {
5613                                            // Field doesn't exist
5614                                            failed_due_to_missing_field = true;
5615                                            break;
5616                                        }
5617                                    }
5618                                    JValue::Array(_) => {
5619                                        // Array access - evaluate normally
5620                                        break;
5621                                    }
5622                                    JValue::Null => {
5623                                        // Hit null in the middle of the path
5624                                        if i > 0 {
5625                                            // Previous field had null value - not undefined
5626                                            failed_due_to_missing_field = false;
5627                                        }
5628                                        break;
5629                                    }
5630                                    _ => break,
5631                                }
5632                            }
5633                        }
5634
5635                        if failed_due_to_missing_field && propagates_undefined(name) {
5636                            return Ok(JValue::Null);
5637                        }
5638                    }
5639                }
5640            }
5641        }
5642
5643        // Fused aggregate pipeline: for $sum/$max/$min/$average with a single Path argument,
5644        // try to fuse filter+extract+aggregate into a single pass.
5645        if args.len() == 1 {
5646            if let Some(result) = self.try_fused_aggregate(name, &args[0], data)? {
5647                return Ok(result);
5648            }
5649        }
5650
5651        let mut evaluated_args = Vec::with_capacity(args.len());
5652        for arg in args {
5653            evaluated_args.push(self.evaluate_internal(arg, data)?);
5654        }
5655
5656        // JSONata feature: when a function is called with no arguments but expects
5657        // at least one, use the current context value (data) as the implicit first argument
5658        // This also applies when functions expecting N arguments receive N-1 arguments,
5659        // in which case the context value becomes the first argument
5660        let context_functions_zero_arg = ["string", "number", "boolean", "uppercase", "lowercase"];
5661        let context_functions_missing_first = [
5662            "substringBefore",
5663            "substringAfter",
5664            "contains",
5665            "split",
5666            "replace",
5667        ];
5668
5669        if evaluated_args.is_empty() && context_functions_zero_arg.contains(&name) {
5670            // Use the current context value as the implicit argument
5671            evaluated_args.push(data.clone());
5672        } else if evaluated_args.len() == 1 && context_functions_missing_first.contains(&name) {
5673            // These functions expect 2+ arguments, but received 1
5674            // Only insert context if it's a compatible type (string for string functions)
5675            // Otherwise, let the function throw T0411 for wrong argument count
5676            if matches!(data, JValue::String(_)) {
5677                evaluated_args.insert(0, data.clone());
5678            }
5679        }
5680
5681        // Special handling for $string() with no explicit arguments
5682        // After context insertion, check if the argument is null (undefined context)
5683        if name == "string"
5684            && args.is_empty()
5685            && !evaluated_args.is_empty()
5686            && evaluated_args[0].is_null()
5687        {
5688            // Context was null/undefined, so return undefined
5689            return Ok(JValue::Null);
5690        }
5691
5692        match name {
5693            "string" => {
5694                if evaluated_args.len() > 2 {
5695                    return Err(EvaluatorError::EvaluationError(
5696                        "string() takes at most 2 arguments".to_string(),
5697                    ));
5698                }
5699
5700                let prettify = if evaluated_args.len() == 2 {
5701                    match &evaluated_args[1] {
5702                        JValue::Bool(b) => Some(*b),
5703                        _ => {
5704                            return Err(EvaluatorError::TypeError(
5705                                "string() prettify parameter must be a boolean".to_string(),
5706                            ))
5707                        }
5708                    }
5709                } else {
5710                    None
5711                };
5712
5713                Ok(functions::string::string(&evaluated_args[0], prettify)?)
5714            }
5715            "length" => {
5716                if evaluated_args.len() != 1 {
5717                    return Err(EvaluatorError::EvaluationError(
5718                        "length() requires exactly 1 argument".to_string(),
5719                    ));
5720                }
5721                match &evaluated_args[0] {
5722                    JValue::String(s) => Ok(functions::string::length(s)?),
5723                    _ => Err(EvaluatorError::TypeError(
5724                        "T0410: Argument 1 of function length does not match function signature"
5725                            .to_string(),
5726                    )),
5727                }
5728            }
5729            "uppercase" => {
5730                if evaluated_args.len() != 1 {
5731                    return Err(EvaluatorError::EvaluationError(
5732                        "uppercase() requires exactly 1 argument".to_string(),
5733                    ));
5734                }
5735                if evaluated_args[0].is_undefined() {
5736                    return Ok(JValue::Undefined);
5737                }
5738                match &evaluated_args[0] {
5739                    JValue::String(s) => Ok(functions::string::uppercase(s)?),
5740                    _ => Err(EvaluatorError::TypeError(
5741                        "T0410: Argument 1 of function uppercase does not match function signature"
5742                            .to_string(),
5743                    )),
5744                }
5745            }
5746            "lowercase" => {
5747                if evaluated_args.len() != 1 {
5748                    return Err(EvaluatorError::EvaluationError(
5749                        "lowercase() requires exactly 1 argument".to_string(),
5750                    ));
5751                }
5752                if evaluated_args[0].is_undefined() {
5753                    return Ok(JValue::Undefined);
5754                }
5755                match &evaluated_args[0] {
5756                    JValue::String(s) => Ok(functions::string::lowercase(s)?),
5757                    _ => Err(EvaluatorError::TypeError(
5758                        "T0410: Argument 1 of function lowercase does not match function signature"
5759                            .to_string(),
5760                    )),
5761                }
5762            }
5763            "number" => {
5764                if evaluated_args.is_empty() {
5765                    return Err(EvaluatorError::EvaluationError(
5766                        "number() requires at least 1 argument".to_string(),
5767                    ));
5768                }
5769                if evaluated_args.len() > 1 {
5770                    return Err(EvaluatorError::TypeError(
5771                        "T0410: Argument 2 of function number does not match function signature"
5772                            .to_string(),
5773                    ));
5774                }
5775                if evaluated_args[0].is_undefined() {
5776                    return Ok(JValue::Undefined);
5777                }
5778                Ok(functions::numeric::number(&evaluated_args[0])?)
5779            }
5780            "sum" => {
5781                if evaluated_args.len() != 1 {
5782                    return Err(EvaluatorError::EvaluationError(
5783                        "sum() requires exactly 1 argument".to_string(),
5784                    ));
5785                }
5786                // Return undefined if argument is undefined
5787                if evaluated_args[0].is_undefined() {
5788                    return Ok(JValue::Undefined);
5789                }
5790                match &evaluated_args[0] {
5791                    JValue::Null => Ok(JValue::Null),
5792                    JValue::Array(arr) => {
5793                        // Use zero-clone iterator-based sum
5794                        Ok(aggregation::sum(arr)?)
5795                    }
5796                    // Non-array values: extract number directly
5797                    JValue::Number(n) => Ok(JValue::Number(*n)),
5798                    other => Ok(functions::numeric::sum(&[other.clone()])?),
5799                }
5800            }
5801            "count" => {
5802                if evaluated_args.len() != 1 {
5803                    return Err(EvaluatorError::EvaluationError(
5804                        "count() requires exactly 1 argument".to_string(),
5805                    ));
5806                }
5807                // Return 0 if argument is undefined
5808                if evaluated_args[0].is_undefined() {
5809                    return Ok(JValue::from(0i64));
5810                }
5811                match &evaluated_args[0] {
5812                    JValue::Null => Ok(JValue::from(0i64)), // null counts as 0
5813                    JValue::Array(arr) => Ok(functions::array::count(arr)?),
5814                    _ => Ok(JValue::from(1i64)), // Non-array value counts as 1
5815                }
5816            }
5817            "substring" => {
5818                if evaluated_args.len() < 2 || evaluated_args.len() > 3 {
5819                    return Err(EvaluatorError::EvaluationError(
5820                        "substring() requires 2 or 3 arguments".to_string(),
5821                    ));
5822                }
5823                if evaluated_args[0].is_undefined() {
5824                    return Ok(JValue::Undefined);
5825                }
5826                match (&evaluated_args[0], &evaluated_args[1]) {
5827                    (JValue::String(s), JValue::Number(start)) => {
5828                        let length = if evaluated_args.len() == 3 {
5829                            match &evaluated_args[2] {
5830                                JValue::Number(l) => Some(*l as i64),
5831                                _ => return Err(EvaluatorError::TypeError(
5832                                    "T0410: Argument 3 of function substring does not match function signature".to_string(),
5833                                )),
5834                            }
5835                        } else {
5836                            None
5837                        };
5838                        Ok(functions::string::substring(s, *start as i64, length)?)
5839                    }
5840                    (JValue::String(_), _) => Err(EvaluatorError::TypeError(
5841                        "T0410: Argument 2 of function substring does not match function signature"
5842                            .to_string(),
5843                    )),
5844                    _ => Err(EvaluatorError::TypeError(
5845                        "T0410: Argument 1 of function substring does not match function signature"
5846                            .to_string(),
5847                    )),
5848                }
5849            }
5850            "substringBefore" => {
5851                if evaluated_args.len() != 2 {
5852                    return Err(EvaluatorError::TypeError(
5853                        "T0411: Context value is not a compatible type with argument 2 of function substringBefore".to_string(),
5854                    ));
5855                }
5856                if evaluated_args[0].is_undefined() {
5857                    return Ok(JValue::Undefined);
5858                }
5859                match (&evaluated_args[0], &evaluated_args[1]) {
5860                    (JValue::String(s), JValue::String(sep)) => Ok(functions::string::substring_before(s, sep)?),
5861                    (JValue::String(_), _) => Err(EvaluatorError::TypeError(
5862                        "T0410: Argument 2 of function substringBefore does not match function signature".to_string(),
5863                    )),
5864                    _ => Err(EvaluatorError::TypeError(
5865                        "T0410: Argument 1 of function substringBefore does not match function signature".to_string(),
5866                    )),
5867                }
5868            }
5869            "substringAfter" => {
5870                if evaluated_args.len() != 2 {
5871                    return Err(EvaluatorError::TypeError(
5872                        "T0411: Context value is not a compatible type with argument 2 of function substringAfter".to_string(),
5873                    ));
5874                }
5875                if evaluated_args[0].is_undefined() {
5876                    return Ok(JValue::Undefined);
5877                }
5878                match (&evaluated_args[0], &evaluated_args[1]) {
5879                    (JValue::String(s), JValue::String(sep)) => Ok(functions::string::substring_after(s, sep)?),
5880                    (JValue::String(_), _) => Err(EvaluatorError::TypeError(
5881                        "T0410: Argument 2 of function substringAfter does not match function signature".to_string(),
5882                    )),
5883                    _ => Err(EvaluatorError::TypeError(
5884                        "T0410: Argument 1 of function substringAfter does not match function signature".to_string(),
5885                    )),
5886                }
5887            }
5888            "pad" => {
5889                if evaluated_args.is_empty() || evaluated_args.len() > 3 {
5890                    return Err(EvaluatorError::EvaluationError(
5891                        "pad() requires 2 or 3 arguments".to_string(),
5892                    ));
5893                }
5894
5895                // First argument: string to pad
5896                let string = match &evaluated_args[0] {
5897                    JValue::String(s) => s.clone(),
5898                    JValue::Null => return Ok(JValue::Null),
5899                    _ => {
5900                        return Err(EvaluatorError::TypeError(
5901                            "pad() first argument must be a string".to_string(),
5902                        ))
5903                    }
5904                };
5905
5906                // Second argument: width (negative = left pad, positive = right pad)
5907                let width = match &evaluated_args.get(1) {
5908                    Some(JValue::Number(n)) => *n as i32,
5909                    _ => {
5910                        return Err(EvaluatorError::TypeError(
5911                            "pad() second argument must be a number".to_string(),
5912                        ))
5913                    }
5914                };
5915
5916                // Third argument: padding string (optional, defaults to space)
5917                let pad_string = match evaluated_args.get(2) {
5918                    Some(JValue::String(s)) if !s.is_empty() => s.clone(),
5919                    _ => Rc::from(" "),
5920                };
5921
5922                let abs_width = width.unsigned_abs() as usize;
5923                // Count Unicode characters (code points), not bytes
5924                let char_count = string.chars().count();
5925
5926                if char_count >= abs_width {
5927                    // String is already long enough
5928                    return Ok(JValue::string(string));
5929                }
5930
5931                let padding_needed = abs_width - char_count;
5932
5933                let pad_chars: Vec<char> = pad_string.chars().collect();
5934                let mut padding = String::with_capacity(padding_needed);
5935                for i in 0..padding_needed {
5936                    padding.push(pad_chars[i % pad_chars.len()]);
5937                }
5938
5939                let result = if width < 0 {
5940                    // Left pad (negative width)
5941                    format!("{}{}", padding, string)
5942                } else {
5943                    // Right pad (positive width)
5944                    format!("{}{}", string, padding)
5945                };
5946
5947                Ok(JValue::string(result))
5948            }
5949
5950            "trim" => {
5951                if evaluated_args.is_empty() {
5952                    return Ok(JValue::Null); // undefined
5953                }
5954                if evaluated_args.len() != 1 {
5955                    return Err(EvaluatorError::EvaluationError(
5956                        "trim() requires at most 1 argument".to_string(),
5957                    ));
5958                }
5959                match &evaluated_args[0] {
5960                    JValue::Null => Ok(JValue::Null),
5961                    JValue::String(s) => Ok(functions::string::trim(s)?),
5962                    _ => Err(EvaluatorError::TypeError(
5963                        "trim() requires a string argument".to_string(),
5964                    )),
5965                }
5966            }
5967            "contains" => {
5968                if evaluated_args.len() != 2 {
5969                    return Err(EvaluatorError::EvaluationError(
5970                        "contains() requires exactly 2 arguments".to_string(),
5971                    ));
5972                }
5973                if evaluated_args[0].is_null() {
5974                    return Ok(JValue::Null);
5975                }
5976                if evaluated_args[0].is_undefined() {
5977                    return Ok(JValue::Undefined);
5978                }
5979                match &evaluated_args[0] {
5980                    JValue::String(s) => Ok(functions::string::contains(s, &evaluated_args[1])?),
5981                    _ => Err(EvaluatorError::TypeError(
5982                        "contains() requires a string as the first argument".to_string(),
5983                    )),
5984                }
5985            }
5986            "split" => {
5987                if evaluated_args.len() < 2 || evaluated_args.len() > 3 {
5988                    return Err(EvaluatorError::EvaluationError(
5989                        "split() requires 2 or 3 arguments".to_string(),
5990                    ));
5991                }
5992                if evaluated_args[0].is_null() {
5993                    return Ok(JValue::Null);
5994                }
5995                if evaluated_args[0].is_undefined() {
5996                    return Ok(JValue::Undefined);
5997                }
5998                match &evaluated_args[0] {
5999                    JValue::String(s) => {
6000                        let limit = if evaluated_args.len() == 3 {
6001                            match &evaluated_args[2] {
6002                                JValue::Number(n) => {
6003                                    let f = *n;
6004                                    // Negative limit is an error
6005                                    if f < 0.0 {
6006                                        return Err(EvaluatorError::EvaluationError(
6007                                            "D3020: Third argument of split function must be a positive number".to_string(),
6008                                        ));
6009                                    }
6010                                    // Floor the value for non-integer limits
6011                                    Some(f.floor() as usize)
6012                                }
6013                                _ => {
6014                                    return Err(EvaluatorError::TypeError(
6015                                        "split() limit must be a number".to_string(),
6016                                    ))
6017                                }
6018                            }
6019                        } else {
6020                            None
6021                        };
6022                        Ok(functions::string::split(s, &evaluated_args[1], limit)?)
6023                    }
6024                    _ => Err(EvaluatorError::TypeError(
6025                        "split() requires a string as the first argument".to_string(),
6026                    )),
6027                }
6028            }
6029            "join" => {
6030                // Special case: if first arg is undefined, return undefined
6031                // But if separator (2nd arg) is undefined, use empty string (default)
6032                if evaluated_args.is_empty() {
6033                    return Err(EvaluatorError::TypeError(
6034                        "T0410: Argument 1 of function $join does not match function signature"
6035                            .to_string(),
6036                    ));
6037                }
6038                if evaluated_args[0].is_null() {
6039                    return Ok(JValue::Null);
6040                }
6041                if evaluated_args[0].is_undefined() {
6042                    return Ok(JValue::Undefined);
6043                }
6044
6045                // Signature: <a<s>s?:s> - array of strings, optional separator, returns string
6046                // The signature handles coercion and validation
6047                use crate::signature::Signature;
6048
6049                let signature = Signature::parse("<a<s>s?:s>").map_err(|e| {
6050                    EvaluatorError::EvaluationError(format!("Invalid signature: {}", e))
6051                })?;
6052
6053                let coerced_args = match signature.validate_and_coerce(&evaluated_args) {
6054                    Ok(args) => args,
6055                    Err(crate::signature::SignatureError::UndefinedArgument) => {
6056                        // This can happen if the separator is undefined
6057                        // In that case, just validate the first arg and use default separator
6058                        let sig_first_arg = Signature::parse("<a<s>:a<s>>").map_err(|e| {
6059                            EvaluatorError::EvaluationError(format!("Invalid signature: {}", e))
6060                        })?;
6061
6062                        match sig_first_arg.validate_and_coerce(&evaluated_args[0..1]) {
6063                            Ok(args) => args,
6064                            Err(crate::signature::SignatureError::ArrayTypeMismatch {
6065                                index,
6066                                expected,
6067                            }) => {
6068                                return Err(EvaluatorError::TypeError(format!(
6069                                    "T0412: Argument {} of function $join must be an array of {}",
6070                                    index, expected
6071                                )));
6072                            }
6073                            Err(e) => {
6074                                return Err(EvaluatorError::TypeError(format!(
6075                                    "Signature validation failed: {}",
6076                                    e
6077                                )));
6078                            }
6079                        }
6080                    }
6081                    Err(crate::signature::SignatureError::ArgumentTypeMismatch {
6082                        index,
6083                        expected,
6084                    }) => {
6085                        return Err(EvaluatorError::TypeError(
6086                            format!("T0410: Argument {} of function $join does not match function signature (expected {})", index, expected)
6087                        ));
6088                    }
6089                    Err(crate::signature::SignatureError::ArrayTypeMismatch {
6090                        index,
6091                        expected,
6092                    }) => {
6093                        return Err(EvaluatorError::TypeError(format!(
6094                            "T0412: Argument {} of function $join must be an array of {}",
6095                            index, expected
6096                        )));
6097                    }
6098                    Err(e) => {
6099                        return Err(EvaluatorError::TypeError(format!(
6100                            "Signature validation failed: {}",
6101                            e
6102                        )));
6103                    }
6104                };
6105
6106                // After coercion, first arg is guaranteed to be an array of strings
6107                match &coerced_args[0] {
6108                    JValue::Array(arr) => {
6109                        let separator = if coerced_args.len() == 2 {
6110                            match &coerced_args[1] {
6111                                JValue::String(s) => Some(&**s),
6112                                JValue::Null => None, // Undefined separator -> use empty string
6113                                _ => None,            // Signature should have validated this
6114                            }
6115                        } else {
6116                            None // No separator provided -> use empty string
6117                        };
6118                        Ok(functions::string::join(arr, separator)?)
6119                    }
6120                    JValue::Null => Ok(JValue::Null),
6121                    _ => unreachable!("Signature validation should ensure array type"),
6122                }
6123            }
6124            "replace" => {
6125                if evaluated_args.len() < 3 || evaluated_args.len() > 4 {
6126                    return Err(EvaluatorError::EvaluationError(
6127                        "replace() requires 3 or 4 arguments".to_string(),
6128                    ));
6129                }
6130                if evaluated_args[0].is_null() {
6131                    return Ok(JValue::Null);
6132                }
6133                if evaluated_args[0].is_undefined() {
6134                    return Ok(JValue::Undefined);
6135                }
6136
6137                // Check if replacement (3rd arg) is a function/lambda
6138                let replacement_is_lambda = matches!(
6139                    evaluated_args[2],
6140                    JValue::Lambda { .. } | JValue::Builtin { .. }
6141                );
6142
6143                if replacement_is_lambda {
6144                    // Lambda replacement mode
6145                    return self.replace_with_lambda(
6146                        &evaluated_args[0],
6147                        &evaluated_args[1],
6148                        &evaluated_args[2],
6149                        if evaluated_args.len() == 4 {
6150                            Some(&evaluated_args[3])
6151                        } else {
6152                            None
6153                        },
6154                        data,
6155                    );
6156                }
6157
6158                // String replacement mode
6159                match (&evaluated_args[0], &evaluated_args[2]) {
6160                    (JValue::String(s), JValue::String(replacement)) => {
6161                        let limit = if evaluated_args.len() == 4 {
6162                            match &evaluated_args[3] {
6163                                JValue::Number(n) => {
6164                                    let lim_f64 = *n;
6165                                    if lim_f64 < 0.0 {
6166                                        return Err(EvaluatorError::EvaluationError(format!(
6167                                            "D3011: Limit must be non-negative, got {}",
6168                                            lim_f64
6169                                        )));
6170                                    }
6171                                    Some(lim_f64 as usize)
6172                                }
6173                                _ => {
6174                                    return Err(EvaluatorError::TypeError(
6175                                        "replace() limit must be a number".to_string(),
6176                                    ))
6177                                }
6178                            }
6179                        } else {
6180                            None
6181                        };
6182                        Ok(functions::string::replace(
6183                            s,
6184                            &evaluated_args[1],
6185                            replacement,
6186                            limit,
6187                        )?)
6188                    }
6189                    _ => Err(EvaluatorError::TypeError(
6190                        "replace() requires string arguments".to_string(),
6191                    )),
6192                }
6193            }
6194            "match" => {
6195                // $match(str, pattern [, limit])
6196                // Returns array of match objects for regex matches or custom matcher function
6197                if evaluated_args.is_empty() || evaluated_args.len() > 3 {
6198                    return Err(EvaluatorError::EvaluationError(
6199                        "match() requires 1 to 3 arguments".to_string(),
6200                    ));
6201                }
6202                if evaluated_args[0].is_null() {
6203                    return Ok(JValue::Null);
6204                }
6205                if evaluated_args[0].is_undefined() {
6206                    return Ok(JValue::Undefined);
6207                }
6208
6209                let s = match &evaluated_args[0] {
6210                    JValue::String(s) => s.clone(),
6211                    _ => {
6212                        return Err(EvaluatorError::TypeError(
6213                            "match() first argument must be a string".to_string(),
6214                        ))
6215                    }
6216                };
6217
6218                // Get optional limit
6219                let limit = if evaluated_args.len() == 3 {
6220                    match &evaluated_args[2] {
6221                        JValue::Number(n) => Some(*n as usize),
6222                        JValue::Null => None,
6223                        _ => {
6224                            return Err(EvaluatorError::TypeError(
6225                                "match() limit must be a number".to_string(),
6226                            ))
6227                        }
6228                    }
6229                } else {
6230                    None
6231                };
6232
6233                // Check if second argument is a custom matcher function (lambda)
6234                let pattern_value = evaluated_args.get(1);
6235                let is_custom_matcher = pattern_value.is_some_and(|val| {
6236                    matches!(val, JValue::Lambda { .. } | JValue::Builtin { .. })
6237                });
6238
6239                if is_custom_matcher {
6240                    // Custom matcher function support
6241                    // Call the matcher with the string, get match objects with {match, start, end, groups, next}
6242                    return self.match_with_custom_matcher(&s, &args[1], limit, data);
6243                }
6244
6245                // Get regex pattern from second argument
6246                let (pattern, flags) = match pattern_value {
6247                    Some(val) => crate::functions::string::extract_regex(val).ok_or_else(|| {
6248                        EvaluatorError::TypeError(
6249                            "match() second argument must be a regex pattern or matcher function"
6250                                .to_string(),
6251                        )
6252                    })?,
6253                    None => (".*".to_string(), "".to_string()),
6254                };
6255
6256                // Build regex
6257                let is_global = flags.contains('g');
6258                let regex_pattern = if flags.contains('i') {
6259                    format!("(?i){}", pattern)
6260                } else {
6261                    pattern.clone()
6262                };
6263
6264                let re = regex::Regex::new(&regex_pattern).map_err(|e| {
6265                    EvaluatorError::EvaluationError(format!("Invalid regex pattern: {}", e))
6266                })?;
6267
6268                let mut results = Vec::new();
6269                let mut count = 0;
6270
6271                for caps in re.captures_iter(&s) {
6272                    if let Some(lim) = limit {
6273                        if count >= lim {
6274                            break;
6275                        }
6276                    }
6277
6278                    let full_match = caps.get(0).unwrap();
6279                    let mut match_obj = IndexMap::new();
6280                    match_obj.insert(
6281                        "match".to_string(),
6282                        JValue::string(full_match.as_str().to_string()),
6283                    );
6284                    match_obj.insert(
6285                        "index".to_string(),
6286                        JValue::Number(full_match.start() as f64),
6287                    );
6288
6289                    // Collect capture groups
6290                    let mut groups: Vec<JValue> = Vec::new();
6291                    for i in 1..caps.len() {
6292                        if let Some(group) = caps.get(i) {
6293                            groups.push(JValue::string(group.as_str().to_string()));
6294                        } else {
6295                            groups.push(JValue::Null);
6296                        }
6297                    }
6298                    if !groups.is_empty() {
6299                        match_obj.insert("groups".to_string(), JValue::array(groups));
6300                    }
6301
6302                    results.push(JValue::object(match_obj));
6303                    count += 1;
6304
6305                    // If not global, only return first match
6306                    if !is_global {
6307                        break;
6308                    }
6309                }
6310
6311                if results.is_empty() {
6312                    Ok(JValue::Null)
6313                } else if results.len() == 1 && !is_global {
6314                    // Single match (non-global) returns the match object directly
6315                    Ok(results.into_iter().next().unwrap())
6316                } else {
6317                    Ok(JValue::array(results))
6318                }
6319            }
6320            "max" => {
6321                if evaluated_args.len() != 1 {
6322                    return Err(EvaluatorError::EvaluationError(
6323                        "max() requires exactly 1 argument".to_string(),
6324                    ));
6325                }
6326                // Check for undefined
6327                if evaluated_args[0].is_undefined() {
6328                    return Ok(JValue::Undefined);
6329                }
6330                match &evaluated_args[0] {
6331                    JValue::Null => Ok(JValue::Null),
6332                    JValue::Array(arr) => {
6333                        // Use zero-clone iterator-based max
6334                        Ok(aggregation::max(arr)?)
6335                    }
6336                    JValue::Number(_) => Ok(evaluated_args[0].clone()), // Single number returns itself
6337                    _ => Err(EvaluatorError::TypeError(
6338                        "max() requires an array or number argument".to_string(),
6339                    )),
6340                }
6341            }
6342            "min" => {
6343                if evaluated_args.len() != 1 {
6344                    return Err(EvaluatorError::EvaluationError(
6345                        "min() requires exactly 1 argument".to_string(),
6346                    ));
6347                }
6348                // Check for undefined
6349                if evaluated_args[0].is_undefined() {
6350                    return Ok(JValue::Undefined);
6351                }
6352                match &evaluated_args[0] {
6353                    JValue::Null => Ok(JValue::Null),
6354                    JValue::Array(arr) => {
6355                        // Use zero-clone iterator-based min
6356                        Ok(aggregation::min(arr)?)
6357                    }
6358                    JValue::Number(_) => Ok(evaluated_args[0].clone()), // Single number returns itself
6359                    _ => Err(EvaluatorError::TypeError(
6360                        "min() requires an array or number argument".to_string(),
6361                    )),
6362                }
6363            }
6364            "average" => {
6365                if evaluated_args.len() != 1 {
6366                    return Err(EvaluatorError::EvaluationError(
6367                        "average() requires exactly 1 argument".to_string(),
6368                    ));
6369                }
6370                // Return undefined if argument is undefined
6371                if evaluated_args[0].is_undefined() {
6372                    return Ok(JValue::Undefined);
6373                }
6374                match &evaluated_args[0] {
6375                    JValue::Null => Ok(JValue::Null),
6376                    JValue::Array(arr) => {
6377                        // Use zero-clone iterator-based average
6378                        Ok(aggregation::average(arr)?)
6379                    }
6380                    JValue::Number(_) => Ok(evaluated_args[0].clone()), // Single number returns itself
6381                    _ => Err(EvaluatorError::TypeError(
6382                        "average() requires an array or number argument".to_string(),
6383                    )),
6384                }
6385            }
6386            "abs" => {
6387                if evaluated_args.len() != 1 {
6388                    return Err(EvaluatorError::EvaluationError(
6389                        "abs() requires exactly 1 argument".to_string(),
6390                    ));
6391                }
6392                match &evaluated_args[0] {
6393                    JValue::Null => Ok(JValue::Null),
6394                    JValue::Number(n) => Ok(functions::numeric::abs(*n)?),
6395                    _ => Err(EvaluatorError::TypeError(
6396                        "abs() requires a number argument".to_string(),
6397                    )),
6398                }
6399            }
6400            "floor" => {
6401                if evaluated_args.len() != 1 {
6402                    return Err(EvaluatorError::EvaluationError(
6403                        "floor() requires exactly 1 argument".to_string(),
6404                    ));
6405                }
6406                match &evaluated_args[0] {
6407                    JValue::Null => Ok(JValue::Null),
6408                    JValue::Number(n) => Ok(functions::numeric::floor(*n)?),
6409                    _ => Err(EvaluatorError::TypeError(
6410                        "floor() requires a number argument".to_string(),
6411                    )),
6412                }
6413            }
6414            "ceil" => {
6415                if evaluated_args.len() != 1 {
6416                    return Err(EvaluatorError::EvaluationError(
6417                        "ceil() requires exactly 1 argument".to_string(),
6418                    ));
6419                }
6420                match &evaluated_args[0] {
6421                    JValue::Null => Ok(JValue::Null),
6422                    JValue::Number(n) => Ok(functions::numeric::ceil(*n)?),
6423                    _ => Err(EvaluatorError::TypeError(
6424                        "ceil() requires a number argument".to_string(),
6425                    )),
6426                }
6427            }
6428            "round" => {
6429                if evaluated_args.is_empty() || evaluated_args.len() > 2 {
6430                    return Err(EvaluatorError::EvaluationError(
6431                        "round() requires 1 or 2 arguments".to_string(),
6432                    ));
6433                }
6434                match &evaluated_args[0] {
6435                    JValue::Null => Ok(JValue::Null),
6436                    JValue::Number(n) => {
6437                        let precision = if evaluated_args.len() == 2 {
6438                            match &evaluated_args[1] {
6439                                JValue::Number(p) => Some(*p as i32),
6440                                _ => {
6441                                    return Err(EvaluatorError::TypeError(
6442                                        "round() precision must be a number".to_string(),
6443                                    ))
6444                                }
6445                            }
6446                        } else {
6447                            None
6448                        };
6449                        Ok(functions::numeric::round(*n, precision)?)
6450                    }
6451                    _ => Err(EvaluatorError::TypeError(
6452                        "round() requires a number argument".to_string(),
6453                    )),
6454                }
6455            }
6456            "sqrt" => {
6457                if evaluated_args.len() != 1 {
6458                    return Err(EvaluatorError::EvaluationError(
6459                        "sqrt() requires exactly 1 argument".to_string(),
6460                    ));
6461                }
6462                match &evaluated_args[0] {
6463                    JValue::Null => Ok(JValue::Null),
6464                    JValue::Number(n) => Ok(functions::numeric::sqrt(*n)?),
6465                    _ => Err(EvaluatorError::TypeError(
6466                        "sqrt() requires a number argument".to_string(),
6467                    )),
6468                }
6469            }
6470            "power" => {
6471                if evaluated_args.len() != 2 {
6472                    return Err(EvaluatorError::EvaluationError(
6473                        "power() requires exactly 2 arguments".to_string(),
6474                    ));
6475                }
6476                if evaluated_args[0].is_null() {
6477                    return Ok(JValue::Null);
6478                }
6479                if evaluated_args[0].is_undefined() {
6480                    return Ok(JValue::Undefined);
6481                }
6482                match (&evaluated_args[0], &evaluated_args[1]) {
6483                    (JValue::Number(base), JValue::Number(exp)) => {
6484                        Ok(functions::numeric::power(*base, *exp)?)
6485                    }
6486                    _ => Err(EvaluatorError::TypeError(
6487                        "power() requires number arguments".to_string(),
6488                    )),
6489                }
6490            }
6491            "formatNumber" => {
6492                if evaluated_args.len() < 2 || evaluated_args.len() > 3 {
6493                    return Err(EvaluatorError::EvaluationError(
6494                        "formatNumber() requires 2 or 3 arguments".to_string(),
6495                    ));
6496                }
6497                if evaluated_args[0].is_null() {
6498                    return Ok(JValue::Null);
6499                }
6500                if evaluated_args[0].is_undefined() {
6501                    return Ok(JValue::Undefined);
6502                }
6503                match (&evaluated_args[0], &evaluated_args[1]) {
6504                    (JValue::Number(num), JValue::String(picture)) => {
6505                        let options = if evaluated_args.len() == 3 {
6506                            Some(&evaluated_args[2])
6507                        } else {
6508                            None
6509                        };
6510                        Ok(functions::numeric::format_number(*num, picture, options)?)
6511                    }
6512                    _ => Err(EvaluatorError::TypeError(
6513                        "formatNumber() requires a number and a string".to_string(),
6514                    )),
6515                }
6516            }
6517            "formatBase" => {
6518                if evaluated_args.is_empty() || evaluated_args.len() > 2 {
6519                    return Err(EvaluatorError::EvaluationError(
6520                        "formatBase() requires 1 or 2 arguments".to_string(),
6521                    ));
6522                }
6523                // Handle undefined input
6524                if evaluated_args[0].is_null() {
6525                    return Ok(JValue::Null);
6526                }
6527                if evaluated_args[0].is_undefined() {
6528                    return Ok(JValue::Undefined);
6529                }
6530                match &evaluated_args[0] {
6531                    JValue::Number(num) => {
6532                        let radix = if evaluated_args.len() == 2 {
6533                            match &evaluated_args[1] {
6534                                JValue::Number(r) => Some(r.trunc() as i64),
6535                                _ => {
6536                                    return Err(EvaluatorError::TypeError(
6537                                        "formatBase() radix must be a number".to_string(),
6538                                    ))
6539                                }
6540                            }
6541                        } else {
6542                            None
6543                        };
6544                        Ok(functions::numeric::format_base(*num, radix)?)
6545                    }
6546                    _ => Err(EvaluatorError::TypeError(
6547                        "formatBase() requires a number".to_string(),
6548                    )),
6549                }
6550            }
6551            "append" => {
6552                if evaluated_args.len() != 2 {
6553                    return Err(EvaluatorError::EvaluationError(
6554                        "append() requires exactly 2 arguments".to_string(),
6555                    ));
6556                }
6557                // Handle null/undefined arguments
6558                let first = &evaluated_args[0];
6559                let second = &evaluated_args[1];
6560
6561                // If second arg is null/undefined, return first as-is (no change)
6562                if second.is_null() || second.is_undefined() {
6563                    return Ok(first.clone());
6564                }
6565
6566                // If first arg is null/undefined, return second as-is (appending to nothing gives second)
6567                if first.is_null() || first.is_undefined() {
6568                    return Ok(second.clone());
6569                }
6570
6571                // Convert both to arrays if needed, then append
6572                let arr = match first {
6573                    JValue::Array(a) => a.to_vec(),
6574                    other => vec![other.clone()], // Wrap non-array in array
6575                };
6576
6577                Ok(functions::array::append(&arr, second)?)
6578            }
6579            "reverse" => {
6580                if evaluated_args.len() != 1 {
6581                    return Err(EvaluatorError::EvaluationError(
6582                        "reverse() requires exactly 1 argument".to_string(),
6583                    ));
6584                }
6585                match &evaluated_args[0] {
6586                    JValue::Null => Ok(JValue::Null),
6587                    JValue::Undefined => Ok(JValue::Undefined),
6588                    JValue::Array(arr) => Ok(functions::array::reverse(arr)?),
6589                    _ => Err(EvaluatorError::TypeError(
6590                        "reverse() requires an array argument".to_string(),
6591                    )),
6592                }
6593            }
6594            "shuffle" => {
6595                if evaluated_args.len() != 1 {
6596                    return Err(EvaluatorError::EvaluationError(
6597                        "shuffle() requires exactly 1 argument".to_string(),
6598                    ));
6599                }
6600                if evaluated_args[0].is_null() {
6601                    return Ok(JValue::Null);
6602                }
6603                if evaluated_args[0].is_undefined() {
6604                    return Ok(JValue::Undefined);
6605                }
6606                match &evaluated_args[0] {
6607                    JValue::Array(arr) => Ok(functions::array::shuffle(arr)?),
6608                    _ => Err(EvaluatorError::TypeError(
6609                        "shuffle() requires an array argument".to_string(),
6610                    )),
6611                }
6612            }
6613
6614            "sift" => {
6615                // $sift(object, function) or $sift(function) - filter object by predicate
6616                if evaluated_args.is_empty() || evaluated_args.len() > 2 {
6617                    return Err(EvaluatorError::EvaluationError(
6618                        "sift() requires 1 or 2 arguments".to_string(),
6619                    ));
6620                }
6621
6622                // Determine which argument is the function
6623                let func_arg = if evaluated_args.len() == 1 {
6624                    &args[0]
6625                } else {
6626                    &args[1]
6627                };
6628
6629                // Detect how many parameters the callback expects
6630                let param_count = self.get_callback_param_count(func_arg);
6631
6632                // Helper function to sift a single object
6633                let sift_object = |evaluator: &mut Self,
6634                                   obj: &IndexMap<String, JValue>,
6635                                   func_node: &AstNode,
6636                                   context_data: &JValue,
6637                                   param_count: usize|
6638                 -> Result<JValue, EvaluatorError> {
6639                    // Only create the object value if callback uses 3 parameters
6640                    let obj_value = if param_count >= 3 {
6641                        Some(JValue::object(obj.clone()))
6642                    } else {
6643                        None
6644                    };
6645
6646                    let mut result = IndexMap::new();
6647                    for (key, value) in obj.iter() {
6648                        // Build argument list based on what callback expects
6649                        let call_args = match param_count {
6650                            1 => vec![value.clone()],
6651                            2 => vec![value.clone(), JValue::string(key.clone())],
6652                            _ => vec![
6653                                value.clone(),
6654                                JValue::string(key.clone()),
6655                                obj_value.as_ref().unwrap().clone(),
6656                            ],
6657                        };
6658
6659                        let pred_result =
6660                            evaluator.apply_function(func_node, &call_args, context_data)?;
6661                        if evaluator.is_truthy(&pred_result) {
6662                            result.insert(key.clone(), value.clone());
6663                        }
6664                    }
6665                    // Return undefined for empty results (will be filtered by function application)
6666                    if result.is_empty() {
6667                        Ok(JValue::Undefined)
6668                    } else {
6669                        Ok(JValue::object(result))
6670                    }
6671                };
6672
6673                // Handle partial application - if only 1 arg, use current context as object
6674                if evaluated_args.len() == 1 {
6675                    // $sift(function) - use current context data as object
6676                    match data {
6677                        JValue::Object(o) => sift_object(self, o, &args[0], data, param_count),
6678                        JValue::Array(arr) => {
6679                            // Map sift over each object in the array
6680                            let mut results = Vec::new();
6681                            for item in arr.iter() {
6682                                if let JValue::Object(o) = item {
6683                                    let sifted = sift_object(self, o, &args[0], item, param_count)?;
6684                                    // sift_object returns undefined for empty results
6685                                    if !sifted.is_undefined() {
6686                                        results.push(sifted);
6687                                    }
6688                                }
6689                            }
6690                            Ok(JValue::array(results))
6691                        }
6692                        JValue::Null => Ok(JValue::Null),
6693                        _ => Ok(JValue::Undefined),
6694                    }
6695                } else {
6696                    // $sift(object, function)
6697                    match &evaluated_args[0] {
6698                        JValue::Object(o) => sift_object(self, o, &args[1], data, param_count),
6699                        JValue::Null => Ok(JValue::Null),
6700                        _ => Err(EvaluatorError::TypeError(
6701                            "sift() first argument must be an object".to_string(),
6702                        )),
6703                    }
6704                }
6705            }
6706
6707            "zip" => {
6708                if evaluated_args.is_empty() {
6709                    return Err(EvaluatorError::EvaluationError(
6710                        "zip() requires at least 1 argument".to_string(),
6711                    ));
6712                }
6713
6714                // Convert arguments to arrays (wrapping non-arrays in single-element arrays)
6715                // If any argument is null/undefined, return empty array
6716                let mut arrays: Vec<Vec<JValue>> = Vec::with_capacity(evaluated_args.len());
6717                for arg in &evaluated_args {
6718                    match arg {
6719                        JValue::Array(arr) => {
6720                            if arr.is_empty() {
6721                                // Empty array means result is empty
6722                                return Ok(JValue::array(vec![]));
6723                            }
6724                            arrays.push(arr.to_vec());
6725                        }
6726                        JValue::Null => {
6727                            // Null/undefined means result is empty
6728                            return Ok(JValue::array(vec![]));
6729                        }
6730                        other => {
6731                            // Wrap non-array values in single-element array
6732                            arrays.push(vec![other.clone()]);
6733                        }
6734                    }
6735                }
6736
6737                if arrays.is_empty() {
6738                    return Ok(JValue::array(vec![]));
6739                }
6740
6741                // Find the length of the shortest array
6742                let min_len = arrays.iter().map(|a| a.len()).min().unwrap_or(0);
6743
6744                // Zip the arrays together
6745                let mut result = Vec::with_capacity(min_len);
6746                for i in 0..min_len {
6747                    let mut tuple = Vec::with_capacity(arrays.len());
6748                    for array in &arrays {
6749                        tuple.push(array[i].clone());
6750                    }
6751                    result.push(JValue::array(tuple));
6752                }
6753
6754                Ok(JValue::array(result))
6755            }
6756
6757            "sort" => {
6758                if evaluated_args.is_empty() || evaluated_args.len() > 2 {
6759                    return Err(EvaluatorError::EvaluationError(
6760                        "sort() requires 1 or 2 arguments".to_string(),
6761                    ));
6762                }
6763
6764                // Use pre-evaluated first argument (avoid double evaluation)
6765                let array_value = &evaluated_args[0];
6766
6767                // Handle undefined input
6768                if array_value.is_null() {
6769                    return Ok(JValue::Null);
6770                }
6771                if array_value.is_undefined() {
6772                    return Ok(JValue::Undefined);
6773                }
6774
6775                let mut arr = match array_value {
6776                    JValue::Array(arr) => arr.to_vec(),
6777                    other => vec![other.clone()],
6778                };
6779
6780                if args.len() == 2 {
6781                    // Sort using the comparator from raw args (need unevaluated lambda AST)
6782                    // Use merge sort for O(n log n) performance instead of O(n²) bubble sort
6783                    self.merge_sort_with_comparator(&mut arr, &args[1], data)?;
6784                    Ok(JValue::array(arr))
6785                } else {
6786                    // Default sort (no comparator)
6787                    Ok(functions::array::sort(&arr)?)
6788                }
6789            }
6790            "distinct" => {
6791                if evaluated_args.len() != 1 {
6792                    return Err(EvaluatorError::EvaluationError(
6793                        "distinct() requires exactly 1 argument".to_string(),
6794                    ));
6795                }
6796                match &evaluated_args[0] {
6797                    JValue::Array(arr) => Ok(functions::array::distinct(arr)?),
6798                    _ => Err(EvaluatorError::TypeError(
6799                        "distinct() requires an array argument".to_string(),
6800                    )),
6801                }
6802            }
6803            "exists" => {
6804                if evaluated_args.len() != 1 {
6805                    return Err(EvaluatorError::EvaluationError(
6806                        "exists() requires exactly 1 argument".to_string(),
6807                    ));
6808                }
6809                Ok(functions::array::exists(&evaluated_args[0])?)
6810            }
6811            "keys" => {
6812                if evaluated_args.len() != 1 {
6813                    return Err(EvaluatorError::EvaluationError(
6814                        "keys() requires exactly 1 argument".to_string(),
6815                    ));
6816                }
6817
6818                // Helper to unwrap single-element arrays
6819                let unwrap_single = |keys: Vec<JValue>| -> JValue {
6820                    if keys.len() == 1 {
6821                        keys.into_iter().next().unwrap()
6822                    } else {
6823                        JValue::array(keys)
6824                    }
6825                };
6826
6827                match &evaluated_args[0] {
6828                    JValue::Null => Ok(JValue::Null),
6829                    JValue::Lambda { .. } | JValue::Builtin { .. } => Ok(JValue::Null),
6830                    JValue::Object(obj) => {
6831                        // Return undefined for empty objects
6832                        if obj.is_empty() {
6833                            Ok(JValue::Null)
6834                        } else {
6835                            let keys: Vec<JValue> =
6836                                obj.keys().map(|k| JValue::string(k.clone())).collect();
6837                            Ok(unwrap_single(keys))
6838                        }
6839                    }
6840                    JValue::Array(arr) => {
6841                        // For arrays, collect keys from all objects
6842                        let mut all_keys = Vec::new();
6843                        for item in arr.iter() {
6844                            // Skip lambda/builtin values
6845                            if matches!(item, JValue::Lambda { .. } | JValue::Builtin { .. }) {
6846                                continue;
6847                            }
6848                            if let JValue::Object(obj) = item {
6849                                for key in obj.keys() {
6850                                    if !all_keys.contains(&JValue::string(key.clone())) {
6851                                        all_keys.push(JValue::string(key.clone()));
6852                                    }
6853                                }
6854                            }
6855                        }
6856                        if all_keys.is_empty() {
6857                            Ok(JValue::Null)
6858                        } else {
6859                            Ok(unwrap_single(all_keys))
6860                        }
6861                    }
6862                    // Non-object types return undefined
6863                    _ => Ok(JValue::Null),
6864                }
6865            }
6866            "lookup" => {
6867                if evaluated_args.len() != 2 {
6868                    return Err(EvaluatorError::EvaluationError(
6869                        "lookup() requires exactly 2 arguments".to_string(),
6870                    ));
6871                }
6872                if evaluated_args[0].is_null() {
6873                    return Ok(JValue::Null);
6874                }
6875                if evaluated_args[0].is_undefined() {
6876                    return Ok(JValue::Undefined);
6877                }
6878
6879                let key = match &evaluated_args[1] {
6880                    JValue::String(k) => &**k,
6881                    _ => {
6882                        return Err(EvaluatorError::TypeError(
6883                            "lookup() requires a string key".to_string(),
6884                        ))
6885                    }
6886                };
6887
6888                // Helper function to recursively lookup in values
6889                fn lookup_recursive(val: &JValue, key: &str) -> Vec<JValue> {
6890                    match val {
6891                        JValue::Array(arr) => {
6892                            let mut results = Vec::new();
6893                            for item in arr.iter() {
6894                                let nested = lookup_recursive(item, key);
6895                                results.extend(nested.iter().cloned());
6896                            }
6897                            results
6898                        }
6899                        JValue::Object(obj) => {
6900                            if let Some(v) = obj.get(key) {
6901                                vec![v.clone()]
6902                            } else {
6903                                vec![]
6904                            }
6905                        }
6906                        _ => vec![],
6907                    }
6908                }
6909
6910                let results = lookup_recursive(&evaluated_args[0], key);
6911                if results.is_empty() {
6912                    Ok(JValue::Null)
6913                } else if results.len() == 1 {
6914                    Ok(results[0].clone())
6915                } else {
6916                    Ok(JValue::array(results))
6917                }
6918            }
6919            "spread" => {
6920                if evaluated_args.len() != 1 {
6921                    return Err(EvaluatorError::EvaluationError(
6922                        "spread() requires exactly 1 argument".to_string(),
6923                    ));
6924                }
6925                match &evaluated_args[0] {
6926                    JValue::Null => Ok(JValue::Null),
6927                    // Not a container - pass through unchanged (e.g. so $string() still
6928                    // sees the function value and applies its own function->"" rule).
6929                    lambda @ (JValue::Lambda { .. } | JValue::Builtin { .. }) => Ok(lambda.clone()),
6930                    JValue::Object(obj) => Ok(functions::object::spread(obj)?),
6931                    JValue::Array(arr) => {
6932                        // Spread each object in the array
6933                        let mut result = Vec::new();
6934                        for item in arr.iter() {
6935                            match item {
6936                                JValue::Lambda { .. } | JValue::Builtin { .. } => {
6937                                    // Skip lambdas in array
6938                                    continue;
6939                                }
6940                                JValue::Object(obj) => {
6941                                    let spread_result = functions::object::spread(obj)?;
6942                                    if let JValue::Array(spread_items) = spread_result {
6943                                        result.extend(spread_items.iter().cloned());
6944                                    } else {
6945                                        result.push(spread_result);
6946                                    }
6947                                }
6948                                // Non-objects in array are returned unchanged
6949                                other => result.push(other.clone()),
6950                            }
6951                        }
6952                        Ok(JValue::array(result))
6953                    }
6954                    // Non-objects are returned unchanged
6955                    other => Ok(other.clone()),
6956                }
6957            }
6958            "merge" => {
6959                if evaluated_args.is_empty() {
6960                    return Err(EvaluatorError::EvaluationError(
6961                        "merge() requires at least 1 argument".to_string(),
6962                    ));
6963                }
6964                // Handle the case where a single array of objects is passed: $merge([obj1, obj2])
6965                // vs multiple object arguments: $merge(obj1, obj2)
6966                if evaluated_args.len() == 1 {
6967                    match &evaluated_args[0] {
6968                        JValue::Array(arr) => Ok(functions::object::merge(arr)?),
6969                        JValue::Null => Ok(JValue::Null),
6970                        JValue::Undefined => Ok(JValue::Undefined),
6971                        JValue::Object(_) => {
6972                            // Single object - just return it
6973                            Ok(evaluated_args[0].clone())
6974                        }
6975                        _ => Err(EvaluatorError::TypeError(
6976                            "merge() requires objects or an array of objects".to_string(),
6977                        )),
6978                    }
6979                } else {
6980                    Ok(functions::object::merge(&evaluated_args)?)
6981                }
6982            }
6983
6984            "map" => {
6985                if args.len() != 2 {
6986                    return Err(EvaluatorError::EvaluationError(
6987                        "map() requires exactly 2 arguments".to_string(),
6988                    ));
6989                }
6990
6991                // Evaluate the array argument
6992                let array = self.evaluate_internal(&args[0], data)?;
6993
6994                match array {
6995                    JValue::Array(arr) => {
6996                        // Detect how many parameters the callback expects
6997                        let param_count = self.get_callback_param_count(&args[1]);
6998
6999                        // CompiledExpr fast path: direct lambda with 1 param, compilable body
7000                        if param_count == 1 {
7001                            if let AstNode::Lambda {
7002                                params,
7003                                body,
7004                                signature: None,
7005                                thunk: false,
7006                            } = &args[1]
7007                            {
7008                                let var_refs: Vec<&str> =
7009                                    params.iter().map(|s| s.as_str()).collect();
7010                                if let Some(compiled) =
7011                                    try_compile_expr_with_allowed_vars(body, &var_refs)
7012                                {
7013                                    let param_name = params[0].as_str();
7014                                    let mut result = Vec::with_capacity(arr.len());
7015                                    let mut vars = HashMap::new();
7016                                    for item in arr.iter() {
7017                                        vars.insert(param_name, item);
7018                                        let mapped = eval_compiled(&compiled, data, Some(&vars))?;
7019                                        if !mapped.is_undefined() {
7020                                            result.push(mapped);
7021                                        }
7022                                    }
7023                                    return Ok(JValue::array(result));
7024                                }
7025                            }
7026                            // Stored lambda variable fast path: $var with pre-compiled body
7027                            if let AstNode::Variable(var_name) = &args[1] {
7028                                if let Some(stored) = self.context.lookup_lambda(var_name) {
7029                                    if let Some(ref ce) = stored.compiled_body.clone() {
7030                                        let param_name = stored.params[0].clone();
7031                                        let captured_data = stored.captured_data.clone();
7032                                        let captured_env_clone = stored.captured_env.clone();
7033                                        let ce_clone = ce.clone();
7034                                        if !captured_env_clone.values().any(|v| {
7035                                            matches!(
7036                                                v,
7037                                                JValue::Lambda { .. } | JValue::Builtin { .. }
7038                                            )
7039                                        }) {
7040                                            let call_data = captured_data.as_ref().unwrap_or(data);
7041                                            let mut result = Vec::with_capacity(arr.len());
7042                                            let mut vars: HashMap<&str, &JValue> =
7043                                                captured_env_clone
7044                                                    .iter()
7045                                                    .map(|(k, v)| (k.as_str(), v))
7046                                                    .collect();
7047                                            for item in arr.iter() {
7048                                                vars.insert(param_name.as_str(), item);
7049                                                let mapped = eval_compiled(
7050                                                    &ce_clone,
7051                                                    call_data,
7052                                                    Some(&vars),
7053                                                )?;
7054                                                if !mapped.is_undefined() {
7055                                                    result.push(mapped);
7056                                                }
7057                                            }
7058                                            return Ok(JValue::array(result));
7059                                        }
7060                                    }
7061                                }
7062                            }
7063                        }
7064
7065                        // Only create the array value if callback uses 3 parameters
7066                        let arr_value = if param_count >= 3 {
7067                            Some(JValue::Array(arr.clone()))
7068                        } else {
7069                            None
7070                        };
7071
7072                        let mut result = Vec::with_capacity(arr.len());
7073                        for (index, item) in arr.iter().enumerate() {
7074                            // Build argument list based on what callback expects
7075                            let call_args = match param_count {
7076                                1 => vec![item.clone()],
7077                                2 => vec![item.clone(), JValue::Number(index as f64)],
7078                                _ => vec![
7079                                    item.clone(),
7080                                    JValue::Number(index as f64),
7081                                    arr_value.as_ref().unwrap().clone(),
7082                                ],
7083                            };
7084
7085                            let mapped = self.apply_function(&args[1], &call_args, data)?;
7086                            // Filter out undefined results but keep explicit null (JSONata map semantics)
7087                            // undefined comes from missing else clause, null is explicit
7088                            if !mapped.is_undefined() {
7089                                result.push(mapped);
7090                            }
7091                        }
7092                        Ok(JValue::array(result))
7093                    }
7094                    JValue::Null => Ok(JValue::Null),
7095                    JValue::Undefined => Ok(JValue::Undefined),
7096                    _ => Err(EvaluatorError::TypeError(
7097                        "map() first argument must be an array".to_string(),
7098                    )),
7099                }
7100            }
7101
7102            "filter" => {
7103                if args.len() != 2 {
7104                    return Err(EvaluatorError::EvaluationError(
7105                        "filter() requires exactly 2 arguments".to_string(),
7106                    ));
7107                }
7108
7109                // Evaluate the array argument
7110                let array = self.evaluate_internal(&args[0], data)?;
7111
7112                // Handle undefined input - return undefined
7113                if array.is_undefined() {
7114                    return Ok(JValue::Undefined);
7115                }
7116
7117                // Handle null input
7118                if array.is_null() {
7119                    return Ok(JValue::Undefined);
7120                }
7121
7122                // Coerce non-array values to single-element arrays
7123                // Track if input was a single value to unwrap result appropriately
7124                // Use references to avoid upfront cloning of all elements
7125                let single_holder;
7126                let (items, was_single_value): (&[JValue], bool) = match &array {
7127                    JValue::Array(arr) => (arr.as_slice(), false),
7128                    _ => {
7129                        single_holder = [array];
7130                        (&single_holder[..], true)
7131                    }
7132                };
7133
7134                // Detect how many parameters the callback expects
7135                let param_count = self.get_callback_param_count(&args[1]);
7136
7137                // CompiledExpr fast path: direct lambda with 1 param, compilable body
7138                if param_count == 1 {
7139                    if let AstNode::Lambda {
7140                        params,
7141                        body,
7142                        signature: None,
7143                        thunk: false,
7144                    } = &args[1]
7145                    {
7146                        let var_refs: Vec<&str> = params.iter().map(|s| s.as_str()).collect();
7147                        if let Some(compiled) = try_compile_expr_with_allowed_vars(body, &var_refs)
7148                        {
7149                            let param_name = params[0].as_str();
7150                            let mut result = Vec::with_capacity(items.len() / 2);
7151                            let mut vars = HashMap::new();
7152                            for item in items.iter() {
7153                                vars.insert(param_name, item);
7154                                let pred_result = eval_compiled(&compiled, data, Some(&vars))?;
7155                                if compiled_is_truthy(&pred_result) {
7156                                    result.push(item.clone());
7157                                }
7158                            }
7159                            if was_single_value {
7160                                if result.len() == 1 {
7161                                    return Ok(result.remove(0));
7162                                } else if result.is_empty() {
7163                                    return Ok(JValue::Undefined);
7164                                }
7165                            }
7166                            return Ok(JValue::array(result));
7167                        }
7168                    }
7169                    // Stored lambda variable fast path: $var with pre-compiled body
7170                    if let AstNode::Variable(var_name) = &args[1] {
7171                        if let Some(stored) = self.context.lookup_lambda(var_name) {
7172                            if let Some(ref ce) = stored.compiled_body.clone() {
7173                                let param_name = stored.params[0].clone();
7174                                let captured_data = stored.captured_data.clone();
7175                                let captured_env_clone = stored.captured_env.clone();
7176                                let ce_clone = ce.clone();
7177                                if !captured_env_clone.values().any(|v| {
7178                                    matches!(v, JValue::Lambda { .. } | JValue::Builtin { .. })
7179                                }) {
7180                                    let call_data = captured_data.as_ref().unwrap_or(data);
7181                                    let mut result = Vec::with_capacity(items.len() / 2);
7182                                    let mut vars: HashMap<&str, &JValue> = captured_env_clone
7183                                        .iter()
7184                                        .map(|(k, v)| (k.as_str(), v))
7185                                        .collect();
7186                                    for item in items.iter() {
7187                                        vars.insert(param_name.as_str(), item);
7188                                        let pred_result =
7189                                            eval_compiled(&ce_clone, call_data, Some(&vars))?;
7190                                        if compiled_is_truthy(&pred_result) {
7191                                            result.push(item.clone());
7192                                        }
7193                                    }
7194                                    if was_single_value {
7195                                        if result.len() == 1 {
7196                                            return Ok(result.remove(0));
7197                                        } else if result.is_empty() {
7198                                            return Ok(JValue::Undefined);
7199                                        }
7200                                    }
7201                                    return Ok(JValue::array(result));
7202                                }
7203                            }
7204                        }
7205                    }
7206                }
7207
7208                // Only create the array value if callback uses 3 parameters
7209                let arr_value = if param_count >= 3 {
7210                    Some(JValue::array(items.to_vec()))
7211                } else {
7212                    None
7213                };
7214
7215                let mut result = Vec::with_capacity(items.len() / 2);
7216
7217                for (index, item) in items.iter().enumerate() {
7218                    // Build argument list based on what callback expects
7219                    let call_args = match param_count {
7220                        1 => vec![item.clone()],
7221                        2 => vec![item.clone(), JValue::Number(index as f64)],
7222                        _ => vec![
7223                            item.clone(),
7224                            JValue::Number(index as f64),
7225                            arr_value.as_ref().unwrap().clone(),
7226                        ],
7227                    };
7228
7229                    let predicate_result = self.apply_function(&args[1], &call_args, data)?;
7230                    if self.is_truthy(&predicate_result) {
7231                        result.push(item.clone());
7232                    }
7233                }
7234
7235                // If input was a single value, return the single matching item
7236                // (or undefined if no match)
7237                if was_single_value {
7238                    if result.len() == 1 {
7239                        return Ok(result.remove(0));
7240                    } else if result.is_empty() {
7241                        return Ok(JValue::Undefined);
7242                    }
7243                }
7244
7245                Ok(JValue::array(result))
7246            }
7247
7248            "reduce" => {
7249                if args.len() < 2 || args.len() > 3 {
7250                    return Err(EvaluatorError::EvaluationError(
7251                        "reduce() requires 2 or 3 arguments".to_string(),
7252                    ));
7253                }
7254
7255                // Check that the callback function has at least 2 parameters
7256                if let AstNode::Lambda { params, .. } = &args[1] {
7257                    if params.len() < 2 {
7258                        return Err(EvaluatorError::EvaluationError(
7259                            "D3050: The second argument of reduce must be a function with at least two arguments".to_string(),
7260                        ));
7261                    }
7262                } else if let AstNode::Function { name, .. } = &args[1] {
7263                    // For now, we can't validate built-in function signatures here
7264                    // But user-defined functions via lambda will be validated above
7265                    let _ = name; // avoid unused warning
7266                }
7267
7268                // Evaluate the array argument
7269                let array = self.evaluate_internal(&args[0], data)?;
7270
7271                // Convert single value to array (JSONata reduce accepts single values)
7272                // Use references to avoid upfront cloning of all elements
7273                let single_holder;
7274                let items: &[JValue] = match &array {
7275                    JValue::Array(arr) => arr.as_slice(),
7276                    JValue::Null => return Ok(JValue::Null),
7277                    _ => {
7278                        single_holder = [array];
7279                        &single_holder[..]
7280                    }
7281                };
7282
7283                if items.is_empty() {
7284                    // Return initial value if provided, otherwise null
7285                    return if args.len() == 3 {
7286                        self.evaluate_internal(&args[2], data)
7287                    } else {
7288                        Ok(JValue::Null)
7289                    };
7290                }
7291
7292                // Get initial accumulator
7293                let mut accumulator = if args.len() == 3 {
7294                    self.evaluate_internal(&args[2], data)?
7295                } else {
7296                    items[0].clone()
7297                };
7298
7299                let start_idx = if args.len() == 3 { 0 } else { 1 };
7300
7301                // Detect how many parameters the callback expects
7302                let param_count = self.get_callback_param_count(&args[1]);
7303
7304                // CompiledExpr fast path: direct lambda with 2 params, compilable body
7305                if param_count == 2 {
7306                    if let AstNode::Lambda {
7307                        params,
7308                        body,
7309                        signature: None,
7310                        thunk: false,
7311                    } = &args[1]
7312                    {
7313                        let var_refs: Vec<&str> = params.iter().map(|s| s.as_str()).collect();
7314                        if let Some(compiled) = try_compile_expr_with_allowed_vars(body, &var_refs)
7315                        {
7316                            let acc_name = params[0].as_str();
7317                            let item_name = params[1].as_str();
7318                            for item in items[start_idx..].iter() {
7319                                let vars: HashMap<&str, &JValue> =
7320                                    HashMap::from([(acc_name, &accumulator), (item_name, item)]);
7321                                accumulator = eval_compiled(&compiled, data, Some(&vars))?;
7322                            }
7323                            return Ok(accumulator);
7324                        }
7325                    }
7326                    // Stored lambda variable fast path: $var with pre-compiled body
7327                    if let AstNode::Variable(var_name) = &args[1] {
7328                        if let Some(stored) = self.context.lookup_lambda(var_name) {
7329                            if stored.params.len() == 2 {
7330                                if let Some(ref ce) = stored.compiled_body.clone() {
7331                                    let acc_param = stored.params[0].clone();
7332                                    let item_param = stored.params[1].clone();
7333                                    let captured_data = stored.captured_data.clone();
7334                                    let captured_env_clone = stored.captured_env.clone();
7335                                    let ce_clone = ce.clone();
7336                                    if !captured_env_clone.values().any(|v| {
7337                                        matches!(v, JValue::Lambda { .. } | JValue::Builtin { .. })
7338                                    }) {
7339                                        let call_data = captured_data.as_ref().unwrap_or(data);
7340                                        for item in items[start_idx..].iter() {
7341                                            let mut vars: HashMap<&str, &JValue> =
7342                                                captured_env_clone
7343                                                    .iter()
7344                                                    .map(|(k, v)| (k.as_str(), v))
7345                                                    .collect();
7346                                            vars.insert(acc_param.as_str(), &accumulator);
7347                                            vars.insert(item_param.as_str(), item);
7348                                            // Evaluate and drop vars before assigning accumulator
7349                                            // to satisfy borrow checker (vars borrows accumulator)
7350                                            let new_acc =
7351                                                eval_compiled(&ce_clone, call_data, Some(&vars))?;
7352                                            drop(vars);
7353                                            accumulator = new_acc;
7354                                        }
7355                                        return Ok(accumulator);
7356                                    }
7357                                }
7358                            }
7359                        }
7360                    }
7361                }
7362
7363                // Only create the array value if callback uses 4 parameters
7364                let arr_value = if param_count >= 4 {
7365                    Some(JValue::array(items.to_vec()))
7366                } else {
7367                    None
7368                };
7369
7370                // Apply function to each element
7371                for (idx, item) in items[start_idx..].iter().enumerate() {
7372                    // For reduce, the function receives (accumulator, value, index, array)
7373                    // Callbacks may use any subset of these parameters
7374                    let actual_idx = start_idx + idx;
7375
7376                    // Build argument list based on what callback expects
7377                    let call_args = match param_count {
7378                        2 => vec![accumulator.clone(), item.clone()],
7379                        3 => vec![
7380                            accumulator.clone(),
7381                            item.clone(),
7382                            JValue::Number(actual_idx as f64),
7383                        ],
7384                        _ => vec![
7385                            accumulator.clone(),
7386                            item.clone(),
7387                            JValue::Number(actual_idx as f64),
7388                            arr_value.as_ref().unwrap().clone(),
7389                        ],
7390                    };
7391
7392                    accumulator = self.apply_function(&args[1], &call_args, data)?;
7393                }
7394
7395                Ok(accumulator)
7396            }
7397
7398            "single" => {
7399                if args.is_empty() || args.len() > 2 {
7400                    return Err(EvaluatorError::EvaluationError(
7401                        "single() requires 1 or 2 arguments".to_string(),
7402                    ));
7403                }
7404
7405                // Evaluate the array argument
7406                let array = self.evaluate_internal(&args[0], data)?;
7407
7408                // Convert to array (wrap single values)
7409                let arr = match array {
7410                    JValue::Array(arr) => arr.to_vec(),
7411                    JValue::Null => return Ok(JValue::Null),
7412                    other => vec![other],
7413                };
7414
7415                if args.len() == 1 {
7416                    // No predicate - array must have exactly 1 element
7417                    match arr.len() {
7418                        0 => Err(EvaluatorError::EvaluationError(
7419                            "single() argument is empty".to_string(),
7420                        )),
7421                        1 => Ok(arr.into_iter().next().unwrap()),
7422                        count => Err(EvaluatorError::EvaluationError(format!(
7423                            "single() argument has {} values (expected exactly 1)",
7424                            count
7425                        ))),
7426                    }
7427                } else {
7428                    // With predicate - find exactly 1 matching element
7429                    let arr_value = JValue::array(arr.clone());
7430                    let mut matches = Vec::new();
7431                    for (index, item) in arr.into_iter().enumerate() {
7432                        // Apply predicate function with (item, index, array)
7433                        let predicate_result = self.apply_function(
7434                            &args[1],
7435                            &[
7436                                item.clone(),
7437                                JValue::Number(index as f64),
7438                                arr_value.clone(),
7439                            ],
7440                            data,
7441                        )?;
7442                        if self.is_truthy(&predicate_result) {
7443                            matches.push(item);
7444                        }
7445                    }
7446
7447                    match matches.len() {
7448                        0 => Err(EvaluatorError::EvaluationError(
7449                            "single() predicate matches no values".to_string(),
7450                        )),
7451                        1 => Ok(matches.into_iter().next().unwrap()),
7452                        count => Err(EvaluatorError::EvaluationError(format!(
7453                            "single() predicate matches {} values (expected exactly 1)",
7454                            count
7455                        ))),
7456                    }
7457                }
7458            }
7459
7460            "each" => {
7461                // $each(object, function) - iterate over object, applying function to each value/key pair
7462                // Returns an array of the function results
7463                if args.is_empty() || args.len() > 2 {
7464                    return Err(EvaluatorError::EvaluationError(
7465                        "each() requires 1 or 2 arguments".to_string(),
7466                    ));
7467                }
7468
7469                // Determine which argument is the object and which is the function
7470                let (obj_value, func_arg) = if args.len() == 1 {
7471                    // Single argument: use current data as object
7472                    (data.clone(), &args[0])
7473                } else {
7474                    // Two arguments: first is object, second is function
7475                    (self.evaluate_internal(&args[0], data)?, &args[1])
7476                };
7477
7478                // Detect how many parameters the callback expects
7479                let param_count = self.get_callback_param_count(func_arg);
7480
7481                match obj_value {
7482                    JValue::Object(obj) => {
7483                        let mut result = Vec::new();
7484                        for (key, value) in obj.iter() {
7485                            // Build argument list based on what callback expects
7486                            // The callback receives the value as the first argument and key as second
7487                            let call_args = match param_count {
7488                                1 => vec![value.clone()],
7489                                _ => vec![value.clone(), JValue::string(key.clone())],
7490                            };
7491
7492                            let fn_result = self.apply_function(func_arg, &call_args, data)?;
7493                            // Skip undefined results (similar to map behavior)
7494                            if !fn_result.is_null() && !fn_result.is_undefined() {
7495                                result.push(fn_result);
7496                            }
7497                        }
7498                        Ok(JValue::array(result))
7499                    }
7500                    JValue::Null => Ok(JValue::Null),
7501                    _ => Err(EvaluatorError::TypeError(
7502                        "each() first argument must be an object".to_string(),
7503                    )),
7504                }
7505            }
7506
7507            "not" => {
7508                if evaluated_args.len() != 1 {
7509                    return Err(EvaluatorError::EvaluationError(
7510                        "not() requires exactly 1 argument".to_string(),
7511                    ));
7512                }
7513                // $not(x) returns the logical negation of x
7514                // null is falsy, so $not(null) = true; undefined stays undefined
7515                if evaluated_args[0].is_undefined() {
7516                    return Ok(JValue::Undefined);
7517                }
7518                Ok(JValue::Bool(!self.is_truthy(&evaluated_args[0])))
7519            }
7520            "boolean" => {
7521                if evaluated_args.len() != 1 {
7522                    return Err(EvaluatorError::EvaluationError(
7523                        "boolean() requires exactly 1 argument".to_string(),
7524                    ));
7525                }
7526                if evaluated_args[0].is_undefined() {
7527                    return Ok(JValue::Undefined);
7528                }
7529                Ok(functions::boolean::boolean(&evaluated_args[0])?)
7530            }
7531            "type" => {
7532                if evaluated_args.len() != 1 {
7533                    return Err(EvaluatorError::EvaluationError(
7534                        "type() requires exactly 1 argument".to_string(),
7535                    ));
7536                }
7537                // Return type string
7538                // In JavaScript: $type(undefined) returns undefined, $type(null) returns "null"
7539                // We use a special marker object to distinguish undefined from null
7540                match &evaluated_args[0] {
7541                    JValue::Null => Ok(JValue::string("null")),
7542                    JValue::Bool(_) => Ok(JValue::string("boolean")),
7543                    JValue::Number(_) => Ok(JValue::string("number")),
7544                    JValue::String(_) => Ok(JValue::string("string")),
7545                    JValue::Array(_) => Ok(JValue::string("array")),
7546                    JValue::Object(_) => Ok(JValue::string("object")),
7547                    JValue::Undefined => Ok(JValue::Undefined),
7548                    JValue::Lambda { .. } | JValue::Builtin { .. } => {
7549                        Ok(JValue::string("function"))
7550                    }
7551                    JValue::Regex { .. } => Ok(JValue::string("regex")),
7552                }
7553            }
7554
7555            "base64encode" => {
7556                if evaluated_args.is_empty() || evaluated_args[0].is_null() {
7557                    return Ok(JValue::Null);
7558                }
7559                if evaluated_args.len() != 1 {
7560                    return Err(EvaluatorError::EvaluationError(
7561                        "base64encode() requires exactly 1 argument".to_string(),
7562                    ));
7563                }
7564                match &evaluated_args[0] {
7565                    JValue::String(s) => Ok(functions::encoding::base64encode(s)?),
7566                    _ => Err(EvaluatorError::TypeError(
7567                        "base64encode() requires a string argument".to_string(),
7568                    )),
7569                }
7570            }
7571            "base64decode" => {
7572                if evaluated_args.is_empty() || evaluated_args[0].is_null() {
7573                    return Ok(JValue::Null);
7574                }
7575                if evaluated_args.len() != 1 {
7576                    return Err(EvaluatorError::EvaluationError(
7577                        "base64decode() requires exactly 1 argument".to_string(),
7578                    ));
7579                }
7580                match &evaluated_args[0] {
7581                    JValue::String(s) => Ok(functions::encoding::base64decode(s)?),
7582                    _ => Err(EvaluatorError::TypeError(
7583                        "base64decode() requires a string argument".to_string(),
7584                    )),
7585                }
7586            }
7587            "encodeUrlComponent" => {
7588                if evaluated_args.len() != 1 {
7589                    return Err(EvaluatorError::EvaluationError(
7590                        "encodeUrlComponent() requires exactly 1 argument".to_string(),
7591                    ));
7592                }
7593                if evaluated_args[0].is_null() {
7594                    return Ok(JValue::Null);
7595                }
7596                if evaluated_args[0].is_undefined() {
7597                    return Ok(JValue::Undefined);
7598                }
7599                match &evaluated_args[0] {
7600                    JValue::String(s) => Ok(functions::encoding::encode_url_component(s)?),
7601                    _ => Err(EvaluatorError::TypeError(
7602                        "encodeUrlComponent() requires a string argument".to_string(),
7603                    )),
7604                }
7605            }
7606            "decodeUrlComponent" => {
7607                if evaluated_args.len() != 1 {
7608                    return Err(EvaluatorError::EvaluationError(
7609                        "decodeUrlComponent() requires exactly 1 argument".to_string(),
7610                    ));
7611                }
7612                if evaluated_args[0].is_null() {
7613                    return Ok(JValue::Null);
7614                }
7615                if evaluated_args[0].is_undefined() {
7616                    return Ok(JValue::Undefined);
7617                }
7618                match &evaluated_args[0] {
7619                    JValue::String(s) => Ok(functions::encoding::decode_url_component(s)?),
7620                    _ => Err(EvaluatorError::TypeError(
7621                        "decodeUrlComponent() requires a string argument".to_string(),
7622                    )),
7623                }
7624            }
7625            "encodeUrl" => {
7626                if evaluated_args.len() != 1 {
7627                    return Err(EvaluatorError::EvaluationError(
7628                        "encodeUrl() requires exactly 1 argument".to_string(),
7629                    ));
7630                }
7631                if evaluated_args[0].is_null() {
7632                    return Ok(JValue::Null);
7633                }
7634                if evaluated_args[0].is_undefined() {
7635                    return Ok(JValue::Undefined);
7636                }
7637                match &evaluated_args[0] {
7638                    JValue::String(s) => Ok(functions::encoding::encode_url(s)?),
7639                    _ => Err(EvaluatorError::TypeError(
7640                        "encodeUrl() requires a string argument".to_string(),
7641                    )),
7642                }
7643            }
7644            "decodeUrl" => {
7645                if evaluated_args.len() != 1 {
7646                    return Err(EvaluatorError::EvaluationError(
7647                        "decodeUrl() requires exactly 1 argument".to_string(),
7648                    ));
7649                }
7650                if evaluated_args[0].is_null() {
7651                    return Ok(JValue::Null);
7652                }
7653                if evaluated_args[0].is_undefined() {
7654                    return Ok(JValue::Undefined);
7655                }
7656                match &evaluated_args[0] {
7657                    JValue::String(s) => Ok(functions::encoding::decode_url(s)?),
7658                    _ => Err(EvaluatorError::TypeError(
7659                        "decodeUrl() requires a string argument".to_string(),
7660                    )),
7661                }
7662            }
7663
7664            "error" => {
7665                // $error(message) - throw error with custom message
7666                if evaluated_args.is_empty() {
7667                    // No message provided
7668                    return Err(EvaluatorError::EvaluationError(
7669                        "D3137: $error() function evaluated".to_string(),
7670                    ));
7671                }
7672
7673                match &evaluated_args[0] {
7674                    JValue::String(s) => {
7675                        Err(EvaluatorError::EvaluationError(format!("D3137: {}", s)))
7676                    }
7677                    _ => Err(EvaluatorError::TypeError(
7678                        "T0410: Argument 1 of function error does not match function signature"
7679                            .to_string(),
7680                    )),
7681                }
7682            }
7683            "assert" => {
7684                // $assert(condition, message) - throw error if condition is false
7685                if evaluated_args.is_empty() || evaluated_args.len() > 2 {
7686                    return Err(EvaluatorError::EvaluationError(
7687                        "assert() requires 1 or 2 arguments".to_string(),
7688                    ));
7689                }
7690
7691                // First argument must be a boolean
7692                let condition = match &evaluated_args[0] {
7693                    JValue::Bool(b) => *b,
7694                    _ => {
7695                        return Err(EvaluatorError::TypeError(
7696                            "T0410: Argument 1 of function $assert does not match function signature".to_string(),
7697                        ));
7698                    }
7699                };
7700
7701                if !condition {
7702                    let message = if evaluated_args.len() == 2 {
7703                        match &evaluated_args[1] {
7704                            JValue::String(s) => s.clone(),
7705                            _ => Rc::from("$assert() statement failed"),
7706                        }
7707                    } else {
7708                        Rc::from("$assert() statement failed")
7709                    };
7710                    return Err(EvaluatorError::EvaluationError(format!(
7711                        "D3141: {}",
7712                        message
7713                    )));
7714                }
7715
7716                Ok(JValue::Null)
7717            }
7718
7719            "eval" => {
7720                // $eval(expression [, context]) - parse and evaluate a JSONata expression at runtime
7721                if evaluated_args.is_empty() || evaluated_args.len() > 2 {
7722                    return Err(EvaluatorError::EvaluationError(
7723                        "T0410: Argument 1 of function $eval must be a string".to_string(),
7724                    ));
7725                }
7726
7727                // If the first argument is null/undefined, return undefined
7728                if evaluated_args[0].is_null() {
7729                    return Ok(JValue::Null);
7730                }
7731                if evaluated_args[0].is_undefined() {
7732                    return Ok(JValue::Undefined);
7733                }
7734
7735                // First argument must be a string expression
7736                let expr_str = match &evaluated_args[0] {
7737                    JValue::String(s) => &**s,
7738                    _ => {
7739                        return Err(EvaluatorError::EvaluationError(
7740                            "T0410: Argument 1 of function $eval must be a string".to_string(),
7741                        ));
7742                    }
7743                };
7744
7745                // Parse the expression
7746                let parsed_ast = match parser::parse(expr_str) {
7747                    Ok(ast) => ast,
7748                    Err(e) => {
7749                        // D3120 is the error code for parse errors in $eval
7750                        return Err(EvaluatorError::EvaluationError(format!(
7751                            "D3120: The expression passed to $eval cannot be parsed: {}",
7752                            e
7753                        )));
7754                    }
7755                };
7756
7757                // Determine the context to use for evaluation
7758                let eval_context = if evaluated_args.len() == 2 {
7759                    &evaluated_args[1]
7760                } else {
7761                    data
7762                };
7763
7764                // Evaluate the parsed expression
7765                match self.evaluate_internal(&parsed_ast, eval_context) {
7766                    Ok(result) => Ok(result),
7767                    Err(e) => {
7768                        // D3121 is the error code for evaluation errors in $eval
7769                        let err_msg = e.to_string();
7770                        if err_msg.starts_with("D3121") || err_msg.contains("Unknown function") {
7771                            Err(EvaluatorError::EvaluationError(format!(
7772                                "D3121: {}",
7773                                err_msg
7774                            )))
7775                        } else {
7776                            Err(e)
7777                        }
7778                    }
7779                }
7780            }
7781
7782            "now" => {
7783                if !evaluated_args.is_empty() {
7784                    return Err(EvaluatorError::EvaluationError(
7785                        "now() takes no arguments".to_string(),
7786                    ));
7787                }
7788                Ok(crate::datetime::now())
7789            }
7790
7791            "millis" => {
7792                if !evaluated_args.is_empty() {
7793                    return Err(EvaluatorError::EvaluationError(
7794                        "millis() takes no arguments".to_string(),
7795                    ));
7796                }
7797                Ok(crate::datetime::millis())
7798            }
7799
7800            "toMillis" => {
7801                if evaluated_args.is_empty() || evaluated_args.len() > 2 {
7802                    return Err(EvaluatorError::EvaluationError(
7803                        "toMillis() requires 1 or 2 arguments".to_string(),
7804                    ));
7805                }
7806
7807                match &evaluated_args[0] {
7808                    JValue::String(s) => {
7809                        // Optional second argument is a picture string for custom parsing
7810                        if evaluated_args.len() == 2 {
7811                            match &evaluated_args[1] {
7812                                JValue::String(picture) => {
7813                                    // Use custom picture format parsing
7814                                    Ok(crate::datetime::to_millis_with_picture(s, picture)?)
7815                                }
7816                                JValue::Null => Ok(JValue::Null),
7817                                JValue::Undefined => Ok(JValue::Undefined),
7818                                _ => Err(EvaluatorError::TypeError(
7819                                    "toMillis() second argument must be a string".to_string(),
7820                                )),
7821                            }
7822                        } else {
7823                            // Use ISO 8601 partial date parsing
7824                            Ok(crate::datetime::to_millis(s)?)
7825                        }
7826                    }
7827                    JValue::Null => Ok(JValue::Null),
7828                    JValue::Undefined => Ok(JValue::Undefined),
7829                    _ => Err(EvaluatorError::TypeError(
7830                        "toMillis() requires a string argument".to_string(),
7831                    )),
7832                }
7833            }
7834
7835            "fromMillis" => {
7836                if evaluated_args.len() != 1 {
7837                    return Err(EvaluatorError::EvaluationError(
7838                        "fromMillis() requires exactly 1 argument".to_string(),
7839                    ));
7840                }
7841
7842                match &evaluated_args[0] {
7843                    JValue::Number(n) => {
7844                        let millis = (if n.fract() == 0.0 {
7845                            Ok(*n as i64)
7846                        } else {
7847                            Err(())
7848                        })
7849                        .map_err(|_| {
7850                            EvaluatorError::TypeError(
7851                                "fromMillis() requires an integer".to_string(),
7852                            )
7853                        })?;
7854                        Ok(crate::datetime::from_millis(millis)?)
7855                    }
7856                    JValue::Null => Ok(JValue::Null),
7857                    JValue::Undefined => Ok(JValue::Undefined),
7858                    _ => Err(EvaluatorError::TypeError(
7859                        "fromMillis() requires a number argument".to_string(),
7860                    )),
7861                }
7862            }
7863
7864            _ => Err(EvaluatorError::ReferenceError(format!(
7865                "Unknown function: {}",
7866                name
7867            ))),
7868        }
7869    }
7870
7871    /// Apply a function (lambda or expression) to values
7872    ///
7873    /// This handles both:
7874    /// 1. Lambda nodes: function($x) { $x * 2 } - binds parameters and evaluates body
7875    /// 2. Simple expressions: price * 2 - evaluates with values as context
7876    fn apply_function(
7877        &mut self,
7878        func_node: &AstNode,
7879        values: &[JValue],
7880        data: &JValue,
7881    ) -> Result<JValue, EvaluatorError> {
7882        match func_node {
7883            AstNode::Lambda {
7884                params,
7885                body,
7886                signature,
7887                thunk,
7888            } => {
7889                // Direct lambda - invoke it
7890                self.invoke_lambda(params, body, signature.as_ref(), values, data, *thunk)
7891            }
7892            AstNode::Function {
7893                name,
7894                args,
7895                is_builtin,
7896            } => {
7897                // Function call - check if it has placeholders (partial application)
7898                let has_placeholder = args.iter().any(|arg| matches!(arg, AstNode::Placeholder));
7899
7900                if has_placeholder {
7901                    // This is a partial application - evaluate it to get the lambda value
7902                    let partial_lambda =
7903                        self.create_partial_application(name, args, *is_builtin, data)?;
7904
7905                    // Now invoke the partial lambda with the provided values
7906                    if let Some(stored) = self.lookup_lambda_from_value(&partial_lambda) {
7907                        return self.invoke_stored_lambda(&stored, values, data);
7908                    }
7909                    Err(EvaluatorError::EvaluationError(
7910                        "Failed to apply partial application".to_string(),
7911                    ))
7912                } else {
7913                    // Regular function call without placeholders
7914                    // Evaluate it and apply if it returns a function
7915                    let result = self.evaluate_internal(func_node, data)?;
7916
7917                    // Check if result is a lambda value
7918                    if let Some(stored) = self.lookup_lambda_from_value(&result) {
7919                        return self.invoke_stored_lambda(&stored, values, data);
7920                    }
7921
7922                    // Otherwise just return the result
7923                    Ok(result)
7924                }
7925            }
7926            AstNode::Variable(var_name) => {
7927                // Check if this variable holds a stored lambda
7928                if let Some(stored_lambda) = self.context.lookup_lambda(var_name).cloned() {
7929                    self.invoke_stored_lambda(&stored_lambda, values, data)
7930                } else if let Some(value) = self.context.lookup(var_name).cloned() {
7931                    // Check if this variable holds a lambda value
7932                    // This handles lambdas passed as bound arguments in partial applications
7933                    if let Some(stored) = self.lookup_lambda_from_value(&value) {
7934                        return self.invoke_stored_lambda(&stored, values, data);
7935                    }
7936                    // Regular variable value - evaluate with first value as context
7937                    if values.is_empty() {
7938                        self.evaluate_internal(func_node, data)
7939                    } else {
7940                        self.evaluate_internal(func_node, &values[0])
7941                    }
7942                } else if self.is_builtin_function(var_name) {
7943                    // This is a built-in function reference (e.g., $string, $number)
7944                    // Call it directly with the provided values (already evaluated)
7945                    self.call_builtin_with_values(var_name, values)
7946                } else {
7947                    // Unknown variable - evaluate with first value as context
7948                    if values.is_empty() {
7949                        self.evaluate_internal(func_node, data)
7950                    } else {
7951                        self.evaluate_internal(func_node, &values[0])
7952                    }
7953                }
7954            }
7955            _ => {
7956                // For non-lambda expressions, evaluate with first value as context
7957                if values.is_empty() {
7958                    self.evaluate_internal(func_node, data)
7959                } else {
7960                    self.evaluate_internal(func_node, &values[0])
7961                }
7962            }
7963        }
7964    }
7965
7966    /// Execute a transform operator on the bound $ value
7967    fn execute_transform(
7968        &mut self,
7969        location: &AstNode,
7970        update: &AstNode,
7971        delete: Option<&AstNode>,
7972        _original_data: &JValue,
7973    ) -> Result<JValue, EvaluatorError> {
7974        // Get the input value from $ binding
7975        let input = self
7976            .context
7977            .lookup("$")
7978            .ok_or_else(|| {
7979                EvaluatorError::EvaluationError("Transform requires $ binding".to_string())
7980            })?
7981            .clone();
7982
7983        // Evaluate location expression on the input to get objects to transform
7984        let located_objects = self.evaluate_internal(location, &input)?;
7985
7986        // Collect target objects into a vector for comparison
7987        let targets: Vec<JValue> = match located_objects {
7988            JValue::Array(arr) => arr.to_vec(),
7989            JValue::Object(_) => vec![located_objects],
7990            JValue::Null => Vec::new(),
7991            other => vec![other],
7992        };
7993
7994        // Validate update parameter - must be an object constructor
7995        // We need to check this before evaluation in case of errors
7996        // For now, we'll validate after evaluation in the transform helper
7997
7998        // Parse delete field names if provided
7999        let delete_fields: Vec<String> = if let Some(delete_node) = delete {
8000            let delete_val = self.evaluate_internal(delete_node, &input)?;
8001            match delete_val {
8002                JValue::Array(arr) => arr
8003                    .iter()
8004                    .filter_map(|v| match v {
8005                        JValue::String(s) => Some(s.to_string()),
8006                        _ => None,
8007                    })
8008                    .collect(),
8009                JValue::String(s) => vec![s.to_string()],
8010                JValue::Null | JValue::Undefined => Vec::new(), // Undefined variable is treated as no deletion
8011                _ => {
8012                    // Delete parameter must be an array of strings or a string
8013                    return Err(EvaluatorError::EvaluationError(
8014                        "T2012: The third argument of the transform operator must be an array of strings".to_string()
8015                    ));
8016                }
8017            }
8018        } else {
8019            Vec::new()
8020        };
8021
8022        // Recursive helper to apply transformation throughout the structure
8023        fn apply_transform_deep(
8024            evaluator: &mut Evaluator,
8025            value: &JValue,
8026            targets: &[JValue],
8027            update: &AstNode,
8028            delete_fields: &[String],
8029        ) -> Result<JValue, EvaluatorError> {
8030            // Check if this value is one of the targets to transform
8031            // Use JValue's PartialEq for semantic equality comparison
8032            if targets.iter().any(|t| t == value) {
8033                // Transform this object
8034                if let JValue::Object(map_rc) = value.clone() {
8035                    let mut map = (*map_rc).clone();
8036                    let update_val = evaluator.evaluate_internal(update, value)?;
8037                    // Validate that update evaluates to an object or null (undefined)
8038                    match update_val {
8039                        JValue::Object(update_map) => {
8040                            for (key, val) in update_map.iter() {
8041                                map.insert(key.clone(), val.clone());
8042                            }
8043                        }
8044                        JValue::Null | JValue::Undefined => {
8045                            // Null/undefined means no updates, just continue to deletions
8046                        }
8047                        _ => {
8048                            return Err(EvaluatorError::EvaluationError(
8049                                "T2011: The second argument of the transform operator must evaluate to an object".to_string()
8050                            ));
8051                        }
8052                    }
8053                    for field in delete_fields {
8054                        map.shift_remove(field);
8055                    }
8056                    return Ok(JValue::object(map));
8057                }
8058                return Ok(value.clone());
8059            }
8060
8061            // Otherwise, recursively process children to find and transform targets
8062            match value {
8063                JValue::Object(map) => {
8064                    let mut new_map = IndexMap::new();
8065                    for (k, v) in map.iter() {
8066                        new_map.insert(
8067                            k.clone(),
8068                            apply_transform_deep(evaluator, v, targets, update, delete_fields)?,
8069                        );
8070                    }
8071                    Ok(JValue::object(new_map))
8072                }
8073                JValue::Array(arr) => {
8074                    let mut new_arr = Vec::new();
8075                    for item in arr.iter() {
8076                        new_arr.push(apply_transform_deep(
8077                            evaluator,
8078                            item,
8079                            targets,
8080                            update,
8081                            delete_fields,
8082                        )?);
8083                    }
8084                    Ok(JValue::array(new_arr))
8085                }
8086                _ => Ok(value.clone()),
8087            }
8088        }
8089
8090        // Apply transformation recursively starting from input
8091        apply_transform_deep(self, &input, &targets, update, &delete_fields)
8092    }
8093
8094    /// Helper to invoke a lambda with given parameters
8095    fn invoke_lambda(
8096        &mut self,
8097        params: &[String],
8098        body: &AstNode,
8099        signature: Option<&String>,
8100        values: &[JValue],
8101        data: &JValue,
8102        thunk: bool,
8103    ) -> Result<JValue, EvaluatorError> {
8104        self.invoke_lambda_with_env(params, body, signature, values, data, None, None, thunk)
8105    }
8106
8107    /// Invoke a lambda with optional captured environment (for closures)
8108    fn invoke_lambda_with_env(
8109        &mut self,
8110        params: &[String],
8111        body: &AstNode,
8112        signature: Option<&String>,
8113        values: &[JValue],
8114        data: &JValue,
8115        captured_env: Option<&HashMap<String, JValue>>,
8116        captured_data: Option<&JValue>,
8117        thunk: bool,
8118    ) -> Result<JValue, EvaluatorError> {
8119        // If this is a thunk (has tail calls), use TCO trampoline
8120        if thunk {
8121            let stored = StoredLambda {
8122                params: params.to_vec(),
8123                body: body.clone(),
8124                compiled_body: None, // Thunks use TCO, not the compiled fast path
8125                signature: signature.cloned(),
8126                captured_env: captured_env.cloned().unwrap_or_default(),
8127                captured_data: captured_data.cloned(),
8128                thunk,
8129            };
8130            return self.invoke_lambda_with_tco(&stored, values, data);
8131        }
8132
8133        // Validate signature if present, and get coerced arguments
8134        // Push a new scope for this lambda invocation
8135        self.context.push_scope();
8136
8137        // First apply captured environment (for closures)
8138        if let Some(env) = captured_env {
8139            for (name, value) in env {
8140                self.context.bind(name.clone(), value.clone());
8141            }
8142        }
8143
8144        if let Some(sig_str) = signature {
8145            // Validate and coerce arguments with signature
8146            let coerced_values = match crate::signature::Signature::parse(sig_str) {
8147                Ok(sig) => match sig.validate_and_coerce(values) {
8148                    Ok(coerced) => coerced,
8149                    Err(e) => {
8150                        self.context.pop_scope();
8151                        match e {
8152                            crate::signature::SignatureError::UndefinedArgument => {
8153                                return Ok(JValue::Null);
8154                            }
8155                            crate::signature::SignatureError::ArgumentTypeMismatch {
8156                                index,
8157                                expected,
8158                            } => {
8159                                return Err(EvaluatorError::TypeError(
8160                                        format!("T0410: Argument {} of function does not match function signature (expected {})", index, expected)
8161                                    ));
8162                            }
8163                            crate::signature::SignatureError::ArrayTypeMismatch {
8164                                index,
8165                                expected,
8166                            } => {
8167                                return Err(EvaluatorError::TypeError(format!(
8168                                    "T0412: Argument {} of function must be an array of {}",
8169                                    index, expected
8170                                )));
8171                            }
8172                            _ => {
8173                                return Err(EvaluatorError::TypeError(format!(
8174                                    "Signature validation failed: {}",
8175                                    e
8176                                )));
8177                            }
8178                        }
8179                    }
8180                },
8181                Err(e) => {
8182                    self.context.pop_scope();
8183                    return Err(EvaluatorError::EvaluationError(format!(
8184                        "Invalid signature: {}",
8185                        e
8186                    )));
8187                }
8188            };
8189            // Bind coerced values to params
8190            for (i, param) in params.iter().enumerate() {
8191                let value = coerced_values.get(i).cloned().unwrap_or(JValue::Undefined);
8192                self.context.bind(param.clone(), value);
8193            }
8194        } else {
8195            // No signature - bind directly from values slice (no allocation)
8196            for (i, param) in params.iter().enumerate() {
8197                let value = values.get(i).cloned().unwrap_or(JValue::Undefined);
8198                self.context.bind(param.clone(), value);
8199            }
8200        }
8201
8202        // Check if this is a partial application (body is a special marker string)
8203        if let AstNode::String(body_str) = body {
8204            if body_str.starts_with("__partial_call:") {
8205                // Parse the partial call info
8206                let parts: Vec<&str> = body_str.split(':').collect();
8207                if parts.len() >= 4 {
8208                    let func_name = parts[1];
8209                    let is_builtin = parts[2] == "true";
8210                    let total_args: usize = parts[3].parse().unwrap_or(0);
8211
8212                    // Get placeholder positions from captured env
8213                    let placeholder_positions: Vec<usize> = if let Some(env) = captured_env {
8214                        if let Some(JValue::Array(positions)) = env.get("__placeholder_positions") {
8215                            positions
8216                                .iter()
8217                                .filter_map(|v| v.as_f64().map(|n| n as usize))
8218                                .collect()
8219                        } else {
8220                            vec![]
8221                        }
8222                    } else {
8223                        vec![]
8224                    };
8225
8226                    // Reconstruct the full argument list
8227                    let mut full_args: Vec<JValue> = vec![JValue::Null; total_args];
8228
8229                    // Fill in bound arguments from captured environment
8230                    if let Some(env) = captured_env {
8231                        for (key, value) in env {
8232                            if key.starts_with("__bound_arg_") {
8233                                if let Ok(pos) = key[12..].parse::<usize>() {
8234                                    if pos < total_args {
8235                                        full_args[pos] = value.clone();
8236                                    }
8237                                }
8238                            }
8239                        }
8240                    }
8241
8242                    // Fill in placeholder positions with provided values
8243                    for (i, &pos) in placeholder_positions.iter().enumerate() {
8244                        if pos < total_args {
8245                            let value = values.get(i).cloned().unwrap_or(JValue::Null);
8246                            full_args[pos] = value;
8247                        }
8248                    }
8249
8250                    // Pop lambda scope, then push a new scope for temp args
8251                    self.context.pop_scope();
8252                    self.context.push_scope();
8253
8254                    // Build AST nodes for the function call arguments
8255                    let mut temp_args: Vec<AstNode> = Vec::new();
8256                    for (i, value) in full_args.iter().enumerate() {
8257                        let temp_name = format!("__temp_arg_{}", i);
8258                        self.context.bind(temp_name.clone(), value.clone());
8259                        temp_args.push(AstNode::Variable(temp_name));
8260                    }
8261
8262                    // Call the original function
8263                    let result =
8264                        self.evaluate_function_call(func_name, &temp_args, is_builtin, data);
8265
8266                    // Pop temp scope
8267                    self.context.pop_scope();
8268
8269                    return result;
8270                }
8271            }
8272        }
8273
8274        // Evaluate lambda body (normal case)
8275        // Use captured_data for lexical scoping if available, otherwise use call-site data
8276        let body_data = captured_data.unwrap_or(data);
8277        let result = self.evaluate_internal(body, body_data)?;
8278
8279        // Pop lambda scope, preserving any lambdas referenced by the return value
8280        // Fast path: scalar results can never contain lambda references
8281        let is_scalar = matches!(
8282            &result,
8283            JValue::Number(_)
8284                | JValue::Bool(_)
8285                | JValue::String(_)
8286                | JValue::Null
8287                | JValue::Undefined
8288        );
8289        if is_scalar {
8290            self.context.pop_scope();
8291        } else {
8292            let lambdas_to_keep = self.extract_lambda_ids(&result);
8293            self.context.pop_scope_preserving_lambdas(&lambdas_to_keep);
8294        }
8295
8296        Ok(result)
8297    }
8298
8299    /// Invoke a lambda with tail call optimization using a trampoline
8300    /// This method uses an iterative loop to handle tail-recursive calls without
8301    /// growing the stack, enabling deep recursion for tail-recursive functions.
8302    fn invoke_lambda_with_tco(
8303        &mut self,
8304        stored_lambda: &StoredLambda,
8305        initial_args: &[JValue],
8306        data: &JValue,
8307    ) -> Result<JValue, EvaluatorError> {
8308        let mut current_lambda = stored_lambda.clone();
8309        let mut current_args = initial_args.to_vec();
8310        let mut current_data = data.clone();
8311
8312        // Maximum number of tail call iterations to prevent infinite loops
8313        // This is much higher than non-TCO depth limit since TCO doesn't grow the stack
8314        const MAX_TCO_ITERATIONS: usize = 100_000;
8315        let mut iterations = 0;
8316
8317        // Push a persistent scope for the TCO trampoline loop.
8318        // This scope persists across all iterations so that lambdas defined
8319        // in one iteration (like recursive $iter) remain available in subsequent ones.
8320        self.context.push_scope();
8321
8322        // Trampoline loop - keeps evaluating until we get a final value
8323        let result = loop {
8324            iterations += 1;
8325            if iterations > MAX_TCO_ITERATIONS {
8326                self.context.pop_scope();
8327                return Err(EvaluatorError::EvaluationError(
8328                    "U1001: Stack overflow - maximum recursion depth (500) exceeded".to_string(),
8329                ));
8330            }
8331
8332            // Evaluate the lambda body within the persistent scope
8333            let result =
8334                self.invoke_lambda_body_for_tco(&current_lambda, &current_args, &current_data)?;
8335
8336            match result {
8337                LambdaResult::JValue(v) => break v,
8338                LambdaResult::TailCall { lambda, args, data } => {
8339                    // Continue with the tail call - no stack growth
8340                    current_lambda = *lambda;
8341                    current_args = args;
8342                    current_data = data;
8343                }
8344            }
8345        };
8346
8347        // Pop the persistent TCO scope, preserving lambdas referenced by the result
8348        let lambdas_to_keep = self.extract_lambda_ids(&result);
8349        self.context.pop_scope_preserving_lambdas(&lambdas_to_keep);
8350
8351        Ok(result)
8352    }
8353
8354    /// Evaluate a lambda body, detecting tail calls for TCO
8355    /// Returns either a final value or a tail call continuation.
8356    /// NOTE: Does not push/pop its own scope - the caller (invoke_lambda_with_tco)
8357    /// manages the persistent scope for the trampoline loop.
8358    fn invoke_lambda_body_for_tco(
8359        &mut self,
8360        lambda: &StoredLambda,
8361        values: &[JValue],
8362        data: &JValue,
8363    ) -> Result<LambdaResult, EvaluatorError> {
8364        // Validate signature if present
8365        let coerced_values = if let Some(sig_str) = &lambda.signature {
8366            match crate::signature::Signature::parse(sig_str) {
8367                Ok(sig) => match sig.validate_and_coerce(values) {
8368                    Ok(coerced) => coerced,
8369                    Err(e) => match e {
8370                        crate::signature::SignatureError::UndefinedArgument => {
8371                            return Ok(LambdaResult::JValue(JValue::Null));
8372                        }
8373                        crate::signature::SignatureError::ArgumentTypeMismatch {
8374                            index,
8375                            expected,
8376                        } => {
8377                            return Err(EvaluatorError::TypeError(
8378                                        format!("T0410: Argument {} of function does not match function signature (expected {})", index, expected)
8379                                    ));
8380                        }
8381                        crate::signature::SignatureError::ArrayTypeMismatch { index, expected } => {
8382                            return Err(EvaluatorError::TypeError(format!(
8383                                "T0412: Argument {} of function must be an array of {}",
8384                                index, expected
8385                            )));
8386                        }
8387                        _ => {
8388                            return Err(EvaluatorError::TypeError(format!(
8389                                "Signature validation failed: {}",
8390                                e
8391                            )));
8392                        }
8393                    },
8394                },
8395                Err(e) => {
8396                    return Err(EvaluatorError::EvaluationError(format!(
8397                        "Invalid signature: {}",
8398                        e
8399                    )));
8400                }
8401            }
8402        } else {
8403            values.to_vec()
8404        };
8405
8406        // Bind directly into the persistent scope (managed by invoke_lambda_with_tco)
8407        // Apply captured environment
8408        for (name, value) in &lambda.captured_env {
8409            self.context.bind(name.clone(), value.clone());
8410        }
8411
8412        // Bind parameters
8413        for (i, param) in lambda.params.iter().enumerate() {
8414            let value = coerced_values.get(i).cloned().unwrap_or(JValue::Null);
8415            self.context.bind(param.clone(), value);
8416        }
8417
8418        // Evaluate the body with tail call detection
8419        let body_data = lambda.captured_data.as_ref().unwrap_or(data);
8420        self.evaluate_for_tco(&lambda.body, body_data)
8421    }
8422
8423    /// Evaluate an expression for TCO, detecting tail calls
8424    /// Returns LambdaResult::TailCall if the expression is a function call to a user lambda
8425    fn evaluate_for_tco(
8426        &mut self,
8427        node: &AstNode,
8428        data: &JValue,
8429    ) -> Result<LambdaResult, EvaluatorError> {
8430        match node {
8431            // Conditional: evaluate condition, then evaluate the chosen branch for TCO
8432            AstNode::Conditional {
8433                condition,
8434                then_branch,
8435                else_branch,
8436            } => {
8437                let cond_value = self.evaluate_internal(condition, data)?;
8438                let is_truthy = self.is_truthy(&cond_value);
8439
8440                if is_truthy {
8441                    self.evaluate_for_tco(then_branch, data)
8442                } else if let Some(else_expr) = else_branch {
8443                    self.evaluate_for_tco(else_expr, data)
8444                } else {
8445                    Ok(LambdaResult::JValue(JValue::Null))
8446                }
8447            }
8448
8449            // Block: evaluate all but last normally, last for TCO
8450            AstNode::Block(exprs) => {
8451                if exprs.is_empty() {
8452                    return Ok(LambdaResult::JValue(JValue::Null));
8453                }
8454
8455                // Evaluate all expressions except the last
8456                let mut result = JValue::Null;
8457                for (i, expr) in exprs.iter().enumerate() {
8458                    if i == exprs.len() - 1 {
8459                        // Last expression - evaluate for TCO
8460                        return self.evaluate_for_tco(expr, data);
8461                    } else {
8462                        result = self.evaluate_internal(expr, data)?;
8463                    }
8464                }
8465                Ok(LambdaResult::JValue(result))
8466            }
8467
8468            // Variable binding: evaluate value, bind, then evaluate result for TCO if present
8469            AstNode::Binary {
8470                op: BinaryOp::ColonEqual,
8471                lhs,
8472                rhs,
8473            } => {
8474                // This is var := value; get the variable name
8475                let var_name = match lhs.as_ref() {
8476                    AstNode::Variable(name) => name.clone(),
8477                    _ => {
8478                        // Not a simple variable binding, evaluate normally
8479                        let result = self.evaluate_internal(node, data)?;
8480                        return Ok(LambdaResult::JValue(result));
8481                    }
8482                };
8483
8484                // Check if RHS is a lambda - store it specially
8485                if let AstNode::Lambda {
8486                    params,
8487                    body,
8488                    signature,
8489                    thunk,
8490                } = rhs.as_ref()
8491                {
8492                    let captured_env = self.capture_environment_for(body, params);
8493                    let compiled_body = if !thunk {
8494                        let var_refs: Vec<&str> = params.iter().map(|s| s.as_str()).collect();
8495                        try_compile_expr_with_allowed_vars(body, &var_refs)
8496                    } else {
8497                        None
8498                    };
8499                    let stored_lambda = StoredLambda {
8500                        params: params.clone(),
8501                        body: (**body).clone(),
8502                        compiled_body,
8503                        signature: signature.clone(),
8504                        captured_env,
8505                        captured_data: Some(data.clone()),
8506                        thunk: *thunk,
8507                    };
8508                    self.context.bind_lambda(var_name, stored_lambda);
8509                    let lambda_repr =
8510                        JValue::lambda("anon", params.clone(), None::<String>, None::<String>);
8511                    return Ok(LambdaResult::JValue(lambda_repr));
8512                }
8513
8514                // Evaluate the RHS
8515                let value = self.evaluate_internal(rhs, data)?;
8516                self.context.bind(var_name, value.clone());
8517                Ok(LambdaResult::JValue(value))
8518            }
8519
8520            // Function call - this is where TCO happens
8521            AstNode::Function { name, args, .. } => {
8522                // Check if this is a call to a stored lambda (user function)
8523                if let Some(stored_lambda) = self.context.lookup_lambda(name).cloned() {
8524                    if stored_lambda.thunk {
8525                        let mut evaluated_args = Vec::with_capacity(args.len());
8526                        for arg in args {
8527                            evaluated_args.push(self.evaluate_internal(arg, data)?);
8528                        }
8529                        return Ok(LambdaResult::TailCall {
8530                            lambda: Box::new(stored_lambda),
8531                            args: evaluated_args,
8532                            data: data.clone(),
8533                        });
8534                    }
8535                }
8536                // Not a thunk lambda - evaluate normally
8537                let result = self.evaluate_internal(node, data)?;
8538                Ok(LambdaResult::JValue(result))
8539            }
8540
8541            // Call node (calling a lambda value)
8542            AstNode::Call { procedure, args } => {
8543                // Evaluate the procedure to get the callable
8544                let callable = self.evaluate_internal(procedure, data)?;
8545
8546                // Check if it's a lambda with TCO
8547                if let JValue::Lambda { lambda_id, .. } = &callable {
8548                    if let Some(stored_lambda) = self.context.lookup_lambda(lambda_id).cloned() {
8549                        if stored_lambda.thunk {
8550                            let mut evaluated_args = Vec::with_capacity(args.len());
8551                            for arg in args {
8552                                evaluated_args.push(self.evaluate_internal(arg, data)?);
8553                            }
8554                            return Ok(LambdaResult::TailCall {
8555                                lambda: Box::new(stored_lambda),
8556                                args: evaluated_args,
8557                                data: data.clone(),
8558                            });
8559                        }
8560                    }
8561                }
8562                // Not a thunk - evaluate normally
8563                let result = self.evaluate_internal(node, data)?;
8564                Ok(LambdaResult::JValue(result))
8565            }
8566
8567            // Variable reference that might be a function call
8568            // This handles cases like $f($x) where $f is referenced by name
8569            AstNode::Variable(_) => {
8570                let result = self.evaluate_internal(node, data)?;
8571                Ok(LambdaResult::JValue(result))
8572            }
8573
8574            // Any other expression - evaluate normally
8575            _ => {
8576                let result = self.evaluate_internal(node, data)?;
8577                Ok(LambdaResult::JValue(result))
8578            }
8579        }
8580    }
8581
8582    /// Match with custom matcher function
8583    ///
8584    /// Implements custom matcher support for $match(str, matcherFunction, limit?)
8585    /// The matcher function is called with the string and returns:
8586    /// { match: string, start: number, end: number, groups: [], next: function }
8587    /// The next function is called repeatedly to get subsequent matches
8588    fn match_with_custom_matcher(
8589        &mut self,
8590        str_value: &str,
8591        matcher_node: &AstNode,
8592        limit: Option<usize>,
8593        data: &JValue,
8594    ) -> Result<JValue, EvaluatorError> {
8595        let mut results = Vec::new();
8596        let mut count = 0;
8597
8598        // Call the matcher function with the string
8599        let str_val = JValue::string(str_value.to_string());
8600        let mut current_match = self.apply_function(matcher_node, &[str_val], data)?;
8601
8602        // Iterate through matches following the 'next' chain
8603        while !current_match.is_undefined() && !current_match.is_null() {
8604            // Check limit
8605            if let Some(lim) = limit {
8606                if count >= lim {
8607                    break;
8608                }
8609            }
8610
8611            // Extract match information from the result object
8612            if let JValue::Object(ref match_obj) = current_match {
8613                // Validate that this is a proper match object
8614                let has_match = match_obj.contains_key("match");
8615                let has_start = match_obj.contains_key("start");
8616                let has_end = match_obj.contains_key("end");
8617                let has_groups = match_obj.contains_key("groups");
8618                let has_next = match_obj.contains_key("next");
8619
8620                if !has_match && !has_start && !has_end && !has_groups && !has_next {
8621                    // Invalid matcher result - T1010 error
8622                    return Err(EvaluatorError::EvaluationError(
8623                        "T1010: The matcher function did not return the correct object structure"
8624                            .to_string(),
8625                    ));
8626                }
8627
8628                // Build the result match object (match, index, groups)
8629                let mut result_obj = IndexMap::new();
8630
8631                if let Some(match_val) = match_obj.get("match") {
8632                    result_obj.insert("match".to_string(), match_val.clone());
8633                }
8634
8635                if let Some(start_val) = match_obj.get("start") {
8636                    result_obj.insert("index".to_string(), start_val.clone());
8637                }
8638
8639                if let Some(groups_val) = match_obj.get("groups") {
8640                    result_obj.insert("groups".to_string(), groups_val.clone());
8641                }
8642
8643                results.push(JValue::object(result_obj));
8644                count += 1;
8645
8646                // Get the next match by calling the 'next' function
8647                if let Some(next_func) = match_obj.get("next") {
8648                    if let Some(stored) = self.lookup_lambda_from_value(next_func) {
8649                        current_match = self.invoke_stored_lambda(&stored, &[], data)?;
8650                        continue;
8651                    }
8652                }
8653
8654                // No next function or couldn't call it - stop iteration
8655                break;
8656            } else {
8657                // Not a valid match object
8658                break;
8659            }
8660        }
8661
8662        // Return results
8663        if results.is_empty() {
8664            Ok(JValue::Undefined)
8665        } else {
8666            Ok(JValue::array(results))
8667        }
8668    }
8669
8670    /// Replace with lambda/function callback
8671    ///
8672    /// Implements lambda replacement for $replace(str, pattern, function, limit?)
8673    /// The function receives a match object with: match, start, end, groups
8674    fn replace_with_lambda(
8675        &mut self,
8676        str_value: &JValue,
8677        pattern_value: &JValue,
8678        lambda_value: &JValue,
8679        limit_value: Option<&JValue>,
8680        data: &JValue,
8681    ) -> Result<JValue, EvaluatorError> {
8682        // Extract string
8683        let s = match str_value {
8684            JValue::String(s) => &**s,
8685            _ => {
8686                return Err(EvaluatorError::TypeError(
8687                    "replace() requires string arguments".to_string(),
8688                ))
8689            }
8690        };
8691
8692        // Extract regex pattern
8693        let (pattern, flags) =
8694            crate::functions::string::extract_regex(pattern_value).ok_or_else(|| {
8695                EvaluatorError::TypeError(
8696                    "replace() pattern must be a regex when using lambda replacement".to_string(),
8697                )
8698            })?;
8699
8700        // Build regex
8701        let re = crate::functions::string::build_regex(&pattern, &flags)?;
8702
8703        // Parse limit
8704        let limit = if let Some(lim_val) = limit_value {
8705            match lim_val {
8706                JValue::Number(n) => {
8707                    let lim_f64 = *n;
8708                    if lim_f64 < 0.0 {
8709                        return Err(EvaluatorError::EvaluationError(format!(
8710                            "D3011: Limit must be non-negative, got {}",
8711                            lim_f64
8712                        )));
8713                    }
8714                    Some(lim_f64 as usize)
8715                }
8716                _ => {
8717                    return Err(EvaluatorError::TypeError(
8718                        "replace() limit must be a number".to_string(),
8719                    ))
8720                }
8721            }
8722        } else {
8723            None
8724        };
8725
8726        // Iterate through matches and replace using lambda
8727        let mut result = String::new();
8728        let mut last_end = 0;
8729        let mut count = 0;
8730
8731        for cap in re.captures_iter(s) {
8732            // Check limit
8733            if let Some(lim) = limit {
8734                if count >= lim {
8735                    break;
8736                }
8737            }
8738
8739            let m = cap.get(0).unwrap();
8740            let match_start = m.start();
8741            let match_end = m.end();
8742            let match_str = m.as_str();
8743
8744            // Add text before match
8745            result.push_str(&s[last_end..match_start]);
8746
8747            // Build match object
8748            let groups: Vec<JValue> = (1..cap.len())
8749                .map(|i| {
8750                    cap.get(i)
8751                        .map(|m| JValue::string(m.as_str().to_string()))
8752                        .unwrap_or(JValue::Null)
8753                })
8754                .collect();
8755
8756            let mut match_map = IndexMap::new();
8757            match_map.insert("match".to_string(), JValue::string(match_str));
8758            match_map.insert("start".to_string(), JValue::Number(match_start as f64));
8759            match_map.insert("end".to_string(), JValue::Number(match_end as f64));
8760            match_map.insert("groups".to_string(), JValue::array(groups));
8761            let match_obj = JValue::object(match_map);
8762
8763            // Invoke lambda with match object
8764            let stored_lambda = self.lookup_lambda_from_value(lambda_value).ok_or_else(|| {
8765                EvaluatorError::TypeError("Replacement must be a lambda function".to_string())
8766            })?;
8767            let lambda_result = self.invoke_stored_lambda(&stored_lambda, &[match_obj], data)?;
8768            let replacement_str = match lambda_result {
8769                JValue::String(s) => s,
8770                _ => {
8771                    return Err(EvaluatorError::TypeError(format!(
8772                        "D3012: Replacement function must return a string, got {:?}",
8773                        lambda_result
8774                    )))
8775                }
8776            };
8777
8778            // Add replacement
8779            result.push_str(&replacement_str);
8780
8781            last_end = match_end;
8782            count += 1;
8783        }
8784
8785        // Add remaining text after last match
8786        result.push_str(&s[last_end..]);
8787
8788        Ok(JValue::string(result))
8789    }
8790
8791    /// Capture the current environment bindings for closure support
8792    fn capture_current_environment(&self) -> HashMap<String, JValue> {
8793        self.context.all_bindings()
8794    }
8795
8796    /// Capture only the variables referenced by a lambda body (selective capture).
8797    /// This avoids cloning the entire environment when only a few variables are needed.
8798    fn capture_environment_for(
8799        &self,
8800        body: &AstNode,
8801        params: &[String],
8802    ) -> HashMap<String, JValue> {
8803        let free_vars = Self::collect_free_variables(body, params);
8804        if free_vars.is_empty() {
8805            return HashMap::new();
8806        }
8807        let mut result = HashMap::new();
8808        for var_name in &free_vars {
8809            if let Some(value) = self.context.lookup(var_name) {
8810                result.insert(var_name.clone(), value.clone());
8811            }
8812        }
8813        result
8814    }
8815
8816    /// Collect all free variables in an AST node that are not bound by the given params.
8817    /// A "free variable" is one that is referenced but not defined within the expression.
8818    fn collect_free_variables(body: &AstNode, params: &[String]) -> HashSet<String> {
8819        let mut free_vars = HashSet::new();
8820        let bound: HashSet<&str> = params.iter().map(|s| s.as_str()).collect();
8821        Self::collect_free_vars_walk(body, &bound, &mut free_vars);
8822        free_vars
8823    }
8824
8825    fn collect_free_vars_walk(node: &AstNode, bound: &HashSet<&str>, free: &mut HashSet<String>) {
8826        match node {
8827            AstNode::Variable(name) => {
8828                if !bound.contains(name.as_str()) {
8829                    free.insert(name.clone());
8830                }
8831            }
8832            AstNode::Function { name, args, .. } => {
8833                // Function name references a variable (e.g., $f(...))
8834                if !bound.contains(name.as_str()) {
8835                    free.insert(name.clone());
8836                }
8837                for arg in args {
8838                    Self::collect_free_vars_walk(arg, bound, free);
8839                }
8840            }
8841            AstNode::Lambda { params, body, .. } => {
8842                // Inner lambda introduces new bindings
8843                let mut inner_bound = bound.clone();
8844                for p in params {
8845                    inner_bound.insert(p.as_str());
8846                }
8847                Self::collect_free_vars_walk(body, &inner_bound, free);
8848            }
8849            AstNode::Binary { op, lhs, rhs } => {
8850                Self::collect_free_vars_walk(lhs, bound, free);
8851                Self::collect_free_vars_walk(rhs, bound, free);
8852                // For ColonEqual, note: the binding is visible after this expr in blocks,
8853                // but block handling takes care of that separately
8854                let _ = op;
8855            }
8856            AstNode::Unary { operand, .. } => {
8857                Self::collect_free_vars_walk(operand, bound, free);
8858            }
8859            AstNode::Path { steps } => {
8860                for step in steps {
8861                    Self::collect_free_vars_walk(&step.node, bound, free);
8862                    for stage in &step.stages {
8863                        match stage {
8864                            Stage::Filter(expr) => Self::collect_free_vars_walk(expr, bound, free),
8865                        }
8866                    }
8867                }
8868            }
8869            AstNode::Call { procedure, args } => {
8870                Self::collect_free_vars_walk(procedure, bound, free);
8871                for arg in args {
8872                    Self::collect_free_vars_walk(arg, bound, free);
8873                }
8874            }
8875            AstNode::Conditional {
8876                condition,
8877                then_branch,
8878                else_branch,
8879            } => {
8880                Self::collect_free_vars_walk(condition, bound, free);
8881                Self::collect_free_vars_walk(then_branch, bound, free);
8882                if let Some(else_expr) = else_branch {
8883                    Self::collect_free_vars_walk(else_expr, bound, free);
8884                }
8885            }
8886            AstNode::Block(exprs) => {
8887                let mut block_bound = bound.clone();
8888                for expr in exprs {
8889                    Self::collect_free_vars_walk(expr, &block_bound, free);
8890                    // Bindings introduced via := become bound for subsequent expressions
8891                    if let AstNode::Binary {
8892                        op: BinaryOp::ColonEqual,
8893                        lhs,
8894                        ..
8895                    } = expr
8896                    {
8897                        if let AstNode::Variable(var_name) = lhs.as_ref() {
8898                            block_bound.insert(var_name.as_str());
8899                        }
8900                    }
8901                }
8902            }
8903            AstNode::Array(exprs) | AstNode::ArrayGroup(exprs) => {
8904                for expr in exprs {
8905                    Self::collect_free_vars_walk(expr, bound, free);
8906                }
8907            }
8908            AstNode::Object(pairs) => {
8909                for (key, value) in pairs {
8910                    Self::collect_free_vars_walk(key, bound, free);
8911                    Self::collect_free_vars_walk(value, bound, free);
8912                }
8913            }
8914            AstNode::ObjectTransform { input, pattern } => {
8915                Self::collect_free_vars_walk(input, bound, free);
8916                for (key, value) in pattern {
8917                    Self::collect_free_vars_walk(key, bound, free);
8918                    Self::collect_free_vars_walk(value, bound, free);
8919                }
8920            }
8921            AstNode::Predicate(expr) | AstNode::FunctionApplication(expr) => {
8922                Self::collect_free_vars_walk(expr, bound, free);
8923            }
8924            AstNode::Sort { input, terms } => {
8925                Self::collect_free_vars_walk(input, bound, free);
8926                for (expr, _) in terms {
8927                    Self::collect_free_vars_walk(expr, bound, free);
8928                }
8929            }
8930            AstNode::IndexBind { input, .. } => {
8931                Self::collect_free_vars_walk(input, bound, free);
8932            }
8933            AstNode::Transform {
8934                location,
8935                update,
8936                delete,
8937            } => {
8938                Self::collect_free_vars_walk(location, bound, free);
8939                Self::collect_free_vars_walk(update, bound, free);
8940                if let Some(del) = delete {
8941                    Self::collect_free_vars_walk(del, bound, free);
8942                }
8943            }
8944            // Leaf nodes with no variable references
8945            AstNode::String(_)
8946            | AstNode::Name(_)
8947            | AstNode::Number(_)
8948            | AstNode::Boolean(_)
8949            | AstNode::Null
8950            | AstNode::Undefined
8951            | AstNode::Placeholder
8952            | AstNode::Regex { .. }
8953            | AstNode::Wildcard
8954            | AstNode::Descendant
8955            | AstNode::ParentVariable(_) => {}
8956        }
8957    }
8958
8959    /// Check if a name refers to a built-in function
8960    fn is_builtin_function(&self, name: &str) -> bool {
8961        matches!(
8962            name,
8963            // String functions
8964            "string" | "length" | "substring" | "substringBefore" | "substringAfter" |
8965            "uppercase" | "lowercase" | "trim" | "pad" | "contains" | "split" |
8966            "join" | "match" | "replace" | "eval" | "base64encode" | "base64decode" |
8967            "encodeUrlComponent" | "encodeUrl" | "decodeUrlComponent" | "decodeUrl" |
8968
8969            // Numeric functions
8970            "number" | "abs" | "floor" | "ceil" | "round" | "power" | "sqrt" |
8971            "random" | "formatNumber" | "formatBase" | "formatInteger" | "parseInteger" |
8972
8973            // Aggregation functions
8974            "sum" | "max" | "min" | "average" |
8975
8976            // Boolean/logic functions
8977            "boolean" | "not" | "exists" |
8978
8979            // Array functions
8980            "count" | "append" | "sort" | "reverse" | "shuffle" | "distinct" | "zip" |
8981
8982            // Object functions
8983            "keys" | "lookup" | "spread" | "merge" | "sift" | "each" | "error" | "assert" | "type" |
8984
8985            // Higher-order functions
8986            "map" | "filter" | "reduce" | "singletonArray" |
8987
8988            // Date/time functions
8989            "now" | "millis" | "fromMillis" | "toMillis"
8990        )
8991    }
8992
8993    /// Call a built-in function directly with pre-evaluated Values
8994    /// This is used when passing built-in functions to higher-order functions like $map
8995    fn call_builtin_with_values(
8996        &mut self,
8997        name: &str,
8998        values: &[JValue],
8999    ) -> Result<JValue, EvaluatorError> {
9000        use crate::functions;
9001
9002        if values.is_empty() {
9003            return Err(EvaluatorError::EvaluationError(format!(
9004                "{}() requires at least 1 argument",
9005                name
9006            )));
9007        }
9008
9009        let arg = &values[0];
9010
9011        match name {
9012            "string" => Ok(functions::string::string(arg, None)?),
9013            "number" => Ok(functions::numeric::number(arg)?),
9014            "boolean" => Ok(functions::boolean::boolean(arg)?),
9015            "not" => {
9016                let b = functions::boolean::boolean(arg)?;
9017                match b {
9018                    JValue::Bool(val) => Ok(JValue::Bool(!val)),
9019                    _ => Err(EvaluatorError::TypeError(
9020                        "not() requires a boolean".to_string(),
9021                    )),
9022                }
9023            }
9024            "exists" => Ok(JValue::Bool(!arg.is_null())),
9025            "abs" => match arg {
9026                JValue::Number(n) => Ok(functions::numeric::abs(*n)?),
9027                _ => Err(EvaluatorError::TypeError(
9028                    "abs() requires a number argument".to_string(),
9029                )),
9030            },
9031            "floor" => match arg {
9032                JValue::Number(n) => Ok(functions::numeric::floor(*n)?),
9033                _ => Err(EvaluatorError::TypeError(
9034                    "floor() requires a number argument".to_string(),
9035                )),
9036            },
9037            "ceil" => match arg {
9038                JValue::Number(n) => Ok(functions::numeric::ceil(*n)?),
9039                _ => Err(EvaluatorError::TypeError(
9040                    "ceil() requires a number argument".to_string(),
9041                )),
9042            },
9043            "round" => match arg {
9044                JValue::Number(n) => Ok(functions::numeric::round(*n, None)?),
9045                _ => Err(EvaluatorError::TypeError(
9046                    "round() requires a number argument".to_string(),
9047                )),
9048            },
9049            "sqrt" => match arg {
9050                JValue::Number(n) => Ok(functions::numeric::sqrt(*n)?),
9051                _ => Err(EvaluatorError::TypeError(
9052                    "sqrt() requires a number argument".to_string(),
9053                )),
9054            },
9055            "uppercase" => match arg {
9056                JValue::String(s) => Ok(JValue::string(s.to_uppercase())),
9057                JValue::Null => Ok(JValue::Null),
9058                _ => Err(EvaluatorError::TypeError(
9059                    "uppercase() requires a string argument".to_string(),
9060                )),
9061            },
9062            "lowercase" => match arg {
9063                JValue::String(s) => Ok(JValue::string(s.to_lowercase())),
9064                JValue::Null => Ok(JValue::Null),
9065                _ => Err(EvaluatorError::TypeError(
9066                    "lowercase() requires a string argument".to_string(),
9067                )),
9068            },
9069            "trim" => match arg {
9070                JValue::String(s) => Ok(JValue::string(s.trim().to_string())),
9071                JValue::Null => Ok(JValue::Null),
9072                _ => Err(EvaluatorError::TypeError(
9073                    "trim() requires a string argument".to_string(),
9074                )),
9075            },
9076            "length" => match arg {
9077                JValue::String(s) => Ok(JValue::Number(s.chars().count() as f64)),
9078                JValue::Array(arr) => Ok(JValue::Number(arr.len() as f64)),
9079                JValue::Null => Ok(JValue::Null),
9080                _ => Err(EvaluatorError::TypeError(
9081                    "length() requires a string or array argument".to_string(),
9082                )),
9083            },
9084            "sum" => match arg {
9085                JValue::Array(arr) => {
9086                    let mut total = 0.0;
9087                    for item in arr.iter() {
9088                        match item {
9089                            JValue::Number(n) => {
9090                                total += *n;
9091                            }
9092                            _ => {
9093                                return Err(EvaluatorError::TypeError(
9094                                    "sum() requires all array elements to be numbers".to_string(),
9095                                ));
9096                            }
9097                        }
9098                    }
9099                    Ok(JValue::Number(total))
9100                }
9101                JValue::Number(n) => Ok(JValue::Number(*n)),
9102                JValue::Null => Ok(JValue::Null),
9103                _ => Err(EvaluatorError::TypeError(
9104                    "sum() requires an array of numbers".to_string(),
9105                )),
9106            },
9107            "count" => {
9108                match arg {
9109                    JValue::Array(arr) => Ok(JValue::Number(arr.len() as f64)),
9110                    JValue::Null => Ok(JValue::Number(0.0)),
9111                    _ => Ok(JValue::Number(1.0)), // Single value counts as 1
9112                }
9113            }
9114            "max" => match arg {
9115                JValue::Array(arr) => {
9116                    let mut max_val: Option<f64> = None;
9117                    for item in arr.iter() {
9118                        if let JValue::Number(n) = item {
9119                            let f = *n;
9120                            max_val = Some(max_val.map_or(f, |m| m.max(f)));
9121                        }
9122                    }
9123                    max_val.map_or(Ok(JValue::Null), |m| Ok(JValue::Number(m)))
9124                }
9125                JValue::Number(n) => Ok(JValue::Number(*n)),
9126                JValue::Null => Ok(JValue::Null),
9127                _ => Err(EvaluatorError::TypeError(
9128                    "max() requires an array of numbers".to_string(),
9129                )),
9130            },
9131            "min" => match arg {
9132                JValue::Array(arr) => {
9133                    let mut min_val: Option<f64> = None;
9134                    for item in arr.iter() {
9135                        if let JValue::Number(n) = item {
9136                            let f = *n;
9137                            min_val = Some(min_val.map_or(f, |m| m.min(f)));
9138                        }
9139                    }
9140                    min_val.map_or(Ok(JValue::Null), |m| Ok(JValue::Number(m)))
9141                }
9142                JValue::Number(n) => Ok(JValue::Number(*n)),
9143                JValue::Null => Ok(JValue::Null),
9144                _ => Err(EvaluatorError::TypeError(
9145                    "min() requires an array of numbers".to_string(),
9146                )),
9147            },
9148            "average" => match arg {
9149                JValue::Array(arr) => {
9150                    let nums: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
9151                    if nums.is_empty() {
9152                        Ok(JValue::Null)
9153                    } else {
9154                        let avg = nums.iter().sum::<f64>() / nums.len() as f64;
9155                        Ok(JValue::Number(avg))
9156                    }
9157                }
9158                JValue::Number(n) => Ok(JValue::Number(*n)),
9159                JValue::Null => Ok(JValue::Null),
9160                _ => Err(EvaluatorError::TypeError(
9161                    "average() requires an array of numbers".to_string(),
9162                )),
9163            },
9164            "append" => {
9165                // append(array1, array2) - append second array to first
9166                if values.len() < 2 {
9167                    return Err(EvaluatorError::EvaluationError(
9168                        "append() requires 2 arguments".to_string(),
9169                    ));
9170                }
9171                let first = &values[0];
9172                let second = &values[1];
9173
9174                // Convert first to array if needed
9175                let mut result = match first {
9176                    JValue::Array(arr) => arr.to_vec(),
9177                    JValue::Null => vec![],
9178                    other => vec![other.clone()],
9179                };
9180
9181                // Append second (flatten if array)
9182                match second {
9183                    JValue::Array(arr) => result.extend(arr.iter().cloned()),
9184                    JValue::Null => {}
9185                    other => result.push(other.clone()),
9186                }
9187
9188                Ok(JValue::array(result))
9189            }
9190            "reverse" => match arg {
9191                JValue::Array(arr) => {
9192                    let mut reversed = arr.to_vec();
9193                    reversed.reverse();
9194                    Ok(JValue::array(reversed))
9195                }
9196                JValue::Null => Ok(JValue::Null),
9197                _ => Err(EvaluatorError::TypeError(
9198                    "reverse() requires an array".to_string(),
9199                )),
9200            },
9201            "keys" => match arg {
9202                JValue::Object(obj) => {
9203                    let keys: Vec<JValue> = obj.keys().map(|k| JValue::string(k.clone())).collect();
9204                    Ok(JValue::array(keys))
9205                }
9206                JValue::Null => Ok(JValue::Null),
9207                _ => Err(EvaluatorError::TypeError(
9208                    "keys() requires an object".to_string(),
9209                )),
9210            },
9211
9212            // Add more functions as needed
9213            _ => Err(EvaluatorError::ReferenceError(format!(
9214                "Built-in function {} cannot be called with values directly",
9215                name
9216            ))),
9217        }
9218    }
9219
9220    /// Collect all descendant values recursively
9221    fn collect_descendants(&self, value: &JValue) -> Vec<JValue> {
9222        let mut descendants = Vec::new();
9223
9224        match value {
9225            JValue::Null => {
9226                // Null has no descendants, return empty
9227                return descendants;
9228            }
9229            JValue::Object(obj) => {
9230                // Include the current object
9231                descendants.push(value.clone());
9232
9233                for val in obj.values() {
9234                    // Recursively collect descendants
9235                    descendants.extend(self.collect_descendants(val));
9236                }
9237            }
9238            JValue::Array(arr) => {
9239                // DO NOT include the array itself - only recurse into elements
9240                // This matches JavaScript behavior: arrays are traversed but not collected
9241                for val in arr.iter() {
9242                    // Recursively collect descendants
9243                    descendants.extend(self.collect_descendants(val));
9244                }
9245            }
9246            _ => {
9247                // For primitives (string, number, boolean), just include the value itself
9248                descendants.push(value.clone());
9249            }
9250        }
9251
9252        descendants
9253    }
9254
9255    /// Evaluate a predicate (array filter or index)
9256    fn evaluate_predicate(
9257        &mut self,
9258        current: &JValue,
9259        predicate: &AstNode,
9260    ) -> Result<JValue, EvaluatorError> {
9261        // Special case: empty brackets [] (represented as Boolean(true))
9262        // This forces the value to be wrapped in an array
9263        if matches!(predicate, AstNode::Boolean(true)) {
9264            return match current {
9265                JValue::Array(arr) => Ok(JValue::Array(arr.clone())),
9266                JValue::Null => Ok(JValue::Null),
9267                other => Ok(JValue::array(vec![other.clone()])),
9268            };
9269        }
9270
9271        match current {
9272            JValue::Array(_arr) => {
9273                // Standalone predicates do simple array operations (no mapping over sub-arrays)
9274
9275                // First, try to evaluate predicate as a simple number (array index)
9276                if let AstNode::Number(n) = predicate {
9277                    // Direct array indexing
9278                    return self.array_index(current, &JValue::Number(*n));
9279                }
9280
9281                // Fast path: if predicate is definitely a filter expression (comparison/logical),
9282                // skip speculative numeric evaluation and go directly to filter logic
9283                if Self::is_filter_predicate(predicate) {
9284                    // Try CompiledExpr fast path
9285                    if let Some(compiled) = try_compile_expr(predicate) {
9286                        let shape = _arr.first().and_then(build_shape_cache);
9287                        let mut filtered = Vec::with_capacity(_arr.len());
9288                        for item in _arr.iter() {
9289                            let result = if let Some(ref s) = shape {
9290                                eval_compiled_shaped(&compiled, item, None, s)?
9291                            } else {
9292                                eval_compiled(&compiled, item, None)?
9293                            };
9294                            if compiled_is_truthy(&result) {
9295                                filtered.push(item.clone());
9296                            }
9297                        }
9298                        return Ok(JValue::array(filtered));
9299                    }
9300                    // Fallback: full AST evaluation per element
9301                    let mut filtered = Vec::new();
9302                    for item in _arr.iter() {
9303                        let item_result = self.evaluate_internal(predicate, item)?;
9304                        if self.is_truthy(&item_result) {
9305                            filtered.push(item.clone());
9306                        }
9307                    }
9308                    return Ok(JValue::array(filtered));
9309                }
9310
9311                // Try to evaluate the predicate to see if it's a numeric index
9312                // If evaluation succeeds and yields a number, use it as an index
9313                // If evaluation fails (e.g., comparison error), treat as filter
9314                match self.evaluate_internal(predicate, current) {
9315                    Ok(JValue::Number(_)) => {
9316                        // It's a numeric index
9317                        let pred_result = self.evaluate_internal(predicate, current)?;
9318                        return self.array_index(current, &pred_result);
9319                    }
9320                    Ok(JValue::Array(indices)) => {
9321                        // Multiple array selectors [[indices]]
9322                        // Check if array contains any non-numeric values
9323                        let has_non_numeric =
9324                            indices.iter().any(|v| !matches!(v, JValue::Number(_)));
9325
9326                        if has_non_numeric {
9327                            // If array contains non-numeric values, return entire array
9328                            return Ok(current.clone());
9329                        }
9330
9331                        // Collect numeric indices, handling negative indices
9332                        let arr_len = _arr.len() as i64;
9333                        let mut resolved_indices: Vec<i64> = indices
9334                            .iter()
9335                            .filter_map(|v| {
9336                                if let JValue::Number(n) = v {
9337                                    let idx = *n as i64;
9338                                    // Resolve negative indices
9339                                    let actual_idx = if idx < 0 { arr_len + idx } else { idx };
9340                                    // Only include valid indices
9341                                    if actual_idx >= 0 && actual_idx < arr_len {
9342                                        Some(actual_idx)
9343                                    } else {
9344                                        None
9345                                    }
9346                                } else {
9347                                    None
9348                                }
9349                            })
9350                            .collect();
9351
9352                        // Sort and deduplicate indices
9353                        resolved_indices.sort();
9354                        resolved_indices.dedup();
9355
9356                        // Select elements at each sorted index
9357                        let result: Vec<JValue> = resolved_indices
9358                            .iter()
9359                            .map(|&idx| _arr[idx as usize].clone())
9360                            .collect();
9361
9362                        return Ok(JValue::array(result));
9363                    }
9364                    Ok(_) => {
9365                        // Evaluated successfully but not a number - might be a filter
9366                        // Fall through to filter logic
9367                    }
9368                    Err(_) => {
9369                        // Evaluation failed - it's likely a filter expression
9370                        // Fall through to filter logic
9371                    }
9372                }
9373
9374                // Try CompiledExpr fast path for filter expressions
9375                if let Some(compiled) = try_compile_expr(predicate) {
9376                    let shape = _arr.first().and_then(build_shape_cache);
9377                    let mut filtered = Vec::with_capacity(_arr.len());
9378                    for item in _arr.iter() {
9379                        let result = if let Some(ref s) = shape {
9380                            eval_compiled_shaped(&compiled, item, None, s)?
9381                        } else {
9382                            eval_compiled(&compiled, item, None)?
9383                        };
9384                        if compiled_is_truthy(&result) {
9385                            filtered.push(item.clone());
9386                        }
9387                    }
9388                    return Ok(JValue::array(filtered));
9389                }
9390
9391                // It's a filter expression - evaluate the predicate for each array element
9392                let mut filtered = Vec::new();
9393                for item in _arr.iter() {
9394                    let item_result = self.evaluate_internal(predicate, item)?;
9395
9396                    // If result is truthy, include this item
9397                    if self.is_truthy(&item_result) {
9398                        filtered.push(item.clone());
9399                    }
9400                }
9401
9402                Ok(JValue::array(filtered))
9403            }
9404            JValue::Object(obj) => {
9405                // For objects, predicate can be either:
9406                // 1. A string - property access (computed property name)
9407                // 2. A boolean expression - filter (return object if truthy)
9408                let pred_result = self.evaluate_internal(predicate, current)?;
9409
9410                // If it's a string, use it as a key for property access
9411                if let JValue::String(key) = &pred_result {
9412                    return Ok(obj.get(&**key).cloned().unwrap_or(JValue::Null));
9413                }
9414
9415                // Otherwise, treat as a filter expression
9416                // If the predicate is truthy, return the object; otherwise return undefined
9417                if self.is_truthy(&pred_result) {
9418                    Ok(current.clone())
9419                } else {
9420                    Ok(JValue::Undefined)
9421                }
9422            }
9423            _ => {
9424                // For primitive values (string, number, boolean):
9425                // In JSONata, scalars are treated as single-element arrays when indexed.
9426                // So value[0] returns value, value[1] returns undefined.
9427
9428                // First check if predicate is a numeric literal
9429                if let AstNode::Number(n) = predicate {
9430                    // For scalars, index 0 or -1 returns the value, others return undefined
9431                    let idx = n.floor() as i64;
9432                    if idx == 0 || idx == -1 {
9433                        return Ok(current.clone());
9434                    } else {
9435                        return Ok(JValue::Undefined);
9436                    }
9437                }
9438
9439                // Try to evaluate the predicate to see if it's a numeric index
9440                let pred_result = self.evaluate_internal(predicate, current)?;
9441
9442                if let JValue::Number(n) = &pred_result {
9443                    // It's a numeric index - treat scalar as single-element array
9444                    let idx = n.floor() as i64;
9445                    if idx == 0 || idx == -1 {
9446                        return Ok(current.clone());
9447                    } else {
9448                        return Ok(JValue::Undefined);
9449                    }
9450                }
9451
9452                // For non-numeric predicates, treat as a filter:
9453                // value[true] returns value, value[false] returns undefined
9454                // This enables patterns like: $k[$v>2] which returns $k if $v>2, otherwise undefined
9455                if self.is_truthy(&pred_result) {
9456                    Ok(current.clone())
9457                } else {
9458                    // Return undefined (not null) so $map can filter it out
9459                    Ok(JValue::Undefined)
9460                }
9461            }
9462        }
9463    }
9464
9465    /// Evaluate a sort term expression, distinguishing missing fields from explicit null
9466    /// Returns JValue::Undefined for missing fields, JValue::Null for explicit null
9467    fn evaluate_sort_term(
9468        &mut self,
9469        term_expr: &AstNode,
9470        element: &JValue,
9471    ) -> Result<JValue, EvaluatorError> {
9472        // For tuples (from index binding), extract the actual value from @ field
9473        let actual_element = if let JValue::Object(obj) = element {
9474            if obj.get("__tuple__") == Some(&JValue::Bool(true)) {
9475                obj.get("@").cloned().unwrap_or(JValue::Null)
9476            } else {
9477                element.clone()
9478            }
9479        } else {
9480            element.clone()
9481        };
9482
9483        // For simple field access (Path with single Name step), check if field exists
9484        if let AstNode::Path { steps } = term_expr {
9485            if steps.len() == 1 && steps[0].stages.is_empty() {
9486                if let AstNode::Name(field_name) = &steps[0].node {
9487                    // Check if the field exists in the element
9488                    if let JValue::Object(obj) = &actual_element {
9489                        return match obj.get(field_name) {
9490                            Some(val) => Ok(val.clone()),  // Field exists (may be null)
9491                            None => Ok(JValue::Undefined), // Field is missing
9492                        };
9493                    } else {
9494                        // Not an object - return undefined
9495                        return Ok(JValue::Undefined);
9496                    }
9497                }
9498            }
9499        }
9500
9501        // For complex expressions, evaluate normally against the actual element
9502        // but with the full tuple as the data context (so index bindings are accessible)
9503        let result = self.evaluate_internal(term_expr, element)?;
9504
9505        // If the result is null from a complex expression, we can't easily tell if it's
9506        // "missing field" or "explicit null". For now, treat null results as undefined
9507        // to maintain compatibility with existing tests.
9508        // TODO: For full JS compatibility, would need deeper analysis of the expression
9509        if result.is_null() {
9510            return Ok(JValue::Undefined);
9511        }
9512
9513        Ok(result)
9514    }
9515
9516    /// Evaluate sort operator
9517    fn evaluate_sort(
9518        &mut self,
9519        data: &JValue,
9520        terms: &[(AstNode, bool)],
9521    ) -> Result<JValue, EvaluatorError> {
9522        // If data is null, return null
9523        if data.is_null() {
9524            return Ok(JValue::Null);
9525        }
9526
9527        // If data is not an array, return it as-is (can't sort a single value)
9528        let array = match data {
9529            JValue::Array(arr) => arr.clone(),
9530            other => return Ok(other.clone()),
9531        };
9532
9533        // If empty array, return as-is
9534        if array.is_empty() {
9535            return Ok(JValue::Array(array));
9536        }
9537
9538        // Evaluate sort keys for each element
9539        let mut indexed_array: Vec<(usize, Vec<JValue>)> = Vec::new();
9540
9541        for (idx, element) in array.iter().enumerate() {
9542            let mut sort_keys = Vec::new();
9543
9544            // Evaluate each sort term with $ bound to the element
9545            for (term_expr, _ascending) in terms {
9546                // Save current $ binding
9547                let saved_dollar = self.context.lookup("$").cloned();
9548
9549                // Bind $ to current element
9550                self.context.bind("$".to_string(), element.clone());
9551
9552                // Evaluate the sort expression, distinguishing missing fields from explicit null
9553                let sort_value = self.evaluate_sort_term(term_expr, element)?;
9554
9555                // Restore $ binding
9556                if let Some(val) = saved_dollar {
9557                    self.context.bind("$".to_string(), val);
9558                } else {
9559                    self.context.unbind("$");
9560                }
9561
9562                sort_keys.push(sort_value);
9563            }
9564
9565            indexed_array.push((idx, sort_keys));
9566        }
9567
9568        // Validate that all sort keys are comparable (same type, or undefined)
9569        // Undefined values (missing fields) are allowed and sort to the end
9570        // Null values (explicit null in data) are NOT allowed (typeof null === 'object' in JS, triggers T2008)
9571        for term_idx in 0..terms.len() {
9572            let mut first_valid_type: Option<&str> = None;
9573
9574            for (_idx, sort_keys) in &indexed_array {
9575                let sort_value = &sort_keys[term_idx];
9576
9577                // Skip undefined markers (missing fields) - these are allowed and sort to end
9578                if sort_value.is_undefined() {
9579                    continue;
9580                }
9581
9582                // Get the type name for this value
9583                // Note: explicit null is NOT allowed - typeof null === 'object' in JS
9584                let value_type = match sort_value {
9585                    JValue::Number(_) => "number",
9586                    JValue::String(_) => "string",
9587                    JValue::Bool(_) => "boolean",
9588                    JValue::Array(_) => "array",
9589                    JValue::Object(_) => "object", // This catches non-undefined objects
9590                    JValue::Null => "null",        // Explicit null from data
9591                    _ => "unknown",
9592                };
9593
9594                // Check that sort keys are only numbers or strings
9595                // Null, boolean, array, and object types are not valid for sorting
9596                if value_type != "number" && value_type != "string" {
9597                    return Err(EvaluatorError::TypeError("T2008: The expressions within an order-by clause must evaluate to numeric or string values".to_string()));
9598                }
9599
9600                // Check if this matches the first valid type we saw
9601                if let Some(first_type) = first_valid_type {
9602                    if first_type != value_type {
9603                        return Err(EvaluatorError::TypeError(format!(
9604                            "T2007: Type mismatch when comparing values in order-by clause: {} and {}",
9605                            first_type, value_type
9606                        )));
9607                    }
9608                } else {
9609                    first_valid_type = Some(value_type);
9610                }
9611            }
9612        }
9613
9614        // Sort the indexed array
9615        indexed_array.sort_by(|a, b| {
9616            // Compare sort keys in order
9617            for (i, (_term_expr, ascending)) in terms.iter().enumerate() {
9618                let left = &a.1[i];
9619                let right = &b.1[i];
9620
9621                let cmp = self.compare_values(left, right);
9622
9623                if cmp != std::cmp::Ordering::Equal {
9624                    return if *ascending { cmp } else { cmp.reverse() };
9625                }
9626            }
9627
9628            // If all keys are equal, maintain original order (stable sort)
9629            a.0.cmp(&b.0)
9630        });
9631
9632        // Extract sorted elements
9633        let sorted: Vec<JValue> = indexed_array
9634            .iter()
9635            .map(|(idx, _)| array[*idx].clone())
9636            .collect();
9637
9638        Ok(JValue::array(sorted))
9639    }
9640
9641    /// Compare two values for sorting (JSONata semantics)
9642    fn compare_values(&self, left: &JValue, right: &JValue) -> Ordering {
9643        // Handle undefined markers first - they sort to the end
9644        let left_undef = left.is_undefined();
9645        let right_undef = right.is_undefined();
9646
9647        if left_undef && right_undef {
9648            return Ordering::Equal;
9649        }
9650        if left_undef {
9651            return Ordering::Greater; // Undefined sorts last
9652        }
9653        if right_undef {
9654            return Ordering::Less;
9655        }
9656
9657        match (left, right) {
9658            // Nulls also sort last (explicit null in data)
9659            (JValue::Null, JValue::Null) => Ordering::Equal,
9660            (JValue::Null, _) => Ordering::Greater,
9661            (_, JValue::Null) => Ordering::Less,
9662
9663            // Numbers
9664            (JValue::Number(a), JValue::Number(b)) => {
9665                let a_f64 = *a;
9666                let b_f64 = *b;
9667                a_f64.partial_cmp(&b_f64).unwrap_or(Ordering::Equal)
9668            }
9669
9670            // Strings
9671            (JValue::String(a), JValue::String(b)) => a.cmp(b),
9672
9673            // Booleans
9674            (JValue::Bool(a), JValue::Bool(b)) => a.cmp(b),
9675
9676            // Arrays (lexicographic comparison)
9677            (JValue::Array(a), JValue::Array(b)) => {
9678                for (a_elem, b_elem) in a.iter().zip(b.iter()) {
9679                    let cmp = self.compare_values(a_elem, b_elem);
9680                    if cmp != Ordering::Equal {
9681                        return cmp;
9682                    }
9683                }
9684                a.len().cmp(&b.len())
9685            }
9686
9687            // Different types: use type ordering
9688            // null < bool < number < string < array < object
9689            (JValue::Bool(_), JValue::Number(_)) => Ordering::Less,
9690            (JValue::Bool(_), JValue::String(_)) => Ordering::Less,
9691            (JValue::Bool(_), JValue::Array(_)) => Ordering::Less,
9692            (JValue::Bool(_), JValue::Object(_)) => Ordering::Less,
9693
9694            (JValue::Number(_), JValue::Bool(_)) => Ordering::Greater,
9695            (JValue::Number(_), JValue::String(_)) => Ordering::Less,
9696            (JValue::Number(_), JValue::Array(_)) => Ordering::Less,
9697            (JValue::Number(_), JValue::Object(_)) => Ordering::Less,
9698
9699            (JValue::String(_), JValue::Bool(_)) => Ordering::Greater,
9700            (JValue::String(_), JValue::Number(_)) => Ordering::Greater,
9701            (JValue::String(_), JValue::Array(_)) => Ordering::Less,
9702            (JValue::String(_), JValue::Object(_)) => Ordering::Less,
9703
9704            (JValue::Array(_), JValue::Bool(_)) => Ordering::Greater,
9705            (JValue::Array(_), JValue::Number(_)) => Ordering::Greater,
9706            (JValue::Array(_), JValue::String(_)) => Ordering::Greater,
9707            (JValue::Array(_), JValue::Object(_)) => Ordering::Less,
9708
9709            (JValue::Object(_), _) => Ordering::Greater,
9710            _ => Ordering::Equal,
9711        }
9712    }
9713
9714    /// Check if a value is truthy (JSONata semantics).
9715    fn is_truthy(&self, value: &JValue) -> bool {
9716        match value {
9717            JValue::Null | JValue::Undefined => false,
9718            JValue::Bool(b) => *b,
9719            JValue::Number(n) => *n != 0.0,
9720            JValue::String(s) => !s.is_empty(),
9721            JValue::Array(arr) => !arr.is_empty(),
9722            JValue::Object(obj) => !obj.is_empty(),
9723            _ => false,
9724        }
9725    }
9726
9727    /// Check if a value is truthy for the default operator (?:)
9728    /// This has special semantics:
9729    /// - Lambda/function objects are not values, so they're falsy
9730    /// - Arrays containing only falsy elements are falsy
9731    /// - Otherwise, use standard truthiness
9732    fn is_truthy_for_default(&self, value: &JValue) -> bool {
9733        match value {
9734            // Lambda/function values are not data values, so they're falsy
9735            JValue::Lambda { .. } | JValue::Builtin { .. } => false,
9736            // Arrays need special handling - check if all elements are falsy
9737            JValue::Array(arr) => {
9738                if arr.is_empty() {
9739                    return false;
9740                }
9741                // Array is truthy only if it contains at least one truthy element
9742                arr.iter().any(|elem| self.is_truthy(elem))
9743            }
9744            // For all other types, use standard truthiness
9745            _ => self.is_truthy(value),
9746        }
9747    }
9748
9749    /// Unwrap singleton arrays to scalar values
9750    /// This is used when no explicit array-keeping operation (like []) was used
9751    fn unwrap_singleton(&self, value: JValue) -> JValue {
9752        match value {
9753            JValue::Array(ref arr) if arr.len() == 1 => arr[0].clone(),
9754            _ => value,
9755        }
9756    }
9757
9758    /// Extract lambda IDs from a value (used for closure preservation)
9759    /// Finds any lambda_id references in the value so they can be preserved
9760    /// when exiting a block scope
9761    fn extract_lambda_ids(&self, value: &JValue) -> Vec<String> {
9762        // Fast path: scalars can never contain lambda references
9763        match value {
9764            JValue::Number(_)
9765            | JValue::Bool(_)
9766            | JValue::String(_)
9767            | JValue::Null
9768            | JValue::Undefined
9769            | JValue::Regex { .. }
9770            | JValue::Builtin { .. } => return Vec::new(),
9771            _ => {}
9772        }
9773        let mut ids = Vec::new();
9774        self.collect_lambda_ids(value, &mut ids);
9775        ids
9776    }
9777
9778    fn collect_lambda_ids(&self, value: &JValue, ids: &mut Vec<String>) {
9779        match value {
9780            JValue::Lambda { lambda_id, .. } => {
9781                let id_str = lambda_id.to_string();
9782                if !ids.contains(&id_str) {
9783                    ids.push(id_str);
9784                    // Transitively follow the stored lambda's captured_env
9785                    // to find all referenced lambdas. This is critical for
9786                    // closures like the Y-combinator where returned lambdas
9787                    // capture other lambdas in their environment.
9788                    if let Some(stored) = self.context.lookup_lambda(lambda_id) {
9789                        let env_values: Vec<JValue> =
9790                            stored.captured_env.values().cloned().collect();
9791                        for env_value in &env_values {
9792                            self.collect_lambda_ids(env_value, ids);
9793                        }
9794                    }
9795                }
9796            }
9797            JValue::Object(map) => {
9798                // Recurse into object values
9799                for v in map.values() {
9800                    self.collect_lambda_ids(v, ids);
9801                }
9802            }
9803            JValue::Array(arr) => {
9804                // Recurse into array elements
9805                for v in arr.iter() {
9806                    self.collect_lambda_ids(v, ids);
9807                }
9808            }
9809            _ => {}
9810        }
9811    }
9812
9813    /// Equality comparison (JSONata semantics)
9814    fn equals(&self, left: &JValue, right: &JValue) -> bool {
9815        crate::functions::array::values_equal(left, right)
9816    }
9817
9818    /// Addition
9819    fn add(
9820        &self,
9821        left: &JValue,
9822        right: &JValue,
9823        left_is_explicit_null: bool,
9824        right_is_explicit_null: bool,
9825    ) -> Result<JValue, EvaluatorError> {
9826        match (left, right) {
9827            (JValue::Number(a), JValue::Number(b)) => Ok(JValue::Number(*a + *b)),
9828            // Explicit null literal with number -> T2002 error
9829            (JValue::Null, JValue::Number(_)) if left_is_explicit_null => {
9830                Err(EvaluatorError::TypeError(
9831                    "T2002: The left side of the + operator must evaluate to a number".to_string(),
9832                ))
9833            }
9834            (JValue::Number(_), JValue::Null) if right_is_explicit_null => {
9835                Err(EvaluatorError::TypeError(
9836                    "T2002: The right side of the + operator must evaluate to a number".to_string(),
9837                ))
9838            }
9839            (JValue::Null, JValue::Null) if left_is_explicit_null || right_is_explicit_null => {
9840                Err(EvaluatorError::TypeError(
9841                    "T2002: The left side of the + operator must evaluate to a number".to_string(),
9842                ))
9843            }
9844            // Undefined variable (null/undefined) with number -> undefined result
9845            (JValue::Null | JValue::Undefined, JValue::Number(_))
9846            | (JValue::Number(_), JValue::Null | JValue::Undefined) => Ok(JValue::Null),
9847            // Boolean with anything (including undefined) -> T2001 error
9848            (JValue::Bool(_), _) => Err(EvaluatorError::TypeError(
9849                "T2001: The left side of the '+' operator must evaluate to a number or a string"
9850                    .to_string(),
9851            )),
9852            (_, JValue::Bool(_)) => Err(EvaluatorError::TypeError(
9853                "T2001: The right side of the '+' operator must evaluate to a number or a string"
9854                    .to_string(),
9855            )),
9856            // Undefined with undefined -> undefined
9857            (JValue::Null | JValue::Undefined, JValue::Null | JValue::Undefined) => {
9858                Ok(JValue::Null)
9859            }
9860            _ => Err(EvaluatorError::TypeError(format!(
9861                "Cannot add {:?} and {:?}",
9862                left, right
9863            ))),
9864        }
9865    }
9866
9867    /// Subtraction
9868    fn subtract(
9869        &self,
9870        left: &JValue,
9871        right: &JValue,
9872        left_is_explicit_null: bool,
9873        right_is_explicit_null: bool,
9874    ) -> Result<JValue, EvaluatorError> {
9875        match (left, right) {
9876            (JValue::Number(a), JValue::Number(b)) => Ok(JValue::Number(*a - *b)),
9877            // Explicit null literal -> error
9878            (JValue::Null, _) if left_is_explicit_null => Err(EvaluatorError::TypeError(
9879                "T2002: The left side of the - operator must evaluate to a number".to_string(),
9880            )),
9881            (_, JValue::Null) if right_is_explicit_null => Err(EvaluatorError::TypeError(
9882                "T2002: The right side of the - operator must evaluate to a number".to_string(),
9883            )),
9884            // Undefined variables -> undefined result
9885            (JValue::Null | JValue::Undefined, _) | (_, JValue::Null | JValue::Undefined) => {
9886                Ok(JValue::Null)
9887            }
9888            _ => Err(EvaluatorError::TypeError(format!(
9889                "Cannot subtract {:?} and {:?}",
9890                left, right
9891            ))),
9892        }
9893    }
9894
9895    /// Multiplication
9896    fn multiply(
9897        &self,
9898        left: &JValue,
9899        right: &JValue,
9900        left_is_explicit_null: bool,
9901        right_is_explicit_null: bool,
9902    ) -> Result<JValue, EvaluatorError> {
9903        match (left, right) {
9904            (JValue::Number(a), JValue::Number(b)) => {
9905                let result = *a * *b;
9906                // Check for overflow to Infinity
9907                if result.is_infinite() {
9908                    return Err(EvaluatorError::EvaluationError(
9909                        "D1001: Number out of range".to_string(),
9910                    ));
9911                }
9912                Ok(JValue::Number(result))
9913            }
9914            // Explicit null literal -> error
9915            (JValue::Null, _) if left_is_explicit_null => Err(EvaluatorError::TypeError(
9916                "T2002: The left side of the * operator must evaluate to a number".to_string(),
9917            )),
9918            (_, JValue::Null) if right_is_explicit_null => Err(EvaluatorError::TypeError(
9919                "T2002: The right side of the * operator must evaluate to a number".to_string(),
9920            )),
9921            // Undefined variables -> undefined result
9922            (JValue::Null | JValue::Undefined, _) | (_, JValue::Null | JValue::Undefined) => {
9923                Ok(JValue::Null)
9924            }
9925            _ => Err(EvaluatorError::TypeError(format!(
9926                "Cannot multiply {:?} and {:?}",
9927                left, right
9928            ))),
9929        }
9930    }
9931
9932    /// Division
9933    fn divide(
9934        &self,
9935        left: &JValue,
9936        right: &JValue,
9937        left_is_explicit_null: bool,
9938        right_is_explicit_null: bool,
9939    ) -> Result<JValue, EvaluatorError> {
9940        match (left, right) {
9941            (JValue::Number(a), JValue::Number(b)) => {
9942                let denominator = *b;
9943                if denominator == 0.0 {
9944                    return Err(EvaluatorError::EvaluationError(
9945                        "Division by zero".to_string(),
9946                    ));
9947                }
9948                Ok(JValue::Number(*a / denominator))
9949            }
9950            // Explicit null literal -> error
9951            (JValue::Null, _) if left_is_explicit_null => Err(EvaluatorError::TypeError(
9952                "T2002: The left side of the / operator must evaluate to a number".to_string(),
9953            )),
9954            (_, JValue::Null) if right_is_explicit_null => Err(EvaluatorError::TypeError(
9955                "T2002: The right side of the / operator must evaluate to a number".to_string(),
9956            )),
9957            // Undefined variables -> undefined result
9958            (JValue::Null | JValue::Undefined, _) | (_, JValue::Null | JValue::Undefined) => {
9959                Ok(JValue::Null)
9960            }
9961            _ => Err(EvaluatorError::TypeError(format!(
9962                "Cannot divide {:?} and {:?}",
9963                left, right
9964            ))),
9965        }
9966    }
9967
9968    /// Modulo
9969    fn modulo(
9970        &self,
9971        left: &JValue,
9972        right: &JValue,
9973        left_is_explicit_null: bool,
9974        right_is_explicit_null: bool,
9975    ) -> Result<JValue, EvaluatorError> {
9976        match (left, right) {
9977            (JValue::Number(a), JValue::Number(b)) => {
9978                let denominator = *b;
9979                if denominator == 0.0 {
9980                    return Err(EvaluatorError::EvaluationError(
9981                        "Division by zero".to_string(),
9982                    ));
9983                }
9984                Ok(JValue::Number(*a % denominator))
9985            }
9986            // Explicit null literal -> error
9987            (JValue::Null, _) if left_is_explicit_null => Err(EvaluatorError::TypeError(
9988                "T2002: The left side of the % operator must evaluate to a number".to_string(),
9989            )),
9990            (_, JValue::Null) if right_is_explicit_null => Err(EvaluatorError::TypeError(
9991                "T2002: The right side of the % operator must evaluate to a number".to_string(),
9992            )),
9993            // Undefined variables -> undefined result
9994            (JValue::Null | JValue::Undefined, _) | (_, JValue::Null | JValue::Undefined) => {
9995                Ok(JValue::Null)
9996            }
9997            _ => Err(EvaluatorError::TypeError(format!(
9998                "Cannot compute modulo of {:?} and {:?}",
9999                left, right
10000            ))),
10001        }
10002    }
10003
10004    /// Get human-readable type name for error messages
10005    fn type_name(value: &JValue) -> &'static str {
10006        match value {
10007            JValue::Null => "null",
10008            JValue::Bool(_) => "boolean",
10009            JValue::Number(_) => "number",
10010            JValue::String(_) => "string",
10011            JValue::Array(_) => "array",
10012            JValue::Object(_) => "object",
10013            _ => "unknown",
10014        }
10015    }
10016
10017    /// Ordered comparison with null/type checking shared across <, <=, >, >=
10018    ///
10019    /// `compare_nums` receives (left_f64, right_f64) for numeric operands.
10020    /// `compare_strs` receives (left_str, right_str) for string operands.
10021    /// `op_symbol` is used in the T2009 error message (e.g. "<", ">=").
10022    fn ordered_compare(
10023        &self,
10024        left: &JValue,
10025        right: &JValue,
10026        left_is_explicit_null: bool,
10027        right_is_explicit_null: bool,
10028        op_symbol: &str,
10029        compare_nums: fn(f64, f64) -> bool,
10030        compare_strs: fn(&str, &str) -> bool,
10031    ) -> Result<JValue, EvaluatorError> {
10032        match (left, right) {
10033            (JValue::Number(a), JValue::Number(b)) => {
10034                Ok(JValue::Bool(compare_nums(*a, *b)))
10035            }
10036            (JValue::String(a), JValue::String(b)) => Ok(JValue::Bool(compare_strs(a, b))),
10037            // Both null/undefined -> return undefined
10038            (JValue::Null, JValue::Null) => Ok(JValue::Null),
10039            // Explicit null literal with any type (except null) -> T2010 error
10040            (JValue::Null, _) if left_is_explicit_null => {
10041                Err(EvaluatorError::EvaluationError("T2010: Type mismatch in comparison".to_string()))
10042            }
10043            (_, JValue::Null) if right_is_explicit_null => {
10044                Err(EvaluatorError::EvaluationError("T2010: Type mismatch in comparison".to_string()))
10045            }
10046            // Boolean with undefined -> T2010 error
10047            (JValue::Bool(_), JValue::Null) | (JValue::Null, JValue::Bool(_)) => {
10048                Err(EvaluatorError::EvaluationError("T2010: Type mismatch in comparison".to_string()))
10049            }
10050            // Number or String with undefined (not explicit null) -> undefined result
10051            (JValue::Number(_), JValue::Null) | (JValue::Null, JValue::Number(_)) |
10052            (JValue::String(_), JValue::Null) | (JValue::Null, JValue::String(_)) => {
10053                Ok(JValue::Null)
10054            }
10055            // String vs Number -> T2009
10056            (JValue::String(_), JValue::Number(_)) | (JValue::Number(_), JValue::String(_)) => {
10057                Err(EvaluatorError::EvaluationError(format!(
10058                    "T2009: The expressions on either side of operator \"{}\" must be of the same data type",
10059                    op_symbol
10060                )))
10061            }
10062            // Boolean comparisons -> T2010
10063            (JValue::Bool(_), _) | (_, JValue::Bool(_)) => {
10064                Err(EvaluatorError::EvaluationError(format!(
10065                    "T2010: Cannot compare {} and {}",
10066                    Self::type_name(left), Self::type_name(right)
10067                )))
10068            }
10069            // Other type mismatches
10070            _ => Err(EvaluatorError::EvaluationError(format!(
10071                "T2010: Cannot compare {} and {}",
10072                Self::type_name(left), Self::type_name(right)
10073            ))),
10074        }
10075    }
10076
10077    /// Less than comparison
10078    fn less_than(
10079        &self,
10080        left: &JValue,
10081        right: &JValue,
10082        left_is_explicit_null: bool,
10083        right_is_explicit_null: bool,
10084    ) -> Result<JValue, EvaluatorError> {
10085        self.ordered_compare(
10086            left,
10087            right,
10088            left_is_explicit_null,
10089            right_is_explicit_null,
10090            "<",
10091            |a, b| a < b,
10092            |a, b| a < b,
10093        )
10094    }
10095
10096    /// Less than or equal comparison
10097    fn less_than_or_equal(
10098        &self,
10099        left: &JValue,
10100        right: &JValue,
10101        left_is_explicit_null: bool,
10102        right_is_explicit_null: bool,
10103    ) -> Result<JValue, EvaluatorError> {
10104        self.ordered_compare(
10105            left,
10106            right,
10107            left_is_explicit_null,
10108            right_is_explicit_null,
10109            "<=",
10110            |a, b| a <= b,
10111            |a, b| a <= b,
10112        )
10113    }
10114
10115    /// Greater than comparison
10116    fn greater_than(
10117        &self,
10118        left: &JValue,
10119        right: &JValue,
10120        left_is_explicit_null: bool,
10121        right_is_explicit_null: bool,
10122    ) -> Result<JValue, EvaluatorError> {
10123        self.ordered_compare(
10124            left,
10125            right,
10126            left_is_explicit_null,
10127            right_is_explicit_null,
10128            ">",
10129            |a, b| a > b,
10130            |a, b| a > b,
10131        )
10132    }
10133
10134    /// Greater than or equal comparison
10135    fn greater_than_or_equal(
10136        &self,
10137        left: &JValue,
10138        right: &JValue,
10139        left_is_explicit_null: bool,
10140        right_is_explicit_null: bool,
10141    ) -> Result<JValue, EvaluatorError> {
10142        self.ordered_compare(
10143            left,
10144            right,
10145            left_is_explicit_null,
10146            right_is_explicit_null,
10147            ">=",
10148            |a, b| a >= b,
10149            |a, b| a >= b,
10150        )
10151    }
10152
10153    /// Convert a value to a string for concatenation
10154    fn value_to_concat_string(value: &JValue) -> Result<String, EvaluatorError> {
10155        match value {
10156            JValue::String(s) => Ok(s.to_string()),
10157            JValue::Null => Ok(String::new()),
10158            JValue::Number(_) | JValue::Bool(_) | JValue::Array(_) | JValue::Object(_) => {
10159                match crate::functions::string::string(value, None) {
10160                    Ok(JValue::String(s)) => Ok(s.to_string()),
10161                    Ok(JValue::Null) => Ok(String::new()),
10162                    _ => Err(EvaluatorError::TypeError(
10163                        "Cannot concatenate complex types".to_string(),
10164                    )),
10165                }
10166            }
10167            _ => Ok(String::new()),
10168        }
10169    }
10170
10171    /// String concatenation
10172    fn concatenate(&self, left: &JValue, right: &JValue) -> Result<JValue, EvaluatorError> {
10173        let left_str = Self::value_to_concat_string(left)?;
10174        let right_str = Self::value_to_concat_string(right)?;
10175        Ok(JValue::string(format!("{}{}", left_str, right_str)))
10176    }
10177
10178    /// Range operator (e.g., 1..5 produces [1,2,3,4,5])
10179    fn range(&self, left: &JValue, right: &JValue) -> Result<JValue, EvaluatorError> {
10180        // Check left operand is a number or null
10181        let start_f64 = match left {
10182            JValue::Number(n) => Some(*n),
10183            JValue::Null => None,
10184            _ => {
10185                return Err(EvaluatorError::EvaluationError(
10186                    "T2003: Left operand of range operator must be a number".to_string(),
10187                ));
10188            }
10189        };
10190
10191        // Check left operand is an integer (if it's a number)
10192        if let Some(val) = start_f64 {
10193            if val.fract() != 0.0 {
10194                return Err(EvaluatorError::EvaluationError(
10195                    "T2003: Left operand of range operator must be an integer".to_string(),
10196                ));
10197            }
10198        }
10199
10200        // Check right operand is a number or null
10201        let end_f64 = match right {
10202            JValue::Number(n) => Some(*n),
10203            JValue::Null => None,
10204            _ => {
10205                return Err(EvaluatorError::EvaluationError(
10206                    "T2004: Right operand of range operator must be a number".to_string(),
10207                ));
10208            }
10209        };
10210
10211        // Check right operand is an integer (if it's a number)
10212        if let Some(val) = end_f64 {
10213            if val.fract() != 0.0 {
10214                return Err(EvaluatorError::EvaluationError(
10215                    "T2004: Right operand of range operator must be an integer".to_string(),
10216                ));
10217            }
10218        }
10219
10220        // If either operand is null, return empty array
10221        if start_f64.is_none() || end_f64.is_none() {
10222            return Ok(JValue::array(vec![]));
10223        }
10224
10225        let start = start_f64.unwrap() as i64;
10226        let end = end_f64.unwrap() as i64;
10227
10228        // Check range size limit (10 million elements max)
10229        let size = if start <= end {
10230            (end - start + 1) as usize
10231        } else {
10232            0
10233        };
10234        if size > 10_000_000 {
10235            return Err(EvaluatorError::EvaluationError(
10236                "D2014: Range operator results in too many elements (> 10,000,000)".to_string(),
10237            ));
10238        }
10239
10240        let mut result = Vec::with_capacity(size);
10241        if start <= end {
10242            for i in start..=end {
10243                result.push(JValue::Number(i as f64));
10244            }
10245        }
10246        // Note: if start > end, return empty array (not reversed)
10247        Ok(JValue::array(result))
10248    }
10249
10250    /// In operator (checks if left is in right array/object)
10251    /// Array indexing: array[index]
10252    fn array_index(&self, array: &JValue, index: &JValue) -> Result<JValue, EvaluatorError> {
10253        match (array, index) {
10254            (JValue::Array(arr), JValue::Number(n)) => {
10255                let idx = *n as i64;
10256                let len = arr.len() as i64;
10257
10258                // Handle negative indexing (offset from end)
10259                let actual_idx = if idx < 0 { len + idx } else { idx };
10260
10261                if actual_idx < 0 || actual_idx >= len {
10262                    Ok(JValue::Undefined)
10263                } else {
10264                    Ok(arr[actual_idx as usize].clone())
10265                }
10266            }
10267            _ => Err(EvaluatorError::TypeError(
10268                "Array indexing requires array and number".to_string(),
10269            )),
10270        }
10271    }
10272
10273    /// Array filtering: array[predicate]
10274    /// Evaluates the predicate for each item in the array and returns items where predicate is true
10275    fn array_filter(
10276        &mut self,
10277        _lhs_node: &AstNode,
10278        rhs_node: &AstNode,
10279        array: &JValue,
10280        _original_data: &JValue,
10281    ) -> Result<JValue, EvaluatorError> {
10282        match array {
10283            JValue::Array(arr) => {
10284                // Pre-allocate with estimated capacity (assume ~50% will match)
10285                let mut filtered = Vec::with_capacity(arr.len() / 2);
10286
10287                for item in arr.iter() {
10288                    // Evaluate the predicate in the context of this array item
10289                    // The item becomes the new "current context" ($)
10290                    let predicate_result = self.evaluate_internal(rhs_node, item)?;
10291
10292                    // Check if the predicate is truthy
10293                    if self.is_truthy(&predicate_result) {
10294                        filtered.push(item.clone());
10295                    }
10296                }
10297
10298                Ok(JValue::array(filtered))
10299            }
10300            _ => Err(EvaluatorError::TypeError(
10301                "Array filtering requires an array".to_string(),
10302            )),
10303        }
10304    }
10305
10306    fn in_operator(&self, left: &JValue, right: &JValue) -> Result<JValue, EvaluatorError> {
10307        // If either side is undefined/null, return false (not an error)
10308        // This matches JavaScript behavior
10309        if left.is_null() || right.is_null() {
10310            return Ok(JValue::Bool(false));
10311        }
10312
10313        match right {
10314            JValue::Array(arr) => Ok(JValue::Bool(arr.iter().any(|v| self.equals(left, v)))),
10315            JValue::Object(obj) => {
10316                if let JValue::String(key) = left {
10317                    Ok(JValue::Bool(obj.contains_key(&**key)))
10318                } else {
10319                    Ok(JValue::Bool(false))
10320                }
10321            }
10322            // If right side is not an array or object (e.g., string, number),
10323            // wrap it in an array for comparison
10324            other => Ok(JValue::Bool(self.equals(left, other))),
10325        }
10326    }
10327
10328    /// Create a partially applied function from a function call with placeholder arguments
10329    /// This evaluates non-placeholder arguments and creates a new lambda that takes
10330    /// the placeholder positions as parameters.
10331    fn create_partial_application(
10332        &mut self,
10333        name: &str,
10334        args: &[AstNode],
10335        is_builtin: bool,
10336        data: &JValue,
10337    ) -> Result<JValue, EvaluatorError> {
10338        // First, look up the function to ensure it exists
10339        let is_lambda = self.context.lookup_lambda(name).is_some()
10340            || (self
10341                .context
10342                .lookup(name)
10343                .map(|v| matches!(v, JValue::Lambda { .. }))
10344                .unwrap_or(false));
10345
10346        // Built-in functions must be called with $ prefix for partial application
10347        // Without $, it's an error (T1007) suggesting the user forgot the $
10348        if !is_lambda && !is_builtin {
10349            // Check if it's a built-in function called without $
10350            if self.is_builtin_function(name) {
10351                return Err(EvaluatorError::EvaluationError(format!(
10352                    "T1007: Attempted to partially apply a non-function. Did you mean ${}?",
10353                    name
10354                )));
10355            }
10356            return Err(EvaluatorError::EvaluationError(
10357                "T1008: Attempted to partially apply a non-function".to_string(),
10358            ));
10359        }
10360
10361        // Evaluate non-placeholder arguments and track placeholder positions
10362        let mut bound_args: Vec<(usize, JValue)> = Vec::new();
10363        let mut placeholder_positions: Vec<usize> = Vec::new();
10364
10365        for (i, arg) in args.iter().enumerate() {
10366            if matches!(arg, AstNode::Placeholder) {
10367                placeholder_positions.push(i);
10368            } else {
10369                let value = self.evaluate_internal(arg, data)?;
10370                bound_args.push((i, value));
10371            }
10372        }
10373
10374        // Generate parameter names for each placeholder
10375        let param_names: Vec<String> = placeholder_positions
10376            .iter()
10377            .enumerate()
10378            .map(|(i, _)| format!("__p{}", i))
10379            .collect();
10380
10381        // Store the partial application info as a special lambda
10382        // When invoked, it will call the original function with bound + placeholder args
10383        let partial_id = format!(
10384            "__partial_{}_{}_{}",
10385            name,
10386            placeholder_positions.len(),
10387            bound_args.len()
10388        );
10389
10390        // Create a stored lambda that represents this partial application
10391        // The body is a marker that we'll interpret specially during invocation
10392        let stored_lambda = StoredLambda {
10393            params: param_names.clone(),
10394            body: AstNode::String(format!(
10395                "__partial_call:{}:{}:{}",
10396                name,
10397                is_builtin,
10398                args.len()
10399            )),
10400            compiled_body: None, // Partial application uses a special body marker
10401            signature: None,
10402            captured_env: {
10403                let mut env = self.capture_current_environment();
10404                // Store the bound arguments in the captured environment
10405                for (pos, value) in &bound_args {
10406                    env.insert(format!("__bound_arg_{}", pos), value.clone());
10407                }
10408                // Store placeholder positions
10409                env.insert(
10410                    "__placeholder_positions".to_string(),
10411                    JValue::array(
10412                        placeholder_positions
10413                            .iter()
10414                            .map(|p| JValue::Number(*p as f64))
10415                            .collect::<Vec<_>>(),
10416                    ),
10417                );
10418                // Store total argument count
10419                env.insert(
10420                    "__total_args".to_string(),
10421                    JValue::Number(args.len() as f64),
10422                );
10423                env
10424            },
10425            captured_data: Some(data.clone()),
10426            thunk: false,
10427        };
10428
10429        self.context.bind_lambda(partial_id.clone(), stored_lambda);
10430
10431        // Return a lambda object that can be invoked
10432        let lambda_obj = JValue::lambda(
10433            partial_id.as_str(),
10434            param_names,
10435            Some(name.to_string()),
10436            None::<String>,
10437        );
10438
10439        Ok(lambda_obj)
10440    }
10441}
10442
10443impl Default for Evaluator {
10444    fn default() -> Self {
10445        Self::new()
10446    }
10447}
10448
10449#[cfg(test)]
10450mod tests {
10451    use super::*;
10452    use crate::ast::{BinaryOp, UnaryOp};
10453
10454    #[test]
10455    fn test_evaluate_literals() {
10456        let mut evaluator = Evaluator::new();
10457        let data = JValue::Null;
10458
10459        // String literal
10460        let result = evaluator
10461            .evaluate(&AstNode::string("hello"), &data)
10462            .unwrap();
10463        assert_eq!(result, JValue::string("hello"));
10464
10465        // Number literal
10466        let result = evaluator.evaluate(&AstNode::number(42.0), &data).unwrap();
10467        assert_eq!(result, JValue::from(42i64));
10468
10469        // Boolean literal
10470        let result = evaluator.evaluate(&AstNode::boolean(true), &data).unwrap();
10471        assert_eq!(result, JValue::Bool(true));
10472
10473        // Null literal
10474        let result = evaluator.evaluate(&AstNode::null(), &data).unwrap();
10475        assert_eq!(result, JValue::Null);
10476    }
10477
10478    #[test]
10479    fn test_evaluate_variables() {
10480        let mut evaluator = Evaluator::new();
10481        let data = JValue::Null;
10482
10483        // Bind a variable
10484        evaluator
10485            .context
10486            .bind("x".to_string(), JValue::from(100i64));
10487
10488        // Look up the variable
10489        let result = evaluator.evaluate(&AstNode::variable("x"), &data).unwrap();
10490        assert_eq!(result, JValue::from(100i64));
10491
10492        // Undefined variable returns null (undefined in JSONata semantics)
10493        let result = evaluator
10494            .evaluate(&AstNode::variable("undefined"), &data)
10495            .unwrap();
10496        assert_eq!(result, JValue::Null);
10497    }
10498
10499    #[test]
10500    fn test_evaluate_path() {
10501        let mut evaluator = Evaluator::new();
10502        let data = JValue::from(serde_json::json!({
10503            "foo": {
10504                "bar": {
10505                    "baz": 42
10506                }
10507            }
10508        }));
10509        // Simple path
10510        let path = AstNode::Path {
10511            steps: vec![PathStep::new(AstNode::Name("foo".to_string()))],
10512        };
10513        let result = evaluator.evaluate(&path, &data).unwrap();
10514        assert_eq!(
10515            result,
10516            JValue::from(serde_json::json!({"bar": {"baz": 42}}))
10517        );
10518
10519        // Nested path
10520        let path = AstNode::Path {
10521            steps: vec![
10522                PathStep::new(AstNode::Name("foo".to_string())),
10523                PathStep::new(AstNode::Name("bar".to_string())),
10524                PathStep::new(AstNode::Name("baz".to_string())),
10525            ],
10526        };
10527        let result = evaluator.evaluate(&path, &data).unwrap();
10528        assert_eq!(result, JValue::from(42i64));
10529
10530        // Missing path returns undefined (not null - see issue #32)
10531        let path = AstNode::Path {
10532            steps: vec![PathStep::new(AstNode::Name("missing".to_string()))],
10533        };
10534        let result = evaluator.evaluate(&path, &data).unwrap();
10535        assert_eq!(result, JValue::Undefined);
10536    }
10537
10538    #[test]
10539    fn test_arithmetic_operations() {
10540        let mut evaluator = Evaluator::new();
10541        let data = JValue::Null;
10542
10543        // Addition
10544        let expr = AstNode::Binary {
10545            op: BinaryOp::Add,
10546            lhs: Box::new(AstNode::number(10.0)),
10547            rhs: Box::new(AstNode::number(5.0)),
10548        };
10549        let result = evaluator.evaluate(&expr, &data).unwrap();
10550        assert_eq!(result, JValue::Number(15.0));
10551
10552        // Subtraction
10553        let expr = AstNode::Binary {
10554            op: BinaryOp::Subtract,
10555            lhs: Box::new(AstNode::number(10.0)),
10556            rhs: Box::new(AstNode::number(5.0)),
10557        };
10558        let result = evaluator.evaluate(&expr, &data).unwrap();
10559        assert_eq!(result, JValue::Number(5.0));
10560
10561        // Multiplication
10562        let expr = AstNode::Binary {
10563            op: BinaryOp::Multiply,
10564            lhs: Box::new(AstNode::number(10.0)),
10565            rhs: Box::new(AstNode::number(5.0)),
10566        };
10567        let result = evaluator.evaluate(&expr, &data).unwrap();
10568        assert_eq!(result, JValue::Number(50.0));
10569
10570        // Division
10571        let expr = AstNode::Binary {
10572            op: BinaryOp::Divide,
10573            lhs: Box::new(AstNode::number(10.0)),
10574            rhs: Box::new(AstNode::number(5.0)),
10575        };
10576        let result = evaluator.evaluate(&expr, &data).unwrap();
10577        assert_eq!(result, JValue::Number(2.0));
10578
10579        // Modulo
10580        let expr = AstNode::Binary {
10581            op: BinaryOp::Modulo,
10582            lhs: Box::new(AstNode::number(10.0)),
10583            rhs: Box::new(AstNode::number(3.0)),
10584        };
10585        let result = evaluator.evaluate(&expr, &data).unwrap();
10586        assert_eq!(result, JValue::Number(1.0));
10587    }
10588
10589    #[test]
10590    fn test_division_by_zero() {
10591        let mut evaluator = Evaluator::new();
10592        let data = JValue::Null;
10593
10594        let expr = AstNode::Binary {
10595            op: BinaryOp::Divide,
10596            lhs: Box::new(AstNode::number(10.0)),
10597            rhs: Box::new(AstNode::number(0.0)),
10598        };
10599        let result = evaluator.evaluate(&expr, &data);
10600        assert!(result.is_err());
10601    }
10602
10603    #[test]
10604    fn test_comparison_operations() {
10605        let mut evaluator = Evaluator::new();
10606        let data = JValue::Null;
10607
10608        // Equal
10609        let expr = AstNode::Binary {
10610            op: BinaryOp::Equal,
10611            lhs: Box::new(AstNode::number(5.0)),
10612            rhs: Box::new(AstNode::number(5.0)),
10613        };
10614        assert_eq!(
10615            evaluator.evaluate(&expr, &data).unwrap(),
10616            JValue::Bool(true)
10617        );
10618
10619        // Not equal
10620        let expr = AstNode::Binary {
10621            op: BinaryOp::NotEqual,
10622            lhs: Box::new(AstNode::number(5.0)),
10623            rhs: Box::new(AstNode::number(3.0)),
10624        };
10625        assert_eq!(
10626            evaluator.evaluate(&expr, &data).unwrap(),
10627            JValue::Bool(true)
10628        );
10629
10630        // Less than
10631        let expr = AstNode::Binary {
10632            op: BinaryOp::LessThan,
10633            lhs: Box::new(AstNode::number(3.0)),
10634            rhs: Box::new(AstNode::number(5.0)),
10635        };
10636        assert_eq!(
10637            evaluator.evaluate(&expr, &data).unwrap(),
10638            JValue::Bool(true)
10639        );
10640
10641        // Greater than
10642        let expr = AstNode::Binary {
10643            op: BinaryOp::GreaterThan,
10644            lhs: Box::new(AstNode::number(5.0)),
10645            rhs: Box::new(AstNode::number(3.0)),
10646        };
10647        assert_eq!(
10648            evaluator.evaluate(&expr, &data).unwrap(),
10649            JValue::Bool(true)
10650        );
10651    }
10652
10653    #[test]
10654    fn test_logical_operations() {
10655        let mut evaluator = Evaluator::new();
10656        let data = JValue::Null;
10657
10658        // And - both true
10659        let expr = AstNode::Binary {
10660            op: BinaryOp::And,
10661            lhs: Box::new(AstNode::boolean(true)),
10662            rhs: Box::new(AstNode::boolean(true)),
10663        };
10664        assert_eq!(
10665            evaluator.evaluate(&expr, &data).unwrap(),
10666            JValue::Bool(true)
10667        );
10668
10669        // And - first false
10670        let expr = AstNode::Binary {
10671            op: BinaryOp::And,
10672            lhs: Box::new(AstNode::boolean(false)),
10673            rhs: Box::new(AstNode::boolean(true)),
10674        };
10675        assert_eq!(
10676            evaluator.evaluate(&expr, &data).unwrap(),
10677            JValue::Bool(false)
10678        );
10679
10680        // Or - first true
10681        let expr = AstNode::Binary {
10682            op: BinaryOp::Or,
10683            lhs: Box::new(AstNode::boolean(true)),
10684            rhs: Box::new(AstNode::boolean(false)),
10685        };
10686        assert_eq!(
10687            evaluator.evaluate(&expr, &data).unwrap(),
10688            JValue::Bool(true)
10689        );
10690
10691        // Or - both false
10692        let expr = AstNode::Binary {
10693            op: BinaryOp::Or,
10694            lhs: Box::new(AstNode::boolean(false)),
10695            rhs: Box::new(AstNode::boolean(false)),
10696        };
10697        assert_eq!(
10698            evaluator.evaluate(&expr, &data).unwrap(),
10699            JValue::Bool(false)
10700        );
10701    }
10702
10703    #[test]
10704    fn test_string_concatenation() {
10705        let mut evaluator = Evaluator::new();
10706        let data = JValue::Null;
10707
10708        let expr = AstNode::Binary {
10709            op: BinaryOp::Concatenate,
10710            lhs: Box::new(AstNode::string("Hello")),
10711            rhs: Box::new(AstNode::string(" World")),
10712        };
10713        let result = evaluator.evaluate(&expr, &data).unwrap();
10714        assert_eq!(result, JValue::string("Hello World"));
10715    }
10716
10717    #[test]
10718    fn test_range_operator() {
10719        let mut evaluator = Evaluator::new();
10720        let data = JValue::Null;
10721
10722        // Forward range
10723        let expr = AstNode::Binary {
10724            op: BinaryOp::Range,
10725            lhs: Box::new(AstNode::number(1.0)),
10726            rhs: Box::new(AstNode::number(5.0)),
10727        };
10728        let result = evaluator.evaluate(&expr, &data).unwrap();
10729        assert_eq!(
10730            result,
10731            JValue::array(vec![
10732                JValue::Number(1.0),
10733                JValue::Number(2.0),
10734                JValue::Number(3.0),
10735                JValue::Number(4.0),
10736                JValue::Number(5.0)
10737            ])
10738        );
10739
10740        // Backward range (start > end) returns empty array
10741        let expr = AstNode::Binary {
10742            op: BinaryOp::Range,
10743            lhs: Box::new(AstNode::number(5.0)),
10744            rhs: Box::new(AstNode::number(1.0)),
10745        };
10746        let result = evaluator.evaluate(&expr, &data).unwrap();
10747        assert_eq!(result, JValue::array(vec![]));
10748    }
10749
10750    #[test]
10751    fn test_in_operator() {
10752        let mut evaluator = Evaluator::new();
10753        let data = JValue::Null;
10754
10755        // In array
10756        let expr = AstNode::Binary {
10757            op: BinaryOp::In,
10758            lhs: Box::new(AstNode::number(3.0)),
10759            rhs: Box::new(AstNode::Array(vec![
10760                AstNode::number(1.0),
10761                AstNode::number(2.0),
10762                AstNode::number(3.0),
10763            ])),
10764        };
10765        let result = evaluator.evaluate(&expr, &data).unwrap();
10766        assert_eq!(result, JValue::Bool(true));
10767
10768        // Not in array
10769        let expr = AstNode::Binary {
10770            op: BinaryOp::In,
10771            lhs: Box::new(AstNode::number(5.0)),
10772            rhs: Box::new(AstNode::Array(vec![
10773                AstNode::number(1.0),
10774                AstNode::number(2.0),
10775                AstNode::number(3.0),
10776            ])),
10777        };
10778        let result = evaluator.evaluate(&expr, &data).unwrap();
10779        assert_eq!(result, JValue::Bool(false));
10780    }
10781
10782    #[test]
10783    fn test_unary_operations() {
10784        let mut evaluator = Evaluator::new();
10785        let data = JValue::Null;
10786
10787        // Negation
10788        let expr = AstNode::Unary {
10789            op: UnaryOp::Negate,
10790            operand: Box::new(AstNode::number(5.0)),
10791        };
10792        let result = evaluator.evaluate(&expr, &data).unwrap();
10793        assert_eq!(result, JValue::Number(-5.0));
10794
10795        // Not
10796        let expr = AstNode::Unary {
10797            op: UnaryOp::Not,
10798            operand: Box::new(AstNode::boolean(true)),
10799        };
10800        let result = evaluator.evaluate(&expr, &data).unwrap();
10801        assert_eq!(result, JValue::Bool(false));
10802    }
10803
10804    #[test]
10805    fn test_array_construction() {
10806        let mut evaluator = Evaluator::new();
10807        let data = JValue::Null;
10808
10809        let expr = AstNode::Array(vec![
10810            AstNode::number(1.0),
10811            AstNode::number(2.0),
10812            AstNode::number(3.0),
10813        ]);
10814        let result = evaluator.evaluate(&expr, &data).unwrap();
10815        // Whole number literals are preserved as integers
10816        assert_eq!(result, JValue::from(serde_json::json!([1, 2, 3])));
10817    }
10818
10819    #[test]
10820    fn test_object_construction() {
10821        let mut evaluator = Evaluator::new();
10822        let data = JValue::Null;
10823
10824        let expr = AstNode::Object(vec![
10825            (AstNode::string("name"), AstNode::string("Alice")),
10826            (AstNode::string("age"), AstNode::number(30.0)),
10827        ]);
10828        let result = evaluator.evaluate(&expr, &data).unwrap();
10829        // Whole number literals are preserved as integers
10830        let mut expected = IndexMap::new();
10831        expected.insert("name".to_string(), JValue::string("Alice"));
10832        expected.insert("age".to_string(), JValue::Number(30.0));
10833        assert_eq!(result, JValue::object(expected));
10834    }
10835
10836    #[test]
10837    fn test_conditional() {
10838        let mut evaluator = Evaluator::new();
10839        let data = JValue::Null;
10840
10841        // True condition
10842        let expr = AstNode::Conditional {
10843            condition: Box::new(AstNode::boolean(true)),
10844            then_branch: Box::new(AstNode::string("yes")),
10845            else_branch: Some(Box::new(AstNode::string("no"))),
10846        };
10847        let result = evaluator.evaluate(&expr, &data).unwrap();
10848        assert_eq!(result, JValue::string("yes"));
10849
10850        // False condition
10851        let expr = AstNode::Conditional {
10852            condition: Box::new(AstNode::boolean(false)),
10853            then_branch: Box::new(AstNode::string("yes")),
10854            else_branch: Some(Box::new(AstNode::string("no"))),
10855        };
10856        let result = evaluator.evaluate(&expr, &data).unwrap();
10857        assert_eq!(result, JValue::string("no"));
10858
10859        // No else branch returns undefined (not null)
10860        let expr = AstNode::Conditional {
10861            condition: Box::new(AstNode::boolean(false)),
10862            then_branch: Box::new(AstNode::string("yes")),
10863            else_branch: None,
10864        };
10865        let result = evaluator.evaluate(&expr, &data).unwrap();
10866        assert_eq!(result, JValue::Undefined);
10867    }
10868
10869    #[test]
10870    fn test_block_expression() {
10871        let mut evaluator = Evaluator::new();
10872        let data = JValue::Null;
10873
10874        let expr = AstNode::Block(vec![
10875            AstNode::number(1.0),
10876            AstNode::number(2.0),
10877            AstNode::number(3.0),
10878        ]);
10879        let result = evaluator.evaluate(&expr, &data).unwrap();
10880        // Block returns the last expression; whole numbers are preserved as integers
10881        assert_eq!(result, JValue::from(3i64));
10882    }
10883
10884    #[test]
10885    fn test_function_calls() {
10886        let mut evaluator = Evaluator::new();
10887        let data = JValue::Null;
10888
10889        // uppercase function
10890        let expr = AstNode::Function {
10891            name: "uppercase".to_string(),
10892            args: vec![AstNode::string("hello")],
10893            is_builtin: true,
10894        };
10895        let result = evaluator.evaluate(&expr, &data).unwrap();
10896        assert_eq!(result, JValue::string("HELLO"));
10897
10898        // lowercase function
10899        let expr = AstNode::Function {
10900            name: "lowercase".to_string(),
10901            args: vec![AstNode::string("HELLO")],
10902            is_builtin: true,
10903        };
10904        let result = evaluator.evaluate(&expr, &data).unwrap();
10905        assert_eq!(result, JValue::string("hello"));
10906
10907        // length function
10908        let expr = AstNode::Function {
10909            name: "length".to_string(),
10910            args: vec![AstNode::string("hello")],
10911            is_builtin: true,
10912        };
10913        let result = evaluator.evaluate(&expr, &data).unwrap();
10914        assert_eq!(result, JValue::from(5i64));
10915
10916        // sum function
10917        let expr = AstNode::Function {
10918            name: "sum".to_string(),
10919            args: vec![AstNode::Array(vec![
10920                AstNode::number(1.0),
10921                AstNode::number(2.0),
10922                AstNode::number(3.0),
10923            ])],
10924            is_builtin: true,
10925        };
10926        let result = evaluator.evaluate(&expr, &data).unwrap();
10927        assert_eq!(result, JValue::Number(6.0));
10928
10929        // count function
10930        let expr = AstNode::Function {
10931            name: "count".to_string(),
10932            args: vec![AstNode::Array(vec![
10933                AstNode::number(1.0),
10934                AstNode::number(2.0),
10935                AstNode::number(3.0),
10936            ])],
10937            is_builtin: true,
10938        };
10939        let result = evaluator.evaluate(&expr, &data).unwrap();
10940        assert_eq!(result, JValue::from(3i64));
10941    }
10942
10943    #[test]
10944    fn test_complex_nested_data() {
10945        let mut evaluator = Evaluator::new();
10946        let data = JValue::from(serde_json::json!({
10947            "users": [
10948                {"name": "Alice", "age": 30},
10949                {"name": "Bob", "age": 25},
10950                {"name": "Charlie", "age": 35}
10951            ],
10952            "metadata": {
10953                "total": 3,
10954                "version": "1.0"
10955            }
10956        }));
10957        // Access nested field
10958        let path = AstNode::Path {
10959            steps: vec![
10960                PathStep::new(AstNode::Name("metadata".to_string())),
10961                PathStep::new(AstNode::Name("version".to_string())),
10962            ],
10963        };
10964        let result = evaluator.evaluate(&path, &data).unwrap();
10965        assert_eq!(result, JValue::string("1.0"));
10966    }
10967
10968    #[test]
10969    fn test_error_handling() {
10970        let mut evaluator = Evaluator::new();
10971        let data = JValue::Null;
10972
10973        // Type error: adding string and number
10974        let expr = AstNode::Binary {
10975            op: BinaryOp::Add,
10976            lhs: Box::new(AstNode::string("hello")),
10977            rhs: Box::new(AstNode::number(5.0)),
10978        };
10979        let result = evaluator.evaluate(&expr, &data);
10980        assert!(result.is_err());
10981
10982        // Reference error: undefined function
10983        let expr = AstNode::Function {
10984            name: "undefined_function".to_string(),
10985            args: vec![],
10986            is_builtin: false,
10987        };
10988        let result = evaluator.evaluate(&expr, &data);
10989        assert!(result.is_err());
10990    }
10991
10992    #[test]
10993    fn test_truthiness() {
10994        let evaluator = Evaluator::new();
10995
10996        assert!(!evaluator.is_truthy(&JValue::Null));
10997        assert!(!evaluator.is_truthy(&JValue::Bool(false)));
10998        assert!(evaluator.is_truthy(&JValue::Bool(true)));
10999        assert!(!evaluator.is_truthy(&JValue::from(0i64)));
11000        assert!(evaluator.is_truthy(&JValue::from(1i64)));
11001        assert!(!evaluator.is_truthy(&JValue::string("")));
11002        assert!(evaluator.is_truthy(&JValue::string("hello")));
11003        assert!(!evaluator.is_truthy(&JValue::array(vec![])));
11004        assert!(evaluator.is_truthy(&JValue::from(serde_json::json!([1, 2, 3]))));
11005    }
11006
11007    #[test]
11008    fn test_integration_with_parser() {
11009        use crate::parser::parse;
11010
11011        let mut evaluator = Evaluator::new();
11012        let data = JValue::from(serde_json::json!({
11013            "price": 10,
11014            "quantity": 5
11015        }));
11016        // Test simple path
11017        let ast = parse("price").unwrap();
11018        let result = evaluator.evaluate(&ast, &data).unwrap();
11019        assert_eq!(result, JValue::from(10i64));
11020
11021        // Test arithmetic
11022        let ast = parse("price * quantity").unwrap();
11023        let result = evaluator.evaluate(&ast, &data).unwrap();
11024        // Note: Arithmetic operations produce f64 results in JSON
11025        assert_eq!(result, JValue::Number(50.0));
11026
11027        // Test comparison
11028        let ast = parse("price > 5").unwrap();
11029        let result = evaluator.evaluate(&ast, &data).unwrap();
11030        assert_eq!(result, JValue::Bool(true));
11031    }
11032
11033    #[test]
11034    fn test_evaluate_dollar_function_uppercase() {
11035        use crate::parser::parse;
11036
11037        let mut evaluator = Evaluator::new();
11038        let ast = parse(r#"$uppercase("hello")"#).unwrap();
11039        let empty = JValue::object(IndexMap::new());
11040        let result = evaluator.evaluate(&ast, &empty).unwrap();
11041        assert_eq!(result, JValue::string("HELLO"));
11042    }
11043
11044    #[test]
11045    fn test_evaluate_dollar_function_sum() {
11046        use crate::parser::parse;
11047
11048        let mut evaluator = Evaluator::new();
11049        let ast = parse("$sum([1, 2, 3, 4, 5])").unwrap();
11050        let empty = JValue::object(IndexMap::new());
11051        let result = evaluator.evaluate(&ast, &empty).unwrap();
11052        assert_eq!(result, JValue::Number(15.0));
11053    }
11054
11055    #[test]
11056    fn test_evaluate_nested_dollar_functions() {
11057        use crate::parser::parse;
11058
11059        let mut evaluator = Evaluator::new();
11060        let ast = parse(r#"$length($lowercase("HELLO"))"#).unwrap();
11061        let empty = JValue::object(IndexMap::new());
11062        let result = evaluator.evaluate(&ast, &empty).unwrap();
11063        // length() returns an integer, not a float
11064        assert_eq!(result, JValue::Number(5.0));
11065    }
11066
11067    #[test]
11068    fn test_array_mapping() {
11069        use crate::parser::parse;
11070
11071        let mut evaluator = Evaluator::new();
11072        let data: JValue = serde_json::from_str(
11073            r#"{
11074            "products": [
11075                {"id": 1, "name": "Laptop", "price": 999.99},
11076                {"id": 2, "name": "Mouse", "price": 29.99},
11077                {"id": 3, "name": "Keyboard", "price": 79.99}
11078            ]
11079        }"#,
11080        )
11081        .map(|v: serde_json::Value| JValue::from(v))
11082        .unwrap();
11083
11084        // Test mapping over array to extract field
11085        let ast = parse("products.name").unwrap();
11086        let result = evaluator.evaluate(&ast, &data).unwrap();
11087        assert_eq!(
11088            result,
11089            JValue::array(vec![
11090                JValue::string("Laptop"),
11091                JValue::string("Mouse"),
11092                JValue::string("Keyboard")
11093            ])
11094        );
11095
11096        // Test mapping over array to extract prices
11097        let ast = parse("products.price").unwrap();
11098        let result = evaluator.evaluate(&ast, &data).unwrap();
11099        assert_eq!(
11100            result,
11101            JValue::array(vec![
11102                JValue::Number(999.99),
11103                JValue::Number(29.99),
11104                JValue::Number(79.99)
11105            ])
11106        );
11107
11108        // Test with $sum function on mapped array
11109        let ast = parse("$sum(products.price)").unwrap();
11110        let result = evaluator.evaluate(&ast, &data).unwrap();
11111        assert_eq!(result, JValue::Number(1109.97));
11112    }
11113
11114    #[test]
11115    fn test_empty_brackets() {
11116        use crate::parser::parse;
11117
11118        let mut evaluator = Evaluator::new();
11119
11120        // Test empty brackets on simple value - should wrap in array
11121        let data: JValue = JValue::from(serde_json::json!({"foo": "bar"}));
11122        let ast = parse("foo[]").unwrap();
11123        let result = evaluator.evaluate(&ast, &data).unwrap();
11124        assert_eq!(
11125            result,
11126            JValue::array(vec![JValue::string("bar")]),
11127            "Empty brackets should wrap value in array"
11128        );
11129
11130        // Test empty brackets on array - should return array as-is
11131        let data2: JValue = JValue::from(serde_json::json!({"arr": [1, 2, 3]}));
11132        let ast2 = parse("arr[]").unwrap();
11133        let result2 = evaluator.evaluate(&ast2, &data2).unwrap();
11134        assert_eq!(
11135            result2,
11136            JValue::array(vec![
11137                JValue::Number(1.0),
11138                JValue::Number(2.0),
11139                JValue::Number(3.0)
11140            ]),
11141            "Empty brackets should preserve array"
11142        );
11143    }
11144}