Skip to main content

json_eval_rs/rlogic/
compiled.rs

1use crate::jsoneval::path_utils;
2use ahash::AHashMap;
3use serde::Serialize;
4use serde_json::Value;
5
6/// Unique identifier for compiled logic expressions
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
8pub struct LogicId(pub(crate) u64);
9
10/// Compiled JSON Logic expression optimized for fast evaluation
11#[derive(Debug, Clone)]
12pub enum CompiledLogic {
13    // Literal values
14    Null,
15    Bool(bool),
16    Number(f64),
17    String(String),
18    Array(Vec<CompiledLogic>),
19
20    // Variable access
21    Var(String, Option<Box<CompiledLogic>>), // name, default
22    Ref(String, Option<Box<CompiledLogic>>), // JSON Schema reference path, default
23
24    // Logical operators
25    And(Vec<CompiledLogic>),
26    Or(Vec<CompiledLogic>),
27    Not(Box<CompiledLogic>),
28    If(Box<CompiledLogic>, Box<CompiledLogic>, Box<CompiledLogic>), // condition, then, else
29
30    // Comparison operators
31    Equal(Box<CompiledLogic>, Box<CompiledLogic>),
32    StrictEqual(Box<CompiledLogic>, Box<CompiledLogic>),
33    NotEqual(Box<CompiledLogic>, Box<CompiledLogic>),
34    StrictNotEqual(Box<CompiledLogic>, Box<CompiledLogic>),
35    LessThan(Box<CompiledLogic>, Box<CompiledLogic>),
36    LessThanOrEqual(Box<CompiledLogic>, Box<CompiledLogic>),
37    GreaterThan(Box<CompiledLogic>, Box<CompiledLogic>),
38    GreaterThanOrEqual(Box<CompiledLogic>, Box<CompiledLogic>),
39
40    // Arithmetic operators
41    Add(Vec<CompiledLogic>),
42    Subtract(Vec<CompiledLogic>),
43    Multiply(Vec<CompiledLogic>),
44    Divide(Vec<CompiledLogic>),
45    Modulo(Box<CompiledLogic>, Box<CompiledLogic>),
46    Power(Box<CompiledLogic>, Box<CompiledLogic>),
47
48    // Array operators
49    Map(Box<CompiledLogic>, Box<CompiledLogic>), // array, logic
50    Filter(Box<CompiledLogic>, Box<CompiledLogic>), // array, logic
51    Reduce(Box<CompiledLogic>, Box<CompiledLogic>, Box<CompiledLogic>), // array, logic, initial
52    All(Box<CompiledLogic>, Box<CompiledLogic>), // array, logic
53    Some(Box<CompiledLogic>, Box<CompiledLogic>), // array, logic
54    None(Box<CompiledLogic>, Box<CompiledLogic>), // array, logic
55    Merge(Vec<CompiledLogic>),
56    In(Box<CompiledLogic>, Box<CompiledLogic>), // value, array
57
58    // String operators
59    Cat(Vec<CompiledLogic>),
60    Substr(
61        Box<CompiledLogic>,
62        Box<CompiledLogic>,
63        Option<Box<CompiledLogic>>,
64    ), // string, start, length
65
66    // Utility operators
67    Missing(Vec<String>),
68    MissingSome(Box<CompiledLogic>, Vec<String>), // minimum, keys
69
70    // Custom operators - Math
71    Abs(Box<CompiledLogic>),
72    Max(Vec<CompiledLogic>),
73    Min(Vec<CompiledLogic>),
74    Pow(Box<CompiledLogic>, Box<CompiledLogic>),
75    Round(Box<CompiledLogic>, Option<Box<CompiledLogic>>), // value, decimals
76    RoundUp(Box<CompiledLogic>, Option<Box<CompiledLogic>>), // value, decimals
77    RoundDown(Box<CompiledLogic>, Option<Box<CompiledLogic>>), // value, decimals
78    Ceiling(Box<CompiledLogic>, Option<Box<CompiledLogic>>), // value, significance (Excel CEILING)
79    Floor(Box<CompiledLogic>, Option<Box<CompiledLogic>>), // value, significance (Excel FLOOR)
80    Trunc(Box<CompiledLogic>, Option<Box<CompiledLogic>>), // value, decimals (Excel TRUNC)
81    Mround(Box<CompiledLogic>, Box<CompiledLogic>),        // value, multiple (Excel MROUND)
82
83    // Custom operators - String
84    Length(Box<CompiledLogic>),
85    Search(
86        Box<CompiledLogic>,
87        Box<CompiledLogic>,
88        Option<Box<CompiledLogic>>,
89    ), // find, within, start_num
90    Left(Box<CompiledLogic>, Option<Box<CompiledLogic>>), // text, num_chars
91    Right(Box<CompiledLogic>, Option<Box<CompiledLogic>>), // text, num_chars
92    Mid(Box<CompiledLogic>, Box<CompiledLogic>, Box<CompiledLogic>), // text, start, num_chars
93    Len(Box<CompiledLogic>),
94    SplitText(
95        Box<CompiledLogic>,
96        Box<CompiledLogic>,
97        Option<Box<CompiledLogic>>,
98    ), // value, separator, index
99    Concat(Vec<CompiledLogic>),
100    SplitValue(Box<CompiledLogic>, Box<CompiledLogic>), // string, separator
101    StringFormat(
102        Box<CompiledLogic>,
103        Option<Box<CompiledLogic>>,
104        Option<Box<CompiledLogic>>,
105        Option<Box<CompiledLogic>>,
106        Option<Box<CompiledLogic>>,
107    ), // value, decimals, prefix, suffix, thousands_sep
108
109    // Custom operators - Logical
110    Xor(Box<CompiledLogic>, Box<CompiledLogic>),
111    IfNull(Box<CompiledLogic>, Box<CompiledLogic>),
112    IsEmpty(Box<CompiledLogic>),
113    Empty,
114
115    // Custom operators - Date
116    Today,
117    Now,
118    Days(Box<CompiledLogic>, Box<CompiledLogic>), // end_date, start_date
119    Year(Box<CompiledLogic>),
120    Month(Box<CompiledLogic>),
121    Day(Box<CompiledLogic>),
122    Date(Box<CompiledLogic>, Box<CompiledLogic>, Box<CompiledLogic>), // year, month, day
123    DateFormat(Box<CompiledLogic>, Option<Box<CompiledLogic>>),       // date, format
124
125    // Custom operators - Array/Table
126    Sum(
127        Box<CompiledLogic>,
128        Option<Box<CompiledLogic>>,
129        Option<Box<CompiledLogic>>,
130    ), // array/value, optional field name, optional index threshold
131    For(Box<CompiledLogic>, Box<CompiledLogic>, Box<CompiledLogic>), // start, end, logic (with $iteration variable)
132
133    // Complex table operations
134    ValueAt(
135        Box<CompiledLogic>,
136        Box<CompiledLogic>,
137        Option<Box<CompiledLogic>>,
138    ), // table, row_idx, col_name
139    MaxAt(Box<CompiledLogic>, Box<CompiledLogic>), // table, col_name
140    IndexAt(
141        Box<CompiledLogic>,
142        Box<CompiledLogic>,
143        Box<CompiledLogic>,
144        Option<Box<CompiledLogic>>,
145    ), // lookup_val, table, field, range
146    Match(Box<CompiledLogic>, Vec<CompiledLogic>), // table, conditions (pairs of value, field)
147    MatchRange(Box<CompiledLogic>, Vec<CompiledLogic>), // table, conditions (triplets of min_col, max_col, value)
148    Choose(Box<CompiledLogic>, Vec<CompiledLogic>),     // table, conditions (pairs of value, field)
149    FindIndex(Box<CompiledLogic>, Vec<CompiledLogic>),  // table, conditions (complex nested logic)
150
151    // Array operations
152    Multiplies(Vec<CompiledLogic>), // flatten and multiply
153    Divides(Vec<CompiledLogic>),    // flatten and divide
154
155    // Advanced date functions
156    YearFrac(
157        Box<CompiledLogic>,
158        Box<CompiledLogic>,
159        Option<Box<CompiledLogic>>,
160    ), // start_date, end_date, basis
161    DateDif(Box<CompiledLogic>, Box<CompiledLogic>, Box<CompiledLogic>), // start_date, end_date, unit
162
163    // UI helpers
164    RangeOptions(Box<CompiledLogic>, Box<CompiledLogic>), // min, max
165    MapOptions(Box<CompiledLogic>, Box<CompiledLogic>, Box<CompiledLogic>), // table, field_label, field_value
166    MapOptionsIf(
167        Box<CompiledLogic>,
168        Box<CompiledLogic>,
169        Box<CompiledLogic>,
170        Vec<CompiledLogic>,
171    ), // table, field_label, field_value, conditions
172    Return(Box<Value>), // return value as-is (no-op, just returns the raw value)
173}
174
175impl CompiledLogic {
176    /// Compile a JSON Logic expression from JSON Value
177    pub fn compile(logic: &Value) -> Result<Self, String> {
178        match logic {
179            Value::Null => Ok(CompiledLogic::Null),
180            Value::Bool(b) => Ok(CompiledLogic::Bool(*b)),
181            Value::Number(n) => {
182                Ok(CompiledLogic::Number(n.as_f64().unwrap_or(0.0)))
183            }
184            Value::String(s) => Ok(CompiledLogic::String(s.clone())),
185            Value::Array(arr) => {
186                let compiled: Result<Vec<_>, _> = arr.iter().map(Self::compile).collect();
187                Ok(CompiledLogic::Array(compiled?))
188            }
189            Value::Object(obj) => {
190                if obj.is_empty() {
191                    return Ok(CompiledLogic::Null);
192                }
193
194                // Get the operator (first key)
195                let (op, args) = obj.iter().next().unwrap();
196
197                Self::compile_operator(op, args)
198            }
199        }
200    }
201
202    fn compile_operator(op: &str, args: &Value) -> Result<Self, String> {
203        match op {
204            // Variable access
205            "var" => {
206                if let Value::String(name) = args {
207                    // OPTIMIZED: Pre-normalize path during compilation
208                    let normalized = path_utils::normalize_to_json_pointer(name).into_owned();
209                    Ok(CompiledLogic::Var(normalized, None))
210                } else if let Value::Array(arr) = args {
211                    if arr.is_empty() {
212                        return Err("var requires at least one argument".to_string());
213                    }
214                    let name = arr[0].as_str().ok_or("var name must be a string")?;
215                    // OPTIMIZED: Pre-normalize path during compilation
216                    let normalized = path_utils::normalize_to_json_pointer(name).into_owned();
217                    let default = if arr.len() > 1 {
218                        Some(Box::new(Self::compile(&arr[1])?))
219                    } else {
220                        None
221                    };
222                    Ok(CompiledLogic::Var(normalized, default))
223                } else {
224                    Err("var requires string or array".to_string())
225                }
226            }
227            "$ref" | "ref" => {
228                if let Value::String(path) = args {
229                    // OPTIMIZED: Pre-normalize path during compilation
230                    let normalized = path_utils::normalize_to_json_pointer(path).into_owned();
231                    Ok(CompiledLogic::Ref(normalized, None))
232                } else if let Value::Array(arr) = args {
233                    if arr.is_empty() {
234                        return Err("$ref requires at least one argument".to_string());
235                    }
236                    let path = arr[0].as_str().ok_or("$ref path must be a string")?;
237                    // OPTIMIZED: Pre-normalize path during compilation
238                    let normalized = path_utils::normalize_to_json_pointer(path).into_owned();
239                    let default = if arr.len() > 1 {
240                        Some(Box::new(Self::compile(&arr[1])?))
241                    } else {
242                        None
243                    };
244                    Ok(CompiledLogic::Ref(normalized, default))
245                } else {
246                    Err("$ref requires string or array".to_string())
247                }
248            }
249
250            // Logical operators
251            "and" => {
252                let arr = args.as_array().ok_or("and requires array")?;
253                let compiled: Result<Vec<_>, _> = arr.iter().map(Self::compile).collect();
254                let items = compiled?;
255                // OPTIMIZATION: Flatten nested and operations (And(And(a,b),c) -> And(a,b,c))
256                Ok(CompiledLogic::And(Self::flatten_and(items)))
257            }
258            "or" => {
259                let arr = args.as_array().ok_or("or requires array")?;
260                let compiled: Result<Vec<_>, _> = arr.iter().map(Self::compile).collect();
261                let items = compiled?;
262                // OPTIMIZATION: Flatten nested or operations (Or(Or(a,b),c) -> Or(a,b,c))
263                Ok(CompiledLogic::Or(Self::flatten_or(items)))
264            }
265            "!" | "not" => {
266                // Handle both array format [value] and direct value format
267                let value_to_negate = if let Value::Array(arr) = args {
268                    if arr.is_empty() {
269                        return Err("! requires an argument".to_string());
270                    }
271                    &arr[0]
272                } else {
273                    args
274                };
275
276                let inner = Self::compile(value_to_negate)?;
277                // OPTIMIZATION: Eliminate double negation (!(!x) -> x)
278                if let CompiledLogic::Not(inner_expr) = inner {
279                    Ok(*inner_expr)
280                } else {
281                    Ok(CompiledLogic::Not(Box::new(inner)))
282                }
283            }
284            "if" => {
285                let arr = args.as_array().ok_or("if requires array")?;
286                if arr.len() < 3 {
287                    return Err("if requires at least 3 arguments".to_string());
288                }
289                Ok(CompiledLogic::If(
290                    Box::new(Self::compile(&arr[0])?),
291                    Box::new(Self::compile(&arr[1])?),
292                    Box::new(Self::compile(&arr[2])?),
293                ))
294            }
295
296            // Comparison operators
297            "==" => Self::compile_binary(args, |a, b| CompiledLogic::Equal(a, b)),
298            "===" => Self::compile_binary(args, |a, b| CompiledLogic::StrictEqual(a, b)),
299            "!=" => Self::compile_binary(args, |a, b| CompiledLogic::NotEqual(a, b)),
300            "!==" => Self::compile_binary(args, |a, b| CompiledLogic::StrictNotEqual(a, b)),
301            "<" => Self::compile_binary(args, |a, b| CompiledLogic::LessThan(a, b)),
302            "<=" => Self::compile_binary(args, |a, b| CompiledLogic::LessThanOrEqual(a, b)),
303            ">" => Self::compile_binary(args, |a, b| CompiledLogic::GreaterThan(a, b)),
304            ">=" => Self::compile_binary(args, |a, b| CompiledLogic::GreaterThanOrEqual(a, b)),
305
306            // Arithmetic operators
307            "+" => {
308                let arr = args.as_array().ok_or("+ requires array")?;
309                let compiled: Result<Vec<_>, _> = arr.iter().map(Self::compile).collect();
310                let items = compiled?;
311                // OPTIMIZATION: Flatten nested additions (Add(Add(a,b),c) -> Add(a,b,c))
312                Ok(CompiledLogic::Add(Self::flatten_add(items)))
313            }
314            "-" => {
315                let arr = args.as_array().ok_or("- requires array")?;
316                let compiled: Result<Vec<_>, _> = arr.iter().map(Self::compile).collect();
317                Ok(CompiledLogic::Subtract(compiled?))
318            }
319            "*" => {
320                let arr = args.as_array().ok_or("* requires array")?;
321                let compiled: Result<Vec<_>, _> = arr.iter().map(Self::compile).collect();
322                let items = compiled?;
323                // OPTIMIZATION: Flatten nested multiplications
324                Ok(CompiledLogic::Multiply(Self::flatten_multiply(items)))
325            }
326            "/" => {
327                let arr = args.as_array().ok_or("/ requires array")?;
328                let compiled: Result<Vec<_>, _> = arr.iter().map(Self::compile).collect();
329                Ok(CompiledLogic::Divide(compiled?))
330            }
331            "%" => Self::compile_binary(args, |a, b| CompiledLogic::Modulo(a, b)),
332            "^" => Self::compile_binary(args, |a, b| CompiledLogic::Power(a, b)),
333
334            // Array operators
335            "map" => Self::compile_binary(args, |a, b| CompiledLogic::Map(a, b)),
336            "filter" => Self::compile_binary(args, |a, b| CompiledLogic::Filter(a, b)),
337            "reduce" => {
338                let arr = args.as_array().ok_or("reduce requires array")?;
339                if arr.len() < 3 {
340                    return Err("reduce requires 3 arguments".to_string());
341                }
342                Ok(CompiledLogic::Reduce(
343                    Box::new(Self::compile(&arr[0])?),
344                    Box::new(Self::compile(&arr[1])?),
345                    Box::new(Self::compile(&arr[2])?),
346                ))
347            }
348            "all" => Self::compile_binary(args, |a, b| CompiledLogic::All(a, b)),
349            "some" => Self::compile_binary(args, |a, b| CompiledLogic::Some(a, b)),
350            "none" => Self::compile_binary(args, |a, b| CompiledLogic::None(a, b)),
351            "merge" => {
352                let arr = args.as_array().ok_or("merge requires array")?;
353                let compiled: Result<Vec<_>, _> = arr.iter().map(Self::compile).collect();
354                Ok(CompiledLogic::Merge(compiled?))
355            }
356            "in" => Self::compile_binary(args, |a, b| CompiledLogic::In(a, b)),
357
358            // String operators
359            "cat" => {
360                let arr = args.as_array().ok_or("cat requires array")?;
361                let compiled: Result<Vec<_>, _> = arr.iter().map(Self::compile).collect();
362                let items = compiled?;
363                // OPTIMIZATION: Flatten nested concatenations (Cat(Cat(a,b),c) -> Cat(a,b,c))
364                Ok(CompiledLogic::Cat(Self::flatten_cat(items)))
365            }
366            "substr" => {
367                let arr = args.as_array().ok_or("substr requires array")?;
368                if arr.len() < 2 {
369                    return Err("substr requires at least 2 arguments".to_string());
370                }
371                let length = if arr.len() > 2 {
372                    Some(Box::new(Self::compile(&arr[2])?))
373                } else {
374                    None
375                };
376                Ok(CompiledLogic::Substr(
377                    Box::new(Self::compile(&arr[0])?),
378                    Box::new(Self::compile(&arr[1])?),
379                    length,
380                ))
381            }
382
383            // Utility operators
384            "missing" => {
385                let keys = if let Value::Array(arr) = args {
386                    arr.iter()
387                        .map(|v| {
388                            v.as_str()
389                                .ok_or("missing key must be string")
390                                .map(|s| s.to_string())
391                        })
392                        .collect::<Result<Vec<_>, _>>()?
393                } else if let Value::String(s) = args {
394                    vec![s.clone()]
395                } else {
396                    return Err("missing requires string or array".to_string());
397                };
398                Ok(CompiledLogic::Missing(keys))
399            }
400            "missing_some" => {
401                let arr = args.as_array().ok_or("missing_some requires array")?;
402                if arr.len() < 2 {
403                    return Err("missing_some requires at least 2 arguments".to_string());
404                }
405                let minimum = Box::new(Self::compile(&arr[0])?);
406                let keys = if let Value::Array(key_arr) = &arr[1] {
407                    key_arr
408                        .iter()
409                        .map(|v| {
410                            v.as_str()
411                                .ok_or("key must be string")
412                                .map(|s| s.to_string())
413                        })
414                        .collect::<Result<Vec<_>, _>>()?
415                } else {
416                    return Err("missing_some keys must be array".to_string());
417                };
418                Ok(CompiledLogic::MissingSome(minimum, keys))
419            }
420
421            // Custom operators - Math
422            "abs" => {
423                let arg = if let Value::Array(arr) = args {
424                    if arr.is_empty() {
425                        return Err("abs requires at least one argument".to_string());
426                    }
427                    &arr[0]
428                } else {
429                    args
430                };
431                Ok(CompiledLogic::Abs(Box::new(Self::compile(arg)?)))
432            }
433            "max" => {
434                let arr = args.as_array().ok_or("max requires array")?;
435                let compiled: Result<Vec<_>, _> = arr.iter().map(Self::compile).collect();
436                Ok(CompiledLogic::Max(compiled?))
437            }
438            "min" => {
439                let arr = args.as_array().ok_or("min requires array")?;
440                let compiled: Result<Vec<_>, _> = arr.iter().map(Self::compile).collect();
441                Ok(CompiledLogic::Min(compiled?))
442            }
443            "pow" | "**" => Self::compile_binary(args, |a, b| CompiledLogic::Pow(a, b)),
444            "round" | "ROUND" => {
445                if let Value::Array(arr) = args {
446                    let decimals = if arr.len() > 1 {
447                        Some(Box::new(Self::compile(&arr[1])?))
448                    } else {
449                        None
450                    };
451                    Ok(CompiledLogic::Round(
452                        Box::new(Self::compile(&arr[0])?),
453                        decimals,
454                    ))
455                } else {
456                    Ok(CompiledLogic::Round(Box::new(Self::compile(args)?), None))
457                }
458            }
459            "roundup" | "ROUNDUP" => {
460                if let Value::Array(arr) = args {
461                    let decimals = if arr.len() > 1 {
462                        Some(Box::new(Self::compile(&arr[1])?))
463                    } else {
464                        None
465                    };
466                    Ok(CompiledLogic::RoundUp(
467                        Box::new(Self::compile(&arr[0])?),
468                        decimals,
469                    ))
470                } else {
471                    Ok(CompiledLogic::RoundUp(Box::new(Self::compile(args)?), None))
472                }
473            }
474            "rounddown" | "ROUNDDOWN" => {
475                if let Value::Array(arr) = args {
476                    let decimals = if arr.len() > 1 {
477                        Some(Box::new(Self::compile(&arr[1])?))
478                    } else {
479                        None
480                    };
481                    Ok(CompiledLogic::RoundDown(
482                        Box::new(Self::compile(&arr[0])?),
483                        decimals,
484                    ))
485                } else {
486                    Ok(CompiledLogic::RoundDown(
487                        Box::new(Self::compile(args)?),
488                        None,
489                    ))
490                }
491            }
492            "ceiling" | "CEILING" => {
493                if let Value::Array(arr) = args {
494                    let significance = if arr.len() > 1 {
495                        Some(Box::new(Self::compile(&arr[1])?))
496                    } else {
497                        None
498                    };
499                    Ok(CompiledLogic::Ceiling(
500                        Box::new(Self::compile(&arr[0])?),
501                        significance,
502                    ))
503                } else {
504                    Ok(CompiledLogic::Ceiling(Box::new(Self::compile(args)?), None))
505                }
506            }
507            "floor" | "FLOOR" => {
508                if let Value::Array(arr) = args {
509                    let significance = if arr.len() > 1 {
510                        Some(Box::new(Self::compile(&arr[1])?))
511                    } else {
512                        None
513                    };
514                    Ok(CompiledLogic::Floor(
515                        Box::new(Self::compile(&arr[0])?),
516                        significance,
517                    ))
518                } else {
519                    Ok(CompiledLogic::Floor(Box::new(Self::compile(args)?), None))
520                }
521            }
522            "trunc" | "TRUNC" => {
523                if let Value::Array(arr) = args {
524                    let decimals = if arr.len() > 1 {
525                        Some(Box::new(Self::compile(&arr[1])?))
526                    } else {
527                        None
528                    };
529                    Ok(CompiledLogic::Trunc(
530                        Box::new(Self::compile(&arr[0])?),
531                        decimals,
532                    ))
533                } else {
534                    Ok(CompiledLogic::Trunc(Box::new(Self::compile(args)?), None))
535                }
536            }
537            "mround" | "MROUND" => Self::compile_binary(args, |a, b| CompiledLogic::Mround(a, b)),
538
539            // Custom operators - String
540            "length" => {
541                let arg = if let Value::Array(arr) = args {
542                    if arr.is_empty() {
543                        return Err("length requires at least one argument".to_string());
544                    }
545                    &arr[0]
546                } else {
547                    args
548                };
549                Ok(CompiledLogic::Length(Box::new(Self::compile(arg)?)))
550            }
551            "len" | "LEN" => {
552                let arg = if let Value::Array(arr) = args {
553                    if arr.is_empty() {
554                        return Err("len requires at least one argument".to_string());
555                    }
556                    &arr[0]
557                } else {
558                    args
559                };
560                Ok(CompiledLogic::Len(Box::new(Self::compile(arg)?)))
561            }
562            "search" | "SEARCH" => {
563                let arr = args.as_array().ok_or("search requires array")?;
564                if arr.len() < 2 {
565                    return Err("search requires at least 2 arguments".to_string());
566                }
567                let start_num = if arr.len() > 2 {
568                    Some(Box::new(Self::compile(&arr[2])?))
569                } else {
570                    None
571                };
572                Ok(CompiledLogic::Search(
573                    Box::new(Self::compile(&arr[0])?),
574                    Box::new(Self::compile(&arr[1])?),
575                    start_num,
576                ))
577            }
578            "left" | "LEFT" => {
579                if let Value::Array(arr) = args {
580                    let num_chars = if arr.len() > 1 {
581                        Some(Box::new(Self::compile(&arr[1])?))
582                    } else {
583                        None
584                    };
585                    Ok(CompiledLogic::Left(
586                        Box::new(Self::compile(&arr[0])?),
587                        num_chars,
588                    ))
589                } else {
590                    Ok(CompiledLogic::Left(Box::new(Self::compile(args)?), None))
591                }
592            }
593            "right" | "RIGHT" => {
594                if let Value::Array(arr) = args {
595                    let num_chars = if arr.len() > 1 {
596                        Some(Box::new(Self::compile(&arr[1])?))
597                    } else {
598                        None
599                    };
600                    Ok(CompiledLogic::Right(
601                        Box::new(Self::compile(&arr[0])?),
602                        num_chars,
603                    ))
604                } else {
605                    Ok(CompiledLogic::Right(Box::new(Self::compile(args)?), None))
606                }
607            }
608            "mid" | "MID" => {
609                let arr = args.as_array().ok_or("mid requires array")?;
610                if arr.len() < 3 {
611                    return Err("mid requires 3 arguments".to_string());
612                }
613                Ok(CompiledLogic::Mid(
614                    Box::new(Self::compile(&arr[0])?),
615                    Box::new(Self::compile(&arr[1])?),
616                    Box::new(Self::compile(&arr[2])?),
617                ))
618            }
619            "splittext" | "SPLITTEXT" => {
620                let arr = args.as_array().ok_or("splittext requires array")?;
621                if arr.len() < 2 {
622                    return Err("splittext requires at least 2 arguments".to_string());
623                }
624                let index = if arr.len() > 2 {
625                    Some(Box::new(Self::compile(&arr[2])?))
626                } else {
627                    None
628                };
629                Ok(CompiledLogic::SplitText(
630                    Box::new(Self::compile(&arr[0])?),
631                    Box::new(Self::compile(&arr[1])?),
632                    index,
633                ))
634            }
635            "concat" | "CONCAT" => {
636                let arr = args.as_array().ok_or("concat requires array")?;
637                let compiled: Result<Vec<_>, _> = arr.iter().map(Self::compile).collect();
638                Ok(CompiledLogic::Concat(compiled?))
639            }
640            "splitvalue" | "SPLITVALUE" => {
641                Self::compile_binary(args, |a, b| CompiledLogic::SplitValue(a, b))
642            }
643            "stringformat" | "STRINGFORMAT" => {
644                let arr = args.as_array().ok_or("stringformat requires array")?;
645                if arr.is_empty() {
646                    return Err("stringformat requires at least 1 argument".to_string());
647                }
648                let decimals = if arr.len() > 1 {
649                    Some(Box::new(Self::compile(&arr[1])?))
650                } else {
651                    None
652                };
653                let prefix = if arr.len() > 2 {
654                    Some(Box::new(Self::compile(&arr[2])?))
655                } else {
656                    None
657                };
658                let suffix = if arr.len() > 3 {
659                    Some(Box::new(Self::compile(&arr[3])?))
660                } else {
661                    None
662                };
663                let thousands_sep = if arr.len() > 4 {
664                    Some(Box::new(Self::compile(&arr[4])?))
665                } else {
666                    None
667                };
668                Ok(CompiledLogic::StringFormat(
669                    Box::new(Self::compile(&arr[0])?),
670                    decimals,
671                    prefix,
672                    suffix,
673                    thousands_sep,
674                ))
675            }
676
677            // Custom operators - Logical
678            "xor" => Self::compile_binary(args, |a, b| CompiledLogic::Xor(a, b)),
679            "ifnull" | "IFNULL" => Self::compile_binary(args, |a, b| CompiledLogic::IfNull(a, b)),
680            "isempty" | "ISEMPTY" => {
681                let arg = if let Value::Array(arr) = args {
682                    if arr.is_empty() {
683                        return Err("ISEMPTY requires at least one argument".to_string());
684                    }
685                    &arr[0]
686                } else {
687                    args
688                };
689                Ok(CompiledLogic::IsEmpty(Box::new(Self::compile(arg)?)))
690            }
691            "empty" | "EMPTY" => Ok(CompiledLogic::Empty),
692
693            // Custom operators - Date
694            "today" | "TODAY" => Ok(CompiledLogic::Today),
695            "now" | "NOW" => Ok(CompiledLogic::Now),
696            "days" | "DAYS" => Self::compile_binary(args, |a, b| CompiledLogic::Days(a, b)),
697            "year" | "YEAR" => {
698                let arg = if let Value::Array(arr) = args {
699                    if arr.is_empty() {
700                        return Err("year requires at least one argument".to_string());
701                    }
702                    &arr[0]
703                } else {
704                    args
705                };
706                Ok(CompiledLogic::Year(Box::new(Self::compile(arg)?)))
707            }
708            "month" | "MONTH" => {
709                let arg = if let Value::Array(arr) = args {
710                    if arr.is_empty() {
711                        return Err("month requires at least one argument".to_string());
712                    }
713                    &arr[0]
714                } else {
715                    args
716                };
717                Ok(CompiledLogic::Month(Box::new(Self::compile(arg)?)))
718            }
719            "day" | "DAY" => {
720                let arg = if let Value::Array(arr) = args {
721                    if arr.is_empty() {
722                        return Err("day requires at least one argument".to_string());
723                    }
724                    &arr[0]
725                } else {
726                    args
727                };
728                Ok(CompiledLogic::Day(Box::new(Self::compile(arg)?)))
729            }
730            "date" | "DATE" => {
731                let arr = args.as_array().ok_or("date requires array")?;
732                if arr.len() < 3 {
733                    return Err("date requires 3 arguments".to_string());
734                }
735                Ok(CompiledLogic::Date(
736                    Box::new(Self::compile(&arr[0])?),
737                    Box::new(Self::compile(&arr[1])?),
738                    Box::new(Self::compile(&arr[2])?),
739                ))
740            }
741            "dateformat" | "DATEFORMAT" => {
742                if let Value::Array(arr) = args {
743                    if arr.is_empty() {
744                        return Err("dateformat requires at least 1 argument".to_string());
745                    }
746                    let format = if arr.len() > 1 {
747                        Some(Box::new(Self::compile(&arr[1])?))
748                    } else {
749                        None
750                    };
751                    Ok(CompiledLogic::DateFormat(
752                        Box::new(Self::compile(&arr[0])?),
753                        format,
754                    ))
755                } else {
756                    Ok(CompiledLogic::DateFormat(
757                        Box::new(Self::compile(args)?),
758                        None,
759                    ))
760                }
761            }
762
763            // Custom operators - Array/Table
764            "sum" | "SUM" => {
765                if let Value::Array(arr) = args {
766                    if arr.is_empty() {
767                        return Err("sum requires at least 1 argument".to_string());
768                    }
769                    let field = if arr.len() > 1 {
770                        Some(Box::new(Self::compile(&arr[1])?))
771                    } else {
772                        None
773                    };
774                    let threshold = if arr.len() > 2 {
775                        Some(Box::new(Self::compile(&arr[2])?))
776                    } else {
777                        None
778                    };
779                    Ok(CompiledLogic::Sum(
780                        Box::new(Self::compile(&arr[0])?),
781                        field,
782                        threshold,
783                    ))
784                } else {
785                    Ok(CompiledLogic::Sum(
786                        Box::new(Self::compile(args)?),
787                        None,
788                        None,
789                    ))
790                }
791            }
792            "FOR" => {
793                let arr = args.as_array().ok_or("FOR requires array")?;
794                if arr.len() < 3 {
795                    return Err("FOR requires 3 arguments: start, end, logic".to_string());
796                }
797                Ok(CompiledLogic::For(
798                    Box::new(Self::compile(&arr[0])?),
799                    Box::new(Self::compile(&arr[1])?),
800                    Box::new(Self::compile(&arr[2])?),
801                ))
802            }
803
804            // Complex table operations
805            "valueat" | "VALUEAT" => {
806                let arr = args.as_array().ok_or("VALUEAT requires array")?;
807                if arr.len() < 2 {
808                    return Err("VALUEAT requires at least 2 arguments".to_string());
809                }
810                let col_name = if arr.len() > 2 {
811                    Some(Box::new(Self::compile(&arr[2])?))
812                } else {
813                    None
814                };
815                Ok(CompiledLogic::ValueAt(
816                    Box::new(Self::compile(&arr[0])?),
817                    Box::new(Self::compile(&arr[1])?),
818                    col_name,
819                ))
820            }
821            "maxat" | "MAXAT" => Self::compile_binary(args, |a, b| CompiledLogic::MaxAt(a, b)),
822            "indexat" | "INDEXAT" => {
823                let arr = args.as_array().ok_or("INDEXAT requires array")?;
824                if arr.len() < 3 {
825                    return Err("INDEXAT requires at least 3 arguments".to_string());
826                }
827                let range = if arr.len() > 3 {
828                    Some(Box::new(Self::compile(&arr[3])?))
829                } else {
830                    None
831                };
832                Ok(CompiledLogic::IndexAt(
833                    Box::new(Self::compile(&arr[0])?),
834                    Box::new(Self::compile(&arr[1])?),
835                    Box::new(Self::compile(&arr[2])?),
836                    range,
837                ))
838            }
839            "match" | "MATCH" => {
840                let arr = args.as_array().ok_or("MATCH requires array")?;
841                if arr.is_empty() {
842                    return Err("MATCH requires at least 1 argument".to_string());
843                }
844                let table = Box::new(Self::compile(&arr[0])?);
845                let conditions: Result<Vec<_>, _> = arr[1..].iter().map(Self::compile).collect();
846                Ok(CompiledLogic::Match(table, conditions?))
847            }
848            "matchrange" | "MATCHRANGE" => {
849                let arr = args.as_array().ok_or("MATCHRANGE requires array")?;
850                if arr.is_empty() {
851                    return Err("MATCHRANGE requires at least 1 argument".to_string());
852                }
853                let table = Box::new(Self::compile(&arr[0])?);
854                let conditions: Result<Vec<_>, _> = arr[1..].iter().map(Self::compile).collect();
855                Ok(CompiledLogic::MatchRange(table, conditions?))
856            }
857            "choose" | "CHOOSE" => {
858                let arr = args.as_array().ok_or("CHOOSE requires array")?;
859                if arr.is_empty() {
860                    return Err("CHOOSE requires at least 1 argument".to_string());
861                }
862                let table = Box::new(Self::compile(&arr[0])?);
863                let conditions: Result<Vec<_>, _> = arr[1..].iter().map(Self::compile).collect();
864                Ok(CompiledLogic::Choose(table, conditions?))
865            }
866            "findindex" | "FINDINDEX" => {
867                let arr = args.as_array().ok_or("FINDINDEX requires array")?;
868                if arr.len() < 2 {
869                    return Err("FINDINDEX requires at least 2 arguments".to_string());
870                }
871                let table = Box::new(Self::compile(&arr[0])?);
872                // CRITICAL: Convert string literals to var references in conditions
873                // This allows ergonomic syntax: "INSAGE" instead of {"var": "INSAGE"}
874                let conditions: Result<Vec<_>, _> = arr[1..]
875                    .iter()
876                    .map(|cond| Self::compile(&Self::preprocess_table_condition(cond)))
877                    .collect();
878                Ok(CompiledLogic::FindIndex(table, conditions?))
879            }
880
881            // Array operations
882            "MULTIPLIES" => {
883                let arr = args.as_array().ok_or("MULTIPLIES requires array")?;
884                let compiled: Result<Vec<_>, _> = arr.iter().map(Self::compile).collect();
885                Ok(CompiledLogic::Multiplies(compiled?))
886            }
887            "DIVIDES" => {
888                let arr = args.as_array().ok_or("DIVIDES requires array")?;
889                let compiled: Result<Vec<_>, _> = arr.iter().map(Self::compile).collect();
890                Ok(CompiledLogic::Divides(compiled?))
891            }
892
893            // Advanced date functions
894            "YEARFRAC" => {
895                let arr = args.as_array().ok_or("YEARFRAC requires array")?;
896                if arr.len() < 2 {
897                    return Err("YEARFRAC requires at least 2 arguments".to_string());
898                }
899                let basis = if arr.len() > 2 {
900                    Some(Box::new(Self::compile(&arr[2])?))
901                } else {
902                    None
903                };
904                Ok(CompiledLogic::YearFrac(
905                    Box::new(Self::compile(&arr[0])?),
906                    Box::new(Self::compile(&arr[1])?),
907                    basis,
908                ))
909            }
910            "DATEDIF" => {
911                let arr = args.as_array().ok_or("DATEDIF requires array")?;
912                if arr.len() < 3 {
913                    return Err("DATEDIF requires 3 arguments".to_string());
914                }
915                Ok(CompiledLogic::DateDif(
916                    Box::new(Self::compile(&arr[0])?),
917                    Box::new(Self::compile(&arr[1])?),
918                    Box::new(Self::compile(&arr[2])?),
919                ))
920            }
921
922            // UI helpers
923            "RANGEOPTIONS" => Self::compile_binary(args, |a, b| CompiledLogic::RangeOptions(a, b)),
924            "MAPOPTIONS" => {
925                let arr = args.as_array().ok_or("MAPOPTIONS requires array")?;
926                if arr.len() < 3 {
927                    return Err("MAPOPTIONS requires 3 arguments".to_string());
928                }
929                Ok(CompiledLogic::MapOptions(
930                    Box::new(Self::compile(&arr[0])?),
931                    Box::new(Self::compile(&arr[1])?),
932                    Box::new(Self::compile(&arr[2])?),
933                ))
934            }
935            "MAPOPTIONSIF" => {
936                let arr = args.as_array().ok_or("MAPOPTIONSIF requires array")?;
937                if arr.len() < 4 {
938                    return Err("MAPOPTIONSIF requires at least 4 arguments".to_string());
939                }
940                let table = Box::new(Self::compile(&arr[0])?);
941                let field_label = Box::new(Self::compile(&arr[1])?);
942                let field_value = Box::new(Self::compile(&arr[2])?);
943
944                let condition_args = &arr[3..];
945                let mut conditions = Vec::new();
946
947                let mut i = 0;
948                while i < condition_args.len() {
949                    let arg = &condition_args[i];
950
951                    // Check if the current argument is an array of [value, operator, field]
952                    if let Some(arg_arr) = arg.as_array() {
953                        if arg_arr.len() == 3 && arg_arr[1].is_string() {
954                            let is_comparison = matches!(
955                                arg_arr[1].as_str().unwrap(),
956                                "==" | "!=" | "===" | "!==" | "<" | ">" | "<=" | ">="
957                            );
958                            if is_comparison {
959                                let value = &arg_arr[0];
960                                let operator = arg_arr[1].as_str().unwrap();
961                                let field = &arg_arr[2];
962
963                                let field_var = if let Value::String(f) = field {
964                                    serde_json::json!({"var": f})
965                                } else {
966                                    field.clone()
967                                };
968
969                                let comparison = serde_json::json!({
970                                    operator: [value.clone(), field_var]
971                                });
972
973                                conditions.push(Self::compile(&comparison)?);
974                                i += 1;
975                                continue;
976                            }
977                        }
978                    }
979
980                    // Look for flat triplet syntax: value, operator, field -> {operator: [value, {var: field}]}
981                    if i + 2 < condition_args.len() {
982                        let value = &condition_args[i];
983                        let operator = &condition_args[i + 1];
984                        let field = &condition_args[i + 2];
985
986                        if let Value::String(op) = operator {
987                            let is_comparison = matches!(
988                                op.as_str(),
989                                "==" | "!=" | "===" | "!==" | "<" | ">" | "<=" | ">="
990                            );
991
992                            if is_comparison {
993                                let field_var = if let Value::String(f) = field {
994                                    serde_json::json!({"var": f})
995                                } else {
996                                    field.clone()
997                                };
998
999                                let comparison = serde_json::json!({
1000                                    op: [value.clone(), field_var]
1001                                });
1002
1003                                conditions.push(Self::compile(&comparison)?);
1004                                i += 3;
1005                                continue;
1006                            }
1007                        }
1008                    }
1009
1010                    // Handle any remaining individual conditions
1011                    conditions.push(Self::compile(&Self::preprocess_table_condition(
1012                        &condition_args[i],
1013                    ))?);
1014                    i += 1;
1015                }
1016
1017                Ok(CompiledLogic::MapOptionsIf(
1018                    table,
1019                    field_label,
1020                    field_value,
1021                    conditions,
1022                ))
1023            }
1024            "return" => Ok(CompiledLogic::Return(Box::new(args.clone()))),
1025
1026            _ => Err(format!("Unknown operator: {}", op)),
1027        }
1028    }
1029
1030    fn compile_binary<F>(args: &Value, f: F) -> Result<Self, String>
1031    where
1032        F: FnOnce(Box<CompiledLogic>, Box<CompiledLogic>) -> CompiledLogic,
1033    {
1034        let arr = args.as_array().ok_or("Binary operator requires array")?;
1035        if arr.len() != 2 {
1036            return Err("Binary operator requires exactly 2 arguments".to_string());
1037        }
1038        Ok(f(
1039            Box::new(Self::compile(&arr[0])?),
1040            Box::new(Self::compile(&arr[1])?),
1041        ))
1042    }
1043
1044    /// Preprocess table condition to convert string literals to var references
1045    /// This allows ergonomic syntax in FINDINDEX/MATCH/CHOOSE conditions
1046    ///
1047    /// Handles formats:
1048    /// - Comparison triplets: ["==", value, "col"] -> {"==": [value, {"var": "col"}]}
1049    /// - Logical operators: ["&&", cond1, cond2] -> {"and": [cond1, cond2]}
1050    /// - String field names: "col" -> {"var": "col"}
1051    fn preprocess_table_condition(value: &Value) -> Value {
1052        match value {
1053            Value::String(s) => {
1054                // Convert standalone strings to var references
1055                serde_json::json!({"var": s})
1056            }
1057            Value::Array(arr) => {
1058                // Check if this is an operator in array shorthand format
1059                if !arr.is_empty() {
1060                    if let Some(op_str) = arr[0].as_str() {
1061                        // Check for comparison operators: [op, value, col]
1062                        let is_comparison = matches!(
1063                            op_str,
1064                            "==" | "!=" | "===" | "!==" | "<" | ">" | "<=" | ">="
1065                        );
1066
1067                        if is_comparison && arr.len() >= 3 {
1068                            // Comparison triplet: [op, value, col] -> {op: [col_var, value]}
1069                            // Evaluates as: row[col] op value
1070                            // DON'T preprocess the value (2nd arg) - keep it as-is
1071                            let value_arg = arr[1].clone();
1072
1073                            // Only convert the column name (3rd arg) to var reference if it's a string
1074                            let col_arg = if let Value::String(col) = &arr[2] {
1075                                // Convert column name string to var reference
1076                                serde_json::json!({"var": col})
1077                            } else {
1078                                // If it's not a string, preprocess it (could be nested expression)
1079                                Self::preprocess_table_condition(&arr[2])
1080                            };
1081
1082                            // Order matters: {op: [col_var, value]} means row[col] op value
1083                            let mut obj = serde_json::Map::new();
1084                            obj.insert(op_str.to_string(), Value::Array(vec![col_arg, value_arg]));
1085                            return Value::Object(obj);
1086                        }
1087
1088                        // Check for logical operators: [op, arg1, arg2, ...]
1089                        let canonical_op = match op_str {
1090                            "&&" => Some("and"),
1091                            "||" => Some("or"),
1092                            "and" | "or" | "!" | "not" | "if" => Some(op_str),
1093                            _ => None,
1094                        };
1095
1096                        if let Some(op_name) = canonical_op {
1097                            // Convert ["op", arg1, arg2, ...] to {"op": [arg1, arg2, ...]}
1098                            let args: Vec<Value> = arr[1..]
1099                                .iter()
1100                                .map(Self::preprocess_table_condition)
1101                                .collect();
1102                            let mut obj = serde_json::Map::new();
1103                            obj.insert(op_name.to_string(), Value::Array(args));
1104                            return Value::Object(obj);
1105                        }
1106                    }
1107                }
1108                // Regular array - recursively process elements
1109                Value::Array(arr.iter().map(Self::preprocess_table_condition).collect())
1110            }
1111            Value::Object(obj) => {
1112                // Recursively process object values, but preserve operators
1113                let mut new_obj = serde_json::Map::new();
1114                for (key, val) in obj {
1115                    // Don't convert strings inside $ref, var, or other special operators
1116                    if key == "$ref" || key == "ref" || key == "var" {
1117                        new_obj.insert(key.clone(), val.clone());
1118                    } else {
1119                        new_obj.insert(key.clone(), Self::preprocess_table_condition(val));
1120                    }
1121                }
1122                Value::Object(new_obj)
1123            }
1124            _ => value.clone(),
1125        }
1126    }
1127
1128    /// Check if this is a simple reference that doesn't need caching
1129    pub fn is_simple_ref(&self) -> bool {
1130        matches!(
1131            self,
1132            CompiledLogic::Ref(_, None) | CompiledLogic::Var(_, None)
1133        )
1134    }
1135
1136    /// Extract all variable names referenced in this logic
1137    pub fn referenced_vars(&self) -> Vec<String> {
1138        let mut vars = Vec::new();
1139        self.collect_vars(&mut vars);
1140        vars.sort();
1141        vars.dedup();
1142        vars
1143    }
1144
1145    /// Flatten nested And operations only
1146    fn flatten_and(items: Vec<CompiledLogic>) -> Vec<CompiledLogic> {
1147        let mut flattened = Vec::new();
1148        for item in items {
1149            match item {
1150                CompiledLogic::And(nested) => {
1151                    // Recursively flatten nested And operations
1152                    flattened.extend(Self::flatten_and(nested));
1153                }
1154                _ => flattened.push(item),
1155            }
1156        }
1157        flattened
1158    }
1159
1160    /// Flatten nested Or operations only
1161    fn flatten_or(items: Vec<CompiledLogic>) -> Vec<CompiledLogic> {
1162        let mut flattened = Vec::new();
1163        for item in items {
1164            match item {
1165                CompiledLogic::Or(nested) => {
1166                    // Recursively flatten nested Or operations
1167                    flattened.extend(Self::flatten_or(nested));
1168                }
1169                _ => flattened.push(item),
1170            }
1171        }
1172        flattened
1173    }
1174
1175    /// Flatten nested Add operations only
1176    fn flatten_add(items: Vec<CompiledLogic>) -> Vec<CompiledLogic> {
1177        let mut flattened = Vec::new();
1178        for item in items {
1179            match item {
1180                CompiledLogic::Add(nested) => {
1181                    // Recursively flatten nested Adds
1182                    flattened.extend(Self::flatten_add(nested));
1183                }
1184                _ => flattened.push(item),
1185            }
1186        }
1187        flattened
1188    }
1189
1190    /// Flatten nested Multiply operations only
1191    fn flatten_multiply(items: Vec<CompiledLogic>) -> Vec<CompiledLogic> {
1192        let mut flattened = Vec::new();
1193        for item in items {
1194            match item {
1195                CompiledLogic::Multiply(nested) => {
1196                    // Recursively flatten nested Multiplies
1197                    flattened.extend(Self::flatten_multiply(nested));
1198                }
1199                _ => flattened.push(item),
1200            }
1201        }
1202        flattened
1203    }
1204
1205    /// Flatten nested Cat (concatenation) operations
1206    /// Combines nested Cat operations into a single flat operation
1207    fn flatten_cat(items: Vec<CompiledLogic>) -> Vec<CompiledLogic> {
1208        let mut flattened = Vec::new();
1209        for item in items {
1210            match &item {
1211                CompiledLogic::Cat(nested) => {
1212                    // Recursively flatten nested Cat operations
1213                    flattened.extend(Self::flatten_cat(nested.clone()));
1214                }
1215                _ => flattened.push(item),
1216            }
1217        }
1218        flattened
1219    }
1220
1221    /// Check if this logic contains forward references (e.g., VALUEAT with $iteration + N where N > 0)
1222    /// Returns true if it references future iterations in a table
1223    pub fn has_forward_reference(&self) -> bool {
1224        let result = self.check_forward_reference();
1225        result
1226    }
1227
1228    fn check_forward_reference(&self) -> bool {
1229        match self {
1230            // VALUEAT with $iteration arithmetic
1231            CompiledLogic::ValueAt(table, idx_logic, col_name) => {
1232                // Check if index contains $iteration + positive_number
1233                let has_fwd = idx_logic.contains_iteration_plus_positive();
1234                if has_fwd {
1235                    return true;
1236                }
1237                // Recursively check all parameters
1238                let table_fwd = table.check_forward_reference();
1239                let idx_fwd = idx_logic.check_forward_reference();
1240                let col_fwd = col_name
1241                    .as_ref()
1242                    .map(|c| c.check_forward_reference())
1243                    .unwrap_or(false);
1244                table_fwd || idx_fwd || col_fwd
1245            }
1246            // Recursively check compound operations
1247            CompiledLogic::Array(arr) => arr.iter().any(|item| item.check_forward_reference()),
1248            CompiledLogic::And(items)
1249            | CompiledLogic::Or(items)
1250            | CompiledLogic::Add(items)
1251            | CompiledLogic::Subtract(items)
1252            | CompiledLogic::Multiply(items)
1253            | CompiledLogic::Divide(items)
1254            | CompiledLogic::Merge(items)
1255            | CompiledLogic::Cat(items)
1256            | CompiledLogic::Max(items)
1257            | CompiledLogic::Min(items)
1258            | CompiledLogic::Concat(items)
1259            | CompiledLogic::Multiplies(items)
1260            | CompiledLogic::Divides(items) => {
1261                items.iter().any(|item| item.check_forward_reference())
1262            }
1263            CompiledLogic::Not(a)
1264            | CompiledLogic::Abs(a)
1265            | CompiledLogic::Length(a)
1266            | CompiledLogic::Len(a)
1267            | CompiledLogic::IsEmpty(a)
1268            | CompiledLogic::Year(a)
1269            | CompiledLogic::Month(a)
1270            | CompiledLogic::Day(a) => a.check_forward_reference(),
1271            CompiledLogic::Round(a, decimals)
1272            | CompiledLogic::RoundUp(a, decimals)
1273            | CompiledLogic::RoundDown(a, decimals)
1274            | CompiledLogic::Ceiling(a, decimals)
1275            | CompiledLogic::Floor(a, decimals)
1276            | CompiledLogic::Trunc(a, decimals)
1277            | CompiledLogic::DateFormat(a, decimals) => {
1278                a.check_forward_reference()
1279                    || decimals
1280                        .as_ref()
1281                        .map_or(false, |d| d.check_forward_reference())
1282            }
1283            CompiledLogic::StringFormat(a, decimals, prefix, suffix, sep) => {
1284                a.check_forward_reference()
1285                    || decimals
1286                        .as_ref()
1287                        .map_or(false, |d| d.check_forward_reference())
1288                    || prefix
1289                        .as_ref()
1290                        .map_or(false, |p| p.check_forward_reference())
1291                    || suffix
1292                        .as_ref()
1293                        .map_or(false, |s| s.check_forward_reference())
1294                    || sep.as_ref().map_or(false, |s| s.check_forward_reference())
1295            }
1296            CompiledLogic::Mround(a, b) => {
1297                a.check_forward_reference() || b.check_forward_reference()
1298            }
1299            CompiledLogic::Return(_) => false, // Raw values don't have forward references
1300            CompiledLogic::If(cond, then_val, else_val) => {
1301                cond.check_forward_reference()
1302                    || then_val.check_forward_reference()
1303                    || else_val.check_forward_reference()
1304            }
1305            CompiledLogic::Equal(a, b)
1306            | CompiledLogic::StrictEqual(a, b)
1307            | CompiledLogic::NotEqual(a, b)
1308            | CompiledLogic::StrictNotEqual(a, b)
1309            | CompiledLogic::LessThan(a, b)
1310            | CompiledLogic::LessThanOrEqual(a, b)
1311            | CompiledLogic::GreaterThan(a, b)
1312            | CompiledLogic::GreaterThanOrEqual(a, b)
1313            | CompiledLogic::Modulo(a, b)
1314            | CompiledLogic::Power(a, b)
1315            | CompiledLogic::Map(a, b)
1316            | CompiledLogic::Filter(a, b)
1317            | CompiledLogic::All(a, b)
1318            | CompiledLogic::Some(a, b)
1319            | CompiledLogic::None(a, b)
1320            | CompiledLogic::In(a, b)
1321            | CompiledLogic::Pow(a, b)
1322            | CompiledLogic::Xor(a, b)
1323            | CompiledLogic::IfNull(a, b)
1324            | CompiledLogic::Days(a, b)
1325            | CompiledLogic::SplitValue(a, b)
1326            | CompiledLogic::MaxAt(a, b)
1327            | CompiledLogic::RangeOptions(a, b) => {
1328                a.check_forward_reference() || b.check_forward_reference()
1329            }
1330            CompiledLogic::Reduce(a, b, c)
1331            | CompiledLogic::Mid(a, b, c)
1332            | CompiledLogic::Date(a, b, c)
1333            | CompiledLogic::DateDif(a, b, c)
1334            | CompiledLogic::MapOptions(a, b, c)
1335            | CompiledLogic::For(a, b, c) => {
1336                a.check_forward_reference()
1337                    || b.check_forward_reference()
1338                    || c.check_forward_reference()
1339            }
1340            CompiledLogic::Substr(s, start, len)
1341            | CompiledLogic::Search(s, start, len)
1342            | CompiledLogic::SplitText(s, start, len)
1343            | CompiledLogic::YearFrac(s, start, len) => {
1344                s.check_forward_reference()
1345                    || start.check_forward_reference()
1346                    || len
1347                        .as_ref()
1348                        .map(|l| l.check_forward_reference())
1349                        .unwrap_or(false)
1350            }
1351            CompiledLogic::Left(a, opt) | CompiledLogic::Right(a, opt) => {
1352                a.check_forward_reference()
1353                    || opt
1354                        .as_ref()
1355                        .map(|o| o.check_forward_reference())
1356                        .unwrap_or(false)
1357            }
1358            CompiledLogic::Sum(a, opt1, opt2) => {
1359                a.check_forward_reference()
1360                    || opt1
1361                        .as_ref()
1362                        .map(|o| o.check_forward_reference())
1363                        .unwrap_or(false)
1364                    || opt2
1365                        .as_ref()
1366                        .map(|o| o.check_forward_reference())
1367                        .unwrap_or(false)
1368            }
1369            CompiledLogic::IndexAt(a, b, c, opt) => {
1370                a.check_forward_reference()
1371                    || b.check_forward_reference()
1372                    || c.check_forward_reference()
1373                    || opt
1374                        .as_ref()
1375                        .map(|o| o.check_forward_reference())
1376                        .unwrap_or(false)
1377            }
1378            CompiledLogic::Match(table, conds)
1379            | CompiledLogic::MatchRange(table, conds)
1380            | CompiledLogic::Choose(table, conds)
1381            | CompiledLogic::FindIndex(table, conds) => {
1382                table.check_forward_reference() || conds.iter().any(|c| c.check_forward_reference())
1383            }
1384            CompiledLogic::MapOptionsIf(table, label, value, conds) => {
1385                table.check_forward_reference()
1386                    || label.check_forward_reference()
1387                    || value.check_forward_reference()
1388                    || conds.iter().any(|c| c.check_forward_reference())
1389            }
1390            CompiledLogic::MissingSome(min, _) => min.check_forward_reference(),
1391            _ => false,
1392        }
1393    }
1394
1395    /// Check if logic contains $iteration + positive_number pattern
1396    fn contains_iteration_plus_positive(&self) -> bool {
1397        match self {
1398            CompiledLogic::Add(items) => {
1399                // Check if one operand references $iteration and another is a positive number literal
1400                let has_iteration = items
1401                    .iter()
1402                    .any(|item| item.referenced_vars().iter().any(|v| v == "$iteration"));
1403
1404                let has_positive = items.iter().any(|item| match item {
1405                    CompiledLogic::Number(f) => *f > 0.0,
1406                    _ => false,
1407                });
1408
1409                let result = has_iteration && has_positive;
1410                result
1411            }
1412            _ => false,
1413        }
1414    }
1415
1416    /// Normalize JSON Schema reference path to dot notation
1417    /// Handles: #/schema/path, #/properties/field, /properties/field, field.path
1418    /// Trims /properties/ and .properties. segments
1419    fn normalize_ref_path(path: &str) -> String {
1420        let mut normalized = path.to_string();
1421
1422        // Remove leading #/ if present
1423        if normalized.starts_with("#/") {
1424            normalized = normalized[2..].to_string();
1425        } else if normalized.starts_with('/') {
1426            normalized = normalized[1..].to_string();
1427        }
1428
1429        // Replace / with . for JSON pointer notation
1430        normalized = normalized.replace('/', ".");
1431
1432        // Remove /properties/ or .properties. segments
1433        normalized = normalized.replace("properties.", "");
1434        normalized = normalized.replace(".properties.", ".");
1435
1436        // Clean up any double dots
1437        while normalized.contains("..") {
1438            normalized = normalized.replace("..", ".");
1439        }
1440
1441        // Remove leading/trailing dots
1442        normalized = normalized.trim_matches('.').to_string();
1443
1444        normalized
1445    }
1446
1447    pub fn collect_vars(&self, vars: &mut Vec<String>) {
1448        match self {
1449            CompiledLogic::Var(name, default) => {
1450                vars.push(name.clone());
1451                if let Some(def) = default {
1452                    def.collect_vars(vars);
1453                }
1454            }
1455            CompiledLogic::Ref(path, default) => {
1456                // Normalize the path and add it
1457                vars.push(Self::normalize_ref_path(path));
1458                if let Some(def) = default {
1459                    def.collect_vars(vars);
1460                }
1461            }
1462            CompiledLogic::Array(arr) => {
1463                for item in arr {
1464                    item.collect_vars(vars);
1465                }
1466            }
1467            CompiledLogic::And(items)
1468            | CompiledLogic::Or(items)
1469            | CompiledLogic::Add(items)
1470            | CompiledLogic::Subtract(items)
1471            | CompiledLogic::Multiply(items)
1472            | CompiledLogic::Divide(items)
1473            | CompiledLogic::Merge(items)
1474            | CompiledLogic::Cat(items)
1475            | CompiledLogic::Max(items)
1476            | CompiledLogic::Min(items)
1477            | CompiledLogic::Concat(items) => {
1478                for item in items {
1479                    item.collect_vars(vars);
1480                }
1481            }
1482            CompiledLogic::Not(a)
1483            | CompiledLogic::Abs(a)
1484            | CompiledLogic::Length(a)
1485            | CompiledLogic::Len(a)
1486            | CompiledLogic::IsEmpty(a)
1487            | CompiledLogic::Year(a)
1488            | CompiledLogic::Month(a)
1489            | CompiledLogic::Day(a) => {
1490                a.collect_vars(vars);
1491            }
1492            CompiledLogic::Round(a, decimals)
1493            | CompiledLogic::RoundUp(a, decimals)
1494            | CompiledLogic::RoundDown(a, decimals)
1495            | CompiledLogic::Ceiling(a, decimals)
1496            | CompiledLogic::Floor(a, decimals)
1497            | CompiledLogic::Trunc(a, decimals)
1498            | CompiledLogic::DateFormat(a, decimals) => {
1499                a.collect_vars(vars);
1500                if let Some(d) = decimals {
1501                    d.collect_vars(vars);
1502                }
1503            }
1504            CompiledLogic::StringFormat(a, decimals, prefix, suffix, sep) => {
1505                a.collect_vars(vars);
1506                if let Some(d) = decimals {
1507                    d.collect_vars(vars);
1508                }
1509                if let Some(p) = prefix {
1510                    p.collect_vars(vars);
1511                }
1512                if let Some(s) = suffix {
1513                    s.collect_vars(vars);
1514                }
1515                if let Some(s) = sep {
1516                    s.collect_vars(vars);
1517                }
1518            }
1519            CompiledLogic::Mround(a, b) => {
1520                a.collect_vars(vars);
1521                b.collect_vars(vars);
1522            }
1523            CompiledLogic::Return(_) => {} // Raw values don't contain vars
1524            CompiledLogic::If(cond, then_val, else_val) => {
1525                cond.collect_vars(vars);
1526                then_val.collect_vars(vars);
1527                else_val.collect_vars(vars);
1528            }
1529            CompiledLogic::Equal(a, b)
1530            | CompiledLogic::StrictEqual(a, b)
1531            | CompiledLogic::NotEqual(a, b)
1532            | CompiledLogic::StrictNotEqual(a, b)
1533            | CompiledLogic::LessThan(a, b)
1534            | CompiledLogic::LessThanOrEqual(a, b)
1535            | CompiledLogic::GreaterThan(a, b)
1536            | CompiledLogic::GreaterThanOrEqual(a, b)
1537            | CompiledLogic::Modulo(a, b)
1538            | CompiledLogic::Power(a, b)
1539            | CompiledLogic::Map(a, b)
1540            | CompiledLogic::Filter(a, b)
1541            | CompiledLogic::All(a, b)
1542            | CompiledLogic::Some(a, b)
1543            | CompiledLogic::None(a, b)
1544            | CompiledLogic::In(a, b)
1545            | CompiledLogic::Pow(a, b)
1546            | CompiledLogic::Xor(a, b)
1547            | CompiledLogic::IfNull(a, b)
1548            | CompiledLogic::Days(a, b)
1549            | CompiledLogic::SplitValue(a, b)
1550            | CompiledLogic::MaxAt(a, b)
1551            | CompiledLogic::RangeOptions(a, b) => {
1552                a.collect_vars(vars);
1553                b.collect_vars(vars);
1554            }
1555            CompiledLogic::Reduce(a, b, c)
1556            | CompiledLogic::Mid(a, b, c)
1557            | CompiledLogic::Date(a, b, c)
1558            | CompiledLogic::DateDif(a, b, c)
1559            | CompiledLogic::MapOptions(a, b, c)
1560            | CompiledLogic::For(a, b, c) => {
1561                a.collect_vars(vars);
1562                b.collect_vars(vars);
1563                c.collect_vars(vars);
1564            }
1565            CompiledLogic::Substr(s, start, len)
1566            | CompiledLogic::Search(s, start, len)
1567            | CompiledLogic::SplitText(s, start, len)
1568            | CompiledLogic::YearFrac(s, start, len) => {
1569                s.collect_vars(vars);
1570                start.collect_vars(vars);
1571                if let Some(l) = len {
1572                    l.collect_vars(vars);
1573                }
1574            }
1575            CompiledLogic::Left(a, opt)
1576            | CompiledLogic::Right(a, opt)
1577            | CompiledLogic::ValueAt(a, _, opt) => {
1578                a.collect_vars(vars);
1579                if let Some(o) = opt {
1580                    o.collect_vars(vars);
1581                }
1582            }
1583            CompiledLogic::Sum(a, opt1, opt2) => {
1584                a.collect_vars(vars);
1585                if let Some(o) = opt1 {
1586                    o.collect_vars(vars);
1587                }
1588                if let Some(o) = opt2 {
1589                    o.collect_vars(vars);
1590                }
1591            }
1592            CompiledLogic::IndexAt(a, b, c, opt) => {
1593                a.collect_vars(vars);
1594                b.collect_vars(vars);
1595                c.collect_vars(vars);
1596                if let Some(o) = opt {
1597                    o.collect_vars(vars);
1598                }
1599            }
1600            CompiledLogic::Match(table, conds)
1601            | CompiledLogic::MatchRange(table, conds)
1602            | CompiledLogic::Choose(table, conds)
1603            | CompiledLogic::FindIndex(table, conds) => {
1604                table.collect_vars(vars);
1605                for cond in conds {
1606                    cond.collect_vars(vars);
1607                }
1608            }
1609            CompiledLogic::Multiplies(items) | CompiledLogic::Divides(items) => {
1610                for item in items {
1611                    item.collect_vars(vars);
1612                }
1613            }
1614            CompiledLogic::MapOptionsIf(table, label, value, conds) => {
1615                table.collect_vars(vars);
1616                label.collect_vars(vars);
1617                value.collect_vars(vars);
1618                for cond in conds {
1619                    cond.collect_vars(vars);
1620                }
1621            }
1622            CompiledLogic::MissingSome(min, _) => {
1623                min.collect_vars(vars);
1624            }
1625            _ => {}
1626        }
1627    }
1628}
1629
1630/// Storage for compiled logic expressions with dependency tracking
1631///
1632/// This store uses the global compiled logic cache to avoid recompiling
1633/// the same logic across different instances. Each instance maintains
1634/// its own local ID mapping to the global storage.
1635pub struct CompiledLogicStore {
1636    next_id: u64,
1637    store: AHashMap<LogicId, CompiledLogic>,
1638    dependencies: AHashMap<LogicId, Vec<String>>,
1639}
1640
1641impl CompiledLogicStore {
1642    pub fn new() -> Self {
1643        Self {
1644            next_id: 0,
1645            store: AHashMap::default(),
1646            dependencies: AHashMap::default(),
1647        }
1648    }
1649
1650    /// Compile and store a JSON Logic expression
1651    ///
1652    /// Uses global storage to avoid recompiling the same logic across instances.
1653    /// The logic is compiled once globally and reused, with this instance maintaining
1654    /// its own local ID for tracking dependencies.
1655    pub fn compile(&mut self, logic: &Value) -> Result<LogicId, String> {
1656        // Use global storage - compiles once and caches globally
1657        let _global_id = super::compiled_logic_store::compile_logic_value(logic)?;
1658
1659        // Get the compiled logic from global store (O(1) lookup)
1660        let compiled = super::compiled_logic_store::get_compiled_logic(_global_id)
1661            .ok_or_else(|| "Failed to retrieve compiled logic from global store".to_string())?;
1662
1663        // Track dependencies locally
1664        let deps = compiled.referenced_vars();
1665
1666        // Assign local ID for this instance
1667        let id = LogicId(self.next_id);
1668        self.next_id += 1;
1669
1670        // Store locally with instance-specific ID
1671        self.store.insert(id, compiled);
1672        self.dependencies.insert(id, deps);
1673
1674        Ok(id)
1675    }
1676
1677    /// Get a compiled logic by ID
1678    pub fn get(&self, id: &LogicId) -> Option<&CompiledLogic> {
1679        self.store.get(id)
1680    }
1681
1682    /// Remove a compiled logic by ID
1683    pub fn remove(&mut self, id: &LogicId) -> Option<CompiledLogic> {
1684        self.dependencies.remove(id);
1685        self.store.remove(id)
1686    }
1687
1688    /// Get dependencies for a logic ID
1689    pub fn get_dependencies(&self, id: &LogicId) -> Option<&[String]> {
1690        self.dependencies.get(id).map(|v| v.as_slice())
1691    }
1692}
1693
1694impl Default for CompiledLogicStore {
1695    fn default() -> Self {
1696        Self::new()
1697    }
1698}
1699
1700#[cfg(test)]
1701mod tests {
1702    use super::*;
1703    use serde_json::json;
1704
1705    fn is_ok(json_value: serde_json::Value) -> bool {
1706        CompiledLogic::compile(&json_value).is_ok()
1707    }
1708
1709    #[test]
1710    fn test_compile_literals() {
1711        assert!(matches!(CompiledLogic::compile(&json!(null)).unwrap(), CompiledLogic::Null));
1712        assert!(matches!(CompiledLogic::compile(&json!(true)).unwrap(), CompiledLogic::Bool(true)));
1713        assert!(matches!(CompiledLogic::compile(&json!(false)).unwrap(), CompiledLogic::Bool(false)));
1714        assert!(matches!(CompiledLogic::compile(&json!(42.5)).unwrap(), CompiledLogic::Number(n) if n == 42.5));
1715        assert!(matches!(CompiledLogic::compile(&json!("hello")).unwrap(), CompiledLogic::String(ref s) if s == "hello"));
1716        assert!(matches!(CompiledLogic::compile(&json!([1, 2, 3])).unwrap(), CompiledLogic::Array(_)));
1717    }
1718
1719    #[test]
1720    fn test_compile_variables() {
1721        assert!(is_ok(json!({"var": "a"})));
1722        assert!(is_ok(json!({"var": ["a", 1]})));
1723        assert!(!is_ok(json!({"var": {}}))); // Invalid var form
1724        assert!(is_ok(json!({"$ref": "#/path"})));
1725    }
1726
1727    #[test]
1728    fn test_compile_logical() {
1729        assert!(is_ok(json!({"and": [true, false]})));
1730        assert!(is_ok(json!({"or": [true, false]})));
1731        assert!(is_ok(json!({"not": [true]})));
1732        assert!(is_ok(json!({"!": [true]})));
1733        assert!(is_ok(json!({"if": [true, 1, 0]})));
1734        assert!(is_ok(json!({"xor": [true, false]})));
1735        assert!(is_ok(json!({"ifnull": [{"var": "a"}, 0]})));
1736        
1737        // Errors
1738        assert!(!is_ok(json!({"if": []})));
1739        assert!(!is_ok(json!({"and": true}))); 
1740    }
1741
1742    #[test]
1743    fn test_compile_comparison() {
1744        assert!(is_ok(json!({"==": [1, 1]})));
1745        assert!(is_ok(json!({"===": [1, 1]})));
1746        assert!(is_ok(json!({"!=": [1, 2]})));
1747        assert!(is_ok(json!({"!==": [1, 2]})));
1748        assert!(is_ok(json!({">": [2, 1]})));
1749        assert!(is_ok(json!({">=": [2, 2]})));
1750        assert!(is_ok(json!({"<": [1, 2]})));
1751        assert!(is_ok(json!({"<=": [2, 2]})));
1752
1753        // Error
1754        assert!(!is_ok(json!({"==": 1})));
1755    }
1756
1757    #[test]
1758    fn test_compile_arithmetic() {
1759        assert!(is_ok(json!({"+": [1, 2]})));
1760        assert!(is_ok(json!({"-": [1, 2]})));
1761        assert!(is_ok(json!({"*": [1, 2]})));
1762        assert!(is_ok(json!({"/": [1, 2]})));
1763        assert!(is_ok(json!({"%": [1, 2]})));
1764        assert!(is_ok(json!({"pow": [2, 3]})));
1765
1766        // Error
1767        assert!(!is_ok(json!({"-": "a"}))); // Needs array of arguments
1768    }
1769
1770    #[test]
1771    fn test_compile_array_ops() {
1772        assert!(is_ok(json!({"map": [[1, 2], {"+": [{"var": ""}, 1]}]})));
1773        assert!(is_ok(json!({"filter": [[1, 2], {">": [{"var": ""}, 1]}]})));
1774        assert!(is_ok(json!({"reduce": [[1, 2], {"+": [{"var": "current"}, {"var": "accumulator"}]}, 0]})));
1775        assert!(is_ok(json!({"all": [[true, false], {"var": ""}]})));
1776        assert!(is_ok(json!({"some": [[true, false], {"var": ""}]})));
1777        assert!(is_ok(json!({"none": [[true, false], {"var": ""}]})));
1778        assert!(is_ok(json!({"merge": [[1], [2]]})));
1779        assert!(is_ok(json!({"in": [1, [1, 2, 3]]})));
1780        assert!(is_ok(json!({"sum": [{"var": "data"}, "a", 0]})));
1781    }
1782
1783    #[test]
1784    fn test_compile_string_ops() {
1785        assert!(is_ok(json!({"cat": ["a", "b"]})));
1786        assert!(is_ok(json!({"substr": ["hello", 0, 2]})));
1787        assert!(is_ok(json!({"search": ["a", "abc", 0]})));
1788        assert!(is_ok(json!({"SPLITTEXT": ["a,b", ",", 0]})));
1789        assert!(is_ok(json!({"left": ["abc", 2]})));
1790        assert!(is_ok(json!({"right": ["abc", 2]})));
1791
1792        // Error
1793        assert!(!is_ok(json!({"substr": "hello"})));
1794    }
1795
1796    #[test]
1797    fn test_compile_math_ops() {
1798        assert!(is_ok(json!({"max": [1, 2, 3]})));
1799        assert!(is_ok(json!({"min": [1, 2, 3]})));
1800        assert!(is_ok(json!({"abs": -1})));
1801        assert!(is_ok(json!({"round": [1.234, 2]})));
1802        assert!(is_ok(json!({"roundup": [1.234, 2]})));
1803        assert!(is_ok(json!({"rounddown": [1.234, 2]})));
1804        assert!(is_ok(json!({"ceiling": [1.234, 1]})));
1805        assert!(is_ok(json!({"floor": [1.234, 1]})));
1806        assert!(is_ok(json!({"trunc": [1.234, 2]})));
1807    }
1808
1809    #[test]
1810    fn test_compile_date_ops() {
1811        assert!(is_ok(json!({"today": []})));
1812        assert!(is_ok(json!({"date": [2024, 12, 1]})));
1813        assert!(is_ok(json!({"days": ["2024-12-02", "2024-12-01"]})));
1814        assert!(is_ok(json!({"year": ["2024-12-01"]})));
1815        assert!(is_ok(json!({"month": ["2024-12-01"]})));
1816        assert!(is_ok(json!({"day": ["2024-12-01"]})));
1817    }
1818
1819    #[test]
1820    fn test_compile_table_ops() {
1821        assert!(is_ok(json!({"VALUEAT": [{"var": "t"}, 0, "a"]})));
1822        assert!(is_ok(json!({"INDEXAT": ["a", {"var": "t"}, "b", true]})));
1823        assert!(is_ok(json!({"MAXAT": [{"var": "t"}, "a"]})));
1824        assert!(is_ok(json!({"MATCH": [{"var": "t"}, "==", 1, "a"]})));
1825        assert!(is_ok(json!({"MATCHRANGE": [{"var": "t"}, "==", 1, "a"]})));
1826        assert!(is_ok(json!({"CHOOSE": [{"var": "t"}, "==", 1, "a"]})));
1827        assert!(is_ok(json!({"FINDINDEX": [{"var": "t"}, "==", 1, "a"]})));
1828        assert!(is_ok(json!({"MAPOPTIONS": [{"var": "t"}, "a", "b"]})));
1829        assert!(is_ok(json!({"MAPOPTIONSIF": [{"var": "t"}, "a", "b", [true, "==", "c"]]})));
1830        
1831        // Error cases
1832        assert!(!is_ok(json!({"VALUEAT": [[{"a": 1}], 0]})));
1833        assert!(!is_ok(json!({"MAPOPTIONSIF": [[], "a"]})));
1834    }
1835
1836    #[test]
1837    fn test_compile_util_ops() {
1838        assert!(is_ok(json!({"missing": ["a", "b"]})));
1839        assert!(is_ok(json!({"missing_some": [1, ["a", "b"]]})));
1840        assert!(is_ok(json!({"empty": []})));
1841        assert!(is_ok(json!({"return": 5})));
1842        assert!(is_ok(json!({"RANGEOPTIONS": [1, 5]})));
1843    }
1844
1845    #[test]
1846    fn test_compile_unknown() {
1847        assert!(!is_ok(json!({"UNKNOWN_OP": [1, 2]})));
1848        assert!(!is_ok(json!({"UNKNOWN_NON_ARRAY": 1})));
1849    }
1850}