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