Skip to main content

json_eval_rs/rlogic/evaluator/
mod.rs

1use super::compiled::CompiledLogic;
2use super::config::RLogicConfig;
3use index::TableIndex;
4use serde_json::Value;
5use std::cell::UnsafeCell;
6use std::collections::HashMap;
7use std::sync::RwLock;
8
9pub mod arithmetic;
10pub mod array_lookup;
11pub mod array_ops;
12pub mod comparison;
13pub mod date_ops;
14pub mod helpers;
15pub mod index;
16pub mod logical;
17pub mod math_ops;
18pub mod optimizations;
19pub mod string_ops;
20pub mod types;
21
22pub use helpers::*;
23pub use types::*;
24
25/// Active self-table scope set during `evaluate_table_inner`.
26///
27/// # Safety
28/// `rows` is a raw pointer to `local_rows` on the stack of `evaluate_table_inner`.
29/// Valid lifetime: from `enter_table_scope()` to `TableScopeGuard::drop()`.
30/// Evaluation is single-threaded (protected by `eval_lock` in `evaluate_internal`).
31pub(crate) struct TableScope {
32    /// Normalized JSON pointer path to the table being evaluated
33    pub path: String,
34    /// Pointer to the local rows being built in table_evaluate_inner
35    pub rows: *const Vec<Value>,
36    /// Optional cursor to the current row index being evaluated (for fast $column lookup)
37    pub current_row: Option<usize>,
38}
39
40// SAFETY: table evaluation is protected by eval_lock (single-threaded access).
41// UnsafeCell provides interior mutability without adding Sync constraints.
42// The raw *const pointer in TableScope is only accessed under eval_lock.
43unsafe impl Send for TableScope {}
44unsafe impl Send for Evaluator {}
45unsafe impl Sync for Evaluator {}
46
47/// RAII guard that clears the active TableScope on drop
48pub struct TableScopeGuard<'a> {
49    evaluator: &'a Evaluator,
50}
51
52impl<'a> Drop for TableScopeGuard<'a> {
53    fn drop(&mut self) {
54        // SAFETY: single-threaded (eval_lock), no concurrent access
55        unsafe {
56            *self.evaluator.table_scope.get() = None;
57        }
58    }
59}
60
61/// High-performance zero-copy evaluator with dual-context support
62///
63/// ## Design Principles
64/// 1. **Zero-copy**: All data access via references, no cloning
65/// 2. **Dual-context**: Separate user_data and internal_context for scoped variables
66/// 3. **Recursive**: Clean recursive evaluation with depth tracking
67///
68/// ## Context Resolution
69/// - Variables ($var) lookup order: internal_context → user_data
70/// - Internal context holds: $iteration, $threshold, $loopIteration, etc.
71pub struct Evaluator {
72    config: RLogicConfig,
73    /// Upfront indices for large tables (name -> index)
74    indices: RwLock<HashMap<String, TableIndex>>,
75    /// Extracted large static arrays for zero-copy resolution
76    static_arrays: Option<std::sync::Arc<indexmap::IndexMap<String, std::sync::Arc<Value>>>>,
77    /// Active self-table scope during table evaluation (None outside table eval)
78    pub(crate) table_scope: UnsafeCell<Option<TableScope>>,
79}
80
81impl Evaluator {
82    pub fn new() -> Self {
83        Self {
84            config: RLogicConfig::default(),
85            indices: RwLock::new(HashMap::new()),
86            static_arrays: None,
87            table_scope: UnsafeCell::new(None),
88        }
89    }
90
91    /// Register a table scope for self-reference interception.
92    ///
93    /// Returns a guard that clears the scope on drop.
94    ///
95    /// # Safety
96    /// `rows` must outlive the returned guard. The guard MUST be dropped before
97    /// `rows` is moved or dropped. Caller (table_evaluate_inner) is responsible.
98    pub(crate) fn enter_table_scope<'a>(
99        &'a self,
100        path: String,
101        rows: &Vec<Value>,
102    ) -> TableScopeGuard<'a> {
103        // SAFETY: single-threaded (eval_lock held by caller)
104        unsafe {
105            *self.table_scope.get() = Some(TableScope {
106                path,
107                rows: rows as *const Vec<Value>,
108                current_row: None,
109            });
110        }
111        TableScopeGuard { evaluator: self }
112    }
113
114    /// Update the rows pointer in the active table scope.
115    pub(crate) fn update_table_scope_rows(&self, rows: &Vec<Value>) {
116        // SAFETY: single-threaded (eval_lock held by caller)
117        unsafe {
118            if let Some(ts) = (*self.table_scope.get()).as_mut() {
119                ts.rows = rows as *const Vec<Value>;
120            }
121        }
122    }
123
124    /// Set the row cursor for the active table scope
125    pub(crate) fn set_table_scope_row(&self, row_idx: Option<usize>) {
126        // SAFETY: single-threaded (eval_lock held by caller)
127        unsafe {
128            if let Some(ts) = (*self.table_scope.get()).as_mut() {
129                ts.current_row = row_idx;
130            }
131        }
132    }
133
134    pub fn with_config(mut self, config: RLogicConfig) -> Self {
135        self.config = config;
136        self
137    }
138
139    /// Set static arrays for evaluation context
140    pub fn set_static_arrays(
141        &mut self,
142        static_arrays: std::sync::Arc<indexmap::IndexMap<String, std::sync::Arc<Value>>>,
143    ) {
144        self.static_arrays = Some(static_arrays);
145    }
146
147    /// Build and store index for a table
148    pub fn index_table(&self, name: &str, data: &Value) {
149        if let Some(index) = TableIndex::new(data) {
150            if let Ok(mut indices) = self.indices.write() {
151                indices.insert(name.to_string(), index);
152            }
153        }
154    }
155
156    /// Clear all stored indices
157    pub fn clear_indices(&self) {
158        if let Ok(mut indices) = self.indices.write() {
159            indices.clear();
160        }
161    }
162
163    /// Public API: Evaluate compiled logic with user data only
164    /// Uses fast path for simple cases to avoid recursion overhead
165    #[inline]
166    pub fn evaluate(&self, logic: &CompiledLogic, data: &Value) -> Result<Value, String> {
167        // Fast path for literals (most common cases)
168        match logic {
169            CompiledLogic::Null => return Ok(Value::Null),
170            CompiledLogic::Bool(b) => return Ok(Value::Bool(*b)),
171            CompiledLogic::Number(n) => {
172                return Ok(self.f64_to_json(*n));
173            }
174            CompiledLogic::String(s) => return Ok(Value::String(s.clone())),
175            CompiledLogic::Var(name, None) if !name.is_empty() => {
176                // Simple variable without default
177                return self.eval_var_or_default(name, &None, data, &Value::Null, 0);
178            }
179            CompiledLogic::Ref(path, None) if !path.is_empty() => {
180                // Simple variable without default
181                return self.eval_var_or_default(path, &None, data, &Value::Null, 0);
182            }
183            // Fast path for small arithmetic operations (≤5 items)
184            CompiledLogic::Add(items) if items.len() <= 5 => {
185                if let Some(result) =
186                    self.eval_arithmetic_fast(ArithOp::Add, items, data, &Value::Null)
187                {
188                    return Ok(result);
189                }
190            }
191            CompiledLogic::Subtract(items) if items.len() <= 5 => {
192                if let Some(result) =
193                    self.eval_arithmetic_fast(ArithOp::Sub, items, data, &Value::Null)
194                {
195                    return Ok(result);
196                }
197            }
198            CompiledLogic::Multiply(items) if items.len() <= 5 => {
199                if let Some(result) =
200                    self.eval_arithmetic_fast(ArithOp::Mul, items, data, &Value::Null)
201                {
202                    return Ok(result);
203                }
204            }
205            CompiledLogic::Divide(items) if items.len() <= 5 => {
206                if let Some(result) =
207                    self.eval_arithmetic_fast(ArithOp::Div, items, data, &Value::Null)
208                {
209                    return Ok(result);
210                }
211            }
212            _ => {}
213        }
214
215        // Fall back to full evaluation for complex cases
216        self.evaluate_with_context(logic, data, &Value::Null, 0)
217    }
218
219    /// Evaluate with internal context (for scoped variables)
220    ///
221    /// # Arguments
222    /// * `logic` - The compiled logic expression to evaluate
223    /// * `user_data` - User's data (primary lookup source)
224    /// * `internal_context` - Internal variables (e.g., $iteration, $loopIteration)
225    ///
226    /// # Zero-Copy Guarantee
227    /// This method uses only references and never clones the data contexts.
228    /// Internal variables are looked up first in `internal_context`, then fall back to `user_data`.
229    #[inline]
230    pub fn evaluate_with_internal_context(
231        &self,
232        logic: &CompiledLogic,
233        user_data: &Value,
234        internal_context: &Value,
235    ) -> Result<Value, String> {
236        self.evaluate_with_context(logic, user_data, internal_context, 0)
237    }
238
239    /// Internal recursive evaluation with depth tracking
240    ///
241    /// # Context Resolution Order
242    /// 1. Check internal_context first (for scoped variables like $loopIteration)
243    /// 2. Fall back to user_data (for regular user variables)
244    ///
245    /// This enables zero-copy scoped variable handling without merging contexts.
246    fn evaluate_with_context(
247        &self,
248        logic: &CompiledLogic,
249        user_data: &Value,
250        internal_context: &Value,
251        depth: usize,
252    ) -> Result<Value, String> {
253        // Recursion limit check
254        if depth > self.config.recursion_limit {
255            return Err("Recursion limit exceeded".to_string());
256        }
257
258        match logic {
259            // ========== Literals ==========
260            CompiledLogic::Null => Ok(Value::Null),
261            CompiledLogic::Bool(b) => Ok(Value::Bool(*b)),
262            CompiledLogic::Number(n) => Ok(self.f64_to_json(*n)),
263            CompiledLogic::String(s) => Ok(Value::String(s.clone())),
264            CompiledLogic::Array(arr) => {
265                let results: Result<Vec<_>, _> = arr
266                    .iter()
267                    .map(|item| {
268                        self.evaluate_with_context(item, user_data, internal_context, depth + 1)
269                    })
270                    .collect();
271                Ok(Value::Array(results?))
272            }
273
274            // ========== Variable Access (Zero-Copy) ==========
275            CompiledLogic::Var(name, default) => {
276                self.eval_var_or_default(name, default, user_data, internal_context, depth)
277            }
278
279            CompiledLogic::Ref(path, default) => {
280                self.eval_var_or_default(path, default, user_data, internal_context, depth)
281            }
282
283            // ========== Logical Operators ==========
284            CompiledLogic::And(items) => {
285                self.eval_and_or(items, true, user_data, internal_context, depth)
286            }
287            CompiledLogic::Or(items) => {
288                self.eval_and_or(items, false, user_data, internal_context, depth)
289            }
290            CompiledLogic::Not(expr) => {
291                let result =
292                    self.evaluate_with_context(expr, user_data, internal_context, depth + 1)?;
293                Ok(Value::Bool(!is_truthy(&result)))
294            }
295            CompiledLogic::If(cond, then_expr, else_expr) => {
296                let condition =
297                    self.evaluate_with_context(cond, user_data, internal_context, depth + 1)?;
298                if is_truthy(&condition) {
299                    self.evaluate_with_context(then_expr, user_data, internal_context, depth + 1)
300                } else {
301                    self.evaluate_with_context(else_expr, user_data, internal_context, depth + 1)
302                }
303            }
304
305            // ========== Comparison Operators ==========
306            CompiledLogic::Equal(a, b) => {
307                self.eval_binary_compare(CompOp::Eq, a, b, user_data, internal_context, depth)
308            }
309            CompiledLogic::StrictEqual(a, b) => {
310                self.eval_binary_compare(CompOp::StrictEq, a, b, user_data, internal_context, depth)
311            }
312            CompiledLogic::NotEqual(a, b) => {
313                self.eval_binary_compare(CompOp::Ne, a, b, user_data, internal_context, depth)
314            }
315            CompiledLogic::StrictNotEqual(a, b) => {
316                self.eval_binary_compare(CompOp::StrictNe, a, b, user_data, internal_context, depth)
317            }
318            CompiledLogic::LessThan(a, b) => {
319                self.eval_binary_compare(CompOp::Lt, a, b, user_data, internal_context, depth)
320            }
321            CompiledLogic::LessThanOrEqual(a, b) => {
322                self.eval_binary_compare(CompOp::Le, a, b, user_data, internal_context, depth)
323            }
324            CompiledLogic::GreaterThan(a, b) => {
325                self.eval_binary_compare(CompOp::Gt, a, b, user_data, internal_context, depth)
326            }
327            CompiledLogic::GreaterThanOrEqual(a, b) => {
328                self.eval_binary_compare(CompOp::Ge, a, b, user_data, internal_context, depth)
329            }
330
331            // ========== Arithmetic Operators ==========
332            CompiledLogic::Add(items) => self.eval_array_fold(
333                items,
334                0.0,
335                |acc, n| Some(acc + n),
336                user_data,
337                internal_context,
338                depth,
339            ),
340            CompiledLogic::Subtract(items) => {
341                if items.is_empty() {
342                    return Ok(self.f64_to_json(0.0));
343                }
344                let first =
345                    self.evaluate_with_context(&items[0], user_data, internal_context, depth + 1)?;
346                let mut result = to_f64(&first);
347
348                if items.len() == 1 {
349                    return Ok(self.f64_to_json(-result));
350                }
351
352                for item in &items[1..] {
353                    let val =
354                        self.evaluate_with_context(item, user_data, internal_context, depth + 1)?;
355                    result -= to_f64(&val);
356                }
357                Ok(self.f64_to_json(result))
358            }
359            CompiledLogic::Multiply(items) => {
360                // Special case: empty multiply returns 0 (matching test expectations, though mathematically identity is 1)
361                if items.is_empty() {
362                    return Ok(self.f64_to_json(0.0));
363                }
364                self.eval_array_fold(
365                    items,
366                    1.0,
367                    |acc, n| Some(acc * n),
368                    user_data,
369                    internal_context,
370                    depth,
371                )
372            }
373            CompiledLogic::Divide(items) => {
374                if items.is_empty() {
375                    return Ok(self.f64_to_json(0.0_f64));
376                }
377                let first =
378                    self.evaluate_with_context(&items[0], user_data, internal_context, depth + 1)?;
379                let mut result = to_f64(&first);
380
381                for item in &items[1..] {
382                    let val =
383                        self.evaluate_with_context(item, user_data, internal_context, depth + 1)?;
384                    let divisor = to_f64(&val);
385                    if divisor == 0.0 {
386                        return Ok(Value::Null);
387                    }
388                    result /= divisor;
389                }
390                Ok(self.f64_to_json(result))
391            }
392            CompiledLogic::Modulo(a, b) => self.eval_binary_arith(
393                a,
394                b,
395                |a, b| if b == 0.0 { None } else { Some(a % b) },
396                user_data,
397                internal_context,
398                depth,
399            ),
400            CompiledLogic::Power(a, b) => self.eval_binary_arith(
401                a,
402                b,
403                |a, b| Some(a.powf(b)),
404                user_data,
405                internal_context,
406                depth,
407            ),
408
409            // ========== Array Operations ==========
410            CompiledLogic::Map(array_expr, logic_expr) => {
411                self.eval_map(array_expr, logic_expr, user_data, internal_context, depth)
412            }
413            CompiledLogic::Filter(array_expr, logic_expr) => {
414                self.eval_filter(array_expr, logic_expr, user_data, internal_context, depth)
415            }
416            CompiledLogic::Reduce(array_expr, logic_expr, initial_expr) => self.eval_reduce(
417                array_expr,
418                logic_expr,
419                initial_expr,
420                user_data,
421                internal_context,
422                depth,
423            ),
424            CompiledLogic::All(array_expr, logic_expr) => self.eval_quantifier(
425                Quantifier::All,
426                array_expr,
427                logic_expr,
428                user_data,
429                internal_context,
430                depth,
431            ),
432            CompiledLogic::Some(array_expr, logic_expr) => self.eval_quantifier(
433                Quantifier::Some,
434                array_expr,
435                logic_expr,
436                user_data,
437                internal_context,
438                depth,
439            ),
440            CompiledLogic::None(array_expr, logic_expr) => self.eval_quantifier(
441                Quantifier::None,
442                array_expr,
443                logic_expr,
444                user_data,
445                internal_context,
446                depth,
447            ),
448            CompiledLogic::Merge(items) => {
449                self.eval_merge(items, user_data, internal_context, depth)
450            }
451            CompiledLogic::In(value_expr, array_expr) => {
452                self.eval_in(value_expr, array_expr, user_data, internal_context, depth)
453            }
454            CompiledLogic::Sum(array_expr, field_expr, threshold_expr) => self.eval_sum(
455                array_expr,
456                field_expr,
457                threshold_expr,
458                user_data,
459                internal_context,
460                depth,
461            ),
462            CompiledLogic::For(start_expr, end_expr, logic_expr) => self.eval_for(
463                start_expr,
464                end_expr,
465                logic_expr,
466                user_data,
467                internal_context,
468                depth,
469            ),
470            CompiledLogic::Multiplies(items) => {
471                self.eval_multiplies(items, user_data, internal_context, depth)
472            }
473            CompiledLogic::Divides(items) => {
474                self.eval_divides(items, user_data, internal_context, depth)
475            }
476
477            // ========== Array Lookup Operations ==========
478            CompiledLogic::ValueAt(table_expr, row_idx_expr, col_name_expr) => self.eval_valueat(
479                table_expr,
480                row_idx_expr,
481                col_name_expr,
482                user_data,
483                internal_context,
484                depth,
485            ),
486            CompiledLogic::MaxAt(table_expr, col_name_expr) => self.eval_maxat(
487                table_expr,
488                col_name_expr,
489                user_data,
490                internal_context,
491                depth,
492            ),
493            CompiledLogic::IndexAt(lookup_expr, table_expr, field_expr, range_expr) => self
494                .eval_indexat(
495                    lookup_expr,
496                    table_expr,
497                    field_expr,
498                    range_expr,
499                    user_data,
500                    internal_context,
501                    depth,
502                ),
503            CompiledLogic::Match(table_expr, conditions) => {
504                self.eval_match(table_expr, conditions, user_data, internal_context, depth)
505            }
506            CompiledLogic::MatchRange(table_expr, conditions) => {
507                self.eval_matchrange(table_expr, conditions, user_data, internal_context, depth)
508            }
509            CompiledLogic::Choose(table_expr, conditions) => {
510                self.eval_choose(table_expr, conditions, user_data, internal_context, depth)
511            }
512            CompiledLogic::FindIndex(table_expr, conditions) => {
513                self.eval_findindex(table_expr, conditions, user_data, internal_context, depth)
514            }
515
516            // ========== String Operations ==========
517            CompiledLogic::Cat(items) => {
518                self.concat_strings(items, user_data, internal_context, depth)
519            }
520            CompiledLogic::Substr(string_expr, start_expr, length_expr) => self.eval_substr(
521                string_expr,
522                start_expr,
523                length_expr,
524                user_data,
525                internal_context,
526                depth,
527            ),
528            CompiledLogic::Search(find_expr, within_expr, start_expr) => self.eval_search(
529                find_expr,
530                within_expr,
531                start_expr,
532                user_data,
533                internal_context,
534                depth,
535            ),
536            CompiledLogic::Left(text_expr, num_expr) => self.extract_text_side(
537                text_expr,
538                num_expr.as_deref(),
539                true,
540                user_data,
541                internal_context,
542                depth,
543            ),
544            CompiledLogic::Right(text_expr, num_expr) => self.extract_text_side(
545                text_expr,
546                num_expr.as_deref(),
547                false,
548                user_data,
549                internal_context,
550                depth,
551            ),
552            CompiledLogic::Mid(text_expr, start_expr, num_expr) => self.eval_mid(
553                text_expr,
554                start_expr,
555                num_expr,
556                user_data,
557                internal_context,
558                depth,
559            ),
560            CompiledLogic::SplitText(value_expr, sep_expr, index_expr) => self.eval_split_text(
561                value_expr,
562                sep_expr,
563                index_expr,
564                user_data,
565                internal_context,
566                depth,
567            ),
568            CompiledLogic::Concat(items) => {
569                self.concat_strings(items, user_data, internal_context, depth)
570            }
571            CompiledLogic::SplitValue(string_expr, sep_expr) => {
572                self.eval_split_value(string_expr, sep_expr, user_data, internal_context, depth)
573            }
574            CompiledLogic::StringFormat(value_expr, decimals, prefix, suffix, thousands_sep) => {
575                self.eval_string_format(
576                    value_expr,
577                    decimals,
578                    prefix,
579                    suffix,
580                    thousands_sep,
581                    user_data,
582                    internal_context,
583                    depth,
584                )
585            }
586            CompiledLogic::Length(expr) => {
587                self.eval_length(expr, user_data, internal_context, depth)
588            }
589            CompiledLogic::Len(expr) => self.eval_len(expr, user_data, internal_context, depth),
590
591            // ========== Math Operations ==========
592            CompiledLogic::Abs(expr) => {
593                self.eval_unary_math(expr, |n| n.abs(), user_data, internal_context, depth)
594            }
595            CompiledLogic::Max(items) => {
596                self.eval_min_max(items, true, user_data, internal_context, depth)
597            }
598            CompiledLogic::Min(items) => {
599                self.eval_min_max(items, false, user_data, internal_context, depth)
600            }
601            CompiledLogic::Pow(base_expr, exp_expr) => {
602                self.eval_pow(base_expr, exp_expr, user_data, internal_context, depth)
603            }
604            CompiledLogic::Round(expr, decimals) => {
605                self.apply_round(expr, decimals, 0, user_data, internal_context, depth)
606            }
607            CompiledLogic::RoundUp(expr, decimals) => {
608                self.apply_round(expr, decimals, 1, user_data, internal_context, depth)
609            }
610            CompiledLogic::RoundDown(expr, decimals) => {
611                self.apply_round(expr, decimals, 2, user_data, internal_context, depth)
612            }
613            CompiledLogic::Ceiling(expr, significance) => {
614                self.eval_ceiling(expr, significance, user_data, internal_context, depth)
615            }
616            CompiledLogic::Floor(expr, significance) => {
617                self.eval_floor(expr, significance, user_data, internal_context, depth)
618            }
619            CompiledLogic::Trunc(expr, decimals) => {
620                self.eval_trunc(expr, decimals, user_data, internal_context, depth)
621            }
622            CompiledLogic::Mround(value_expr, multiple_expr) => self.eval_mround(
623                value_expr,
624                multiple_expr,
625                user_data,
626                internal_context,
627                depth,
628            ),
629
630            // ========== Date Operations ==========
631            CompiledLogic::Today => self.eval_today(),
632            CompiledLogic::Now => self.eval_now(),
633            CompiledLogic::Days(end_expr, start_expr) => {
634                self.eval_days(end_expr, start_expr, user_data, internal_context, depth)
635            }
636            CompiledLogic::Year(expr) => {
637                self.extract_date_component(expr, "year", user_data, internal_context, depth)
638            }
639            CompiledLogic::Month(expr) => {
640                self.extract_date_component(expr, "month", user_data, internal_context, depth)
641            }
642            CompiledLogic::Day(expr) => {
643                self.extract_date_component(expr, "day", user_data, internal_context, depth)
644            }
645            CompiledLogic::Date(year_expr, month_expr, day_expr) => self.eval_date(
646                year_expr,
647                month_expr,
648                day_expr,
649                user_data,
650                internal_context,
651                depth,
652            ),
653            CompiledLogic::DateFormat(date_expr, format_expr) => {
654                self.eval_date_format(date_expr, format_expr, user_data, internal_context, depth)
655            }
656            CompiledLogic::YearFrac(start_expr, end_expr, basis_expr) => self.eval_year_frac(
657                start_expr,
658                end_expr,
659                basis_expr,
660                user_data,
661                internal_context,
662                depth,
663            ),
664            CompiledLogic::DateDif(start_expr, end_expr, unit_expr) => self.eval_date_dif(
665                start_expr,
666                end_expr,
667                unit_expr,
668                user_data,
669                internal_context,
670                depth,
671            ),
672
673            // ========== Utility Operators ==========
674            CompiledLogic::Missing(keys) => {
675                let missing: Vec<_> = keys
676                    .iter()
677                    .filter(|key| self.is_key_missing(user_data, key))
678                    .map(|k| Value::String(k.clone()))
679                    .collect();
680                Ok(Value::Array(missing))
681            }
682            CompiledLogic::MissingSome(min_expr, keys) => {
683                let min_val =
684                    self.evaluate_with_context(min_expr, user_data, internal_context, depth + 1)?;
685                let minimum = to_number(&min_val) as usize;
686
687                let present = keys
688                    .iter()
689                    .filter(|key| !self.is_key_missing(user_data, key))
690                    .count();
691
692                if present >= minimum {
693                    Ok(Value::Array(vec![]))
694                } else {
695                    let missing: Vec<_> = keys
696                        .iter()
697                        .filter(|key| self.is_key_missing(user_data, key))
698                        .map(|k| Value::String(k.clone()))
699                        .collect();
700                    Ok(Value::Array(missing))
701                }
702            }
703
704            // ========== Logical Utility Operators ==========
705            CompiledLogic::Xor(a_expr, b_expr) => {
706                let a_val =
707                    self.evaluate_with_context(a_expr, user_data, internal_context, depth + 1)?;
708                let b_val =
709                    self.evaluate_with_context(b_expr, user_data, internal_context, depth + 1)?;
710                Ok(Value::Bool(is_truthy(&a_val) ^ is_truthy(&b_val)))
711            }
712            CompiledLogic::IfNull(cond_expr, alt_expr) => {
713                let cond_val =
714                    self.evaluate_with_context(cond_expr, user_data, internal_context, depth + 1)?;
715                if is_null_like(&cond_val) {
716                    self.evaluate_with_context(alt_expr, user_data, internal_context, depth + 1)
717                } else {
718                    Ok(cond_val)
719                }
720            }
721            CompiledLogic::IsEmpty(expr) => {
722                let val =
723                    self.evaluate_with_context(expr, user_data, internal_context, depth + 1)?;
724                let empty = match &val {
725                    Value::Null => true,
726                    Value::String(s) => s.is_empty(),
727                    _ => false,
728                };
729                Ok(Value::Bool(empty))
730            }
731            CompiledLogic::Empty => Ok(Value::String(String::new())),
732
733            // ========== UI Helper Operators ==========
734            CompiledLogic::RangeOptions(min_expr, max_expr) => {
735                let min_val =
736                    self.evaluate_with_context(min_expr, user_data, internal_context, depth + 1)?;
737                let max_val =
738                    self.evaluate_with_context(max_expr, user_data, internal_context, depth + 1)?;
739
740                let min = to_number(&min_val) as i32;
741                let max = to_number(&max_val) as i32;
742
743                if min > max {
744                    return Ok(Value::Array(vec![]));
745                }
746
747                let options: Vec<Value> = (min..=max)
748                    .map(|i| {
749                        serde_json::json!({
750                            "label": i.to_string(),
751                            "value": i.to_string()
752                        })
753                    })
754                    .collect();
755
756                Ok(Value::Array(options))
757            }
758            CompiledLogic::MapOptions(table_expr, label_expr, value_expr) => {
759                let table_val =
760                    self.evaluate_with_context(table_expr, user_data, internal_context, depth + 1)?;
761                let label_val =
762                    self.evaluate_with_context(label_expr, user_data, internal_context, depth + 1)?;
763                let value_val =
764                    self.evaluate_with_context(value_expr, user_data, internal_context, depth + 1)?;
765
766                if let (Value::Array(arr), Value::String(label_field), Value::String(value_field)) =
767                    (&table_val, &label_val, &value_val)
768                {
769                    let options: Vec<Value> = arr
770                        .iter()
771                        .filter_map(|row| {
772                            row.as_object().and_then(|obj| {
773                                Some(create_option(obj.get(label_field)?, obj.get(value_field)?))
774                            })
775                        })
776                        .collect();
777                    Ok(Value::Array(options))
778                } else {
779                    Ok(Value::Array(vec![]))
780                }
781            }
782            CompiledLogic::MapOptionsIf(table_expr, label_expr, value_expr, conditions) => {
783                let table_val =
784                    self.evaluate_with_context(table_expr, user_data, internal_context, depth + 1)?;
785                let label_val =
786                    self.evaluate_with_context(label_expr, user_data, internal_context, depth + 1)?;
787                let value_val =
788                    self.evaluate_with_context(value_expr, user_data, internal_context, depth + 1)?;
789
790                if let (Value::Array(arr), Value::String(label_field), Value::String(value_field)) =
791                    (&table_val, &label_val, &value_val)
792                {
793                    let mut options = Vec::new();
794
795                    for row in arr {
796                        let obj = match row.as_object() {
797                            Some(obj) => obj,
798                            None => continue,
799                        };
800
801                        let mut all_match = true;
802
803                        for condition in conditions {
804                            // Evaluate condition with row as primary context, user_data as fallback
805                            let result =
806                                self.evaluate_with_context(condition, row, user_data, depth + 1)?;
807                            if !is_truthy(&result) {
808                                all_match = false;
809                                break;
810                            }
811                        }
812
813                        if all_match {
814                            if let (Some(label), Some(value)) =
815                                (obj.get(label_field), obj.get(value_field))
816                            {
817                                options.push(create_option(label, value));
818                            }
819                        }
820                    }
821
822                    Ok(Value::Array(options))
823                } else {
824                    Ok(Value::Array(vec![]))
825                }
826            }
827            CompiledLogic::Return(value) => {
828                // Return the raw value as-is without any evaluation
829                Ok(value.as_ref().clone())
830            }
831        }
832    }
833
834    /// Helper for evaluating variable/ref with default (zero-copy)
835    #[inline]
836    fn eval_var_or_default(
837        &self,
838        name: &str,
839        default: &Option<Box<CompiledLogic>>,
840        user_data: &Value,
841        internal_context: &Value,
842        depth: usize,
843    ) -> Result<Value, String> {
844        // Fast path: check active table scope first.
845        // When evaluating a table's own columns (forward/backward pass), Var/Ref nodes
846        // that resolve to the table's own path (e.g. used in MAP/FILTER/REDUCE over self)
847        // must see local_rows, not stale data in scope_data.
848        if !name.is_empty() {
849            // SAFETY: single-threaded (eval_lock), UnsafeCell
850            let scope = unsafe { &*self.table_scope.get() };
851            if let Some(ts) = scope.as_ref() {
852                if name == ts.path {
853                    // SAFETY: local_rows outlives this evaluation frame
854                    let rows = unsafe { &*ts.rows };
855                    return Ok(Value::Array(rows.clone()));
856                }
857            }
858        }
859
860        // Special case: empty name "" refers to root context (user_data only)
861        // For named variables, try internal context first (for $loopIteration, $iteration, etc.)
862        let value = if name.is_empty() {
863            self.get_var(user_data, name)
864        } else {
865            self.get_var(internal_context, name)
866                .or_else(|| self.get_var(user_data, name))
867        };
868        match value {
869            Some(v) if !v.is_null() => Ok(v.clone()), // Only clone the resolved value
870            _ => {
871                if let Some(def) = default {
872                    self.evaluate_with_context(def, user_data, internal_context, depth + 1)
873                } else {
874                    Ok(Value::Null)
875                }
876            }
877        }
878    }
879
880    /// Convert f64 to JSON number
881    #[inline(always)]
882    fn f64_to_json(&self, f: f64) -> Value {
883        helpers::f64_to_json(f, self.config.safe_nan_handling)
884    }
885}
886
887impl Default for Evaluator {
888    fn default() -> Self {
889        Self::new()
890    }
891}