Skip to main content

json_eval_rs/rlogic/
compiled.rs

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