1use crate::jsoneval::path_utils;
2use rapidhash::RapidHashMap;
3use serde::Serialize;
4use serde_json::Value;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
8pub struct LogicId(pub(crate) u64);
9
10#[derive(Debug, Clone)]
12pub enum CompiledLogic {
13 Null,
15 Bool(bool),
16 Number(f64),
17 String(String),
18 Array(Vec<CompiledLogic>),
19
20 Var(String, Option<Box<CompiledLogic>>), Ref(String, Option<Box<CompiledLogic>>), And(Vec<CompiledLogic>),
26 Or(Vec<CompiledLogic>),
27 Not(Box<CompiledLogic>),
28 If(Box<CompiledLogic>, Box<CompiledLogic>, Box<CompiledLogic>), 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 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 Map(Box<CompiledLogic>, Box<CompiledLogic>), Filter(Box<CompiledLogic>, Box<CompiledLogic>), Reduce(Box<CompiledLogic>, Box<CompiledLogic>, Box<CompiledLogic>), All(Box<CompiledLogic>, Box<CompiledLogic>), Some(Box<CompiledLogic>, Box<CompiledLogic>), None(Box<CompiledLogic>, Box<CompiledLogic>), Merge(Vec<CompiledLogic>),
56 In(Box<CompiledLogic>, Box<CompiledLogic>), Cat(Vec<CompiledLogic>),
60 Substr(
61 Box<CompiledLogic>,
62 Box<CompiledLogic>,
63 Option<Box<CompiledLogic>>,
64 ), Missing(Vec<String>),
68 MissingSome(Box<CompiledLogic>, Vec<String>), Abs(Box<CompiledLogic>),
72 Max(Vec<CompiledLogic>),
73 Min(Vec<CompiledLogic>),
74 Pow(Box<CompiledLogic>, Box<CompiledLogic>),
75 Round(Box<CompiledLogic>, Option<Box<CompiledLogic>>), RoundUp(Box<CompiledLogic>, Option<Box<CompiledLogic>>), RoundDown(Box<CompiledLogic>, Option<Box<CompiledLogic>>), Ceiling(Box<CompiledLogic>, Option<Box<CompiledLogic>>), Floor(Box<CompiledLogic>, Option<Box<CompiledLogic>>), Trunc(Box<CompiledLogic>, Option<Box<CompiledLogic>>), Mround(Box<CompiledLogic>, Box<CompiledLogic>), Length(Box<CompiledLogic>),
85 Search(
86 Box<CompiledLogic>,
87 Box<CompiledLogic>,
88 Option<Box<CompiledLogic>>,
89 ), Left(Box<CompiledLogic>, Option<Box<CompiledLogic>>), Right(Box<CompiledLogic>, Option<Box<CompiledLogic>>), Mid(Box<CompiledLogic>, Box<CompiledLogic>, Box<CompiledLogic>), Len(Box<CompiledLogic>),
94 SplitText(
95 Box<CompiledLogic>,
96 Box<CompiledLogic>,
97 Option<Box<CompiledLogic>>,
98 ), Concat(Vec<CompiledLogic>),
100 SplitValue(Box<CompiledLogic>, Box<CompiledLogic>), StringFormat(
102 Box<CompiledLogic>,
103 Option<Box<CompiledLogic>>,
104 Option<Box<CompiledLogic>>,
105 Option<Box<CompiledLogic>>,
106 Option<Box<CompiledLogic>>,
107 ), Xor(Box<CompiledLogic>, Box<CompiledLogic>),
111 IfNull(Box<CompiledLogic>, Box<CompiledLogic>),
112 IsEmpty(Box<CompiledLogic>),
113 Empty,
114
115 Today,
117 Now,
118 Days(Box<CompiledLogic>, Box<CompiledLogic>), Year(Box<CompiledLogic>),
120 Month(Box<CompiledLogic>),
121 Day(Box<CompiledLogic>),
122 Date(Box<CompiledLogic>, Box<CompiledLogic>, Box<CompiledLogic>), DateFormat(Box<CompiledLogic>, Option<Box<CompiledLogic>>), Sum(
127 Box<CompiledLogic>,
128 Option<Box<CompiledLogic>>,
129 Option<Box<CompiledLogic>>,
130 ), For(Box<CompiledLogic>, Box<CompiledLogic>, Box<CompiledLogic>), ValueAt(
135 Box<CompiledLogic>,
136 Box<CompiledLogic>,
137 Option<Box<CompiledLogic>>,
138 ), MaxAt(Box<CompiledLogic>, Box<CompiledLogic>), IndexAt(
141 Box<CompiledLogic>,
142 Box<CompiledLogic>,
143 Box<CompiledLogic>,
144 Option<Box<CompiledLogic>>,
145 ), Match(Box<CompiledLogic>, Vec<CompiledLogic>), MatchRange(Box<CompiledLogic>, Vec<CompiledLogic>), Choose(Box<CompiledLogic>, Vec<CompiledLogic>), FindIndex(Box<CompiledLogic>, Vec<CompiledLogic>), Multiplies(Vec<CompiledLogic>), Divides(Vec<CompiledLogic>), YearFrac(
157 Box<CompiledLogic>,
158 Box<CompiledLogic>,
159 Option<Box<CompiledLogic>>,
160 ), DateDif(Box<CompiledLogic>, Box<CompiledLogic>, Box<CompiledLogic>), RangeOptions(Box<CompiledLogic>, Box<CompiledLogic>), MapOptions(Box<CompiledLogic>, Box<CompiledLogic>, Box<CompiledLogic>), MapOptionsIf(
167 Box<CompiledLogic>,
168 Box<CompiledLogic>,
169 Box<CompiledLogic>,
170 Vec<CompiledLogic>,
171 ), Return(Box<Value>), }
174
175impl CompiledLogic {
176 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 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 "var" => {
204 if let Value::String(name) = args {
205 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 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 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 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 "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 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 Ok(CompiledLogic::Or(Self::flatten_or(items)))
262 }
263 "!" | "not" => {
264 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 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 "==" => 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 "+" => {
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 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 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 "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 "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 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 "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 "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 "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 "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 "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 "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 "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 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 "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 "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 "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 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 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 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 fn preprocess_table_condition(value: &Value) -> Value {
1050 match value {
1051 Value::String(s) => {
1052 serde_json::json!({"var": s})
1054 }
1055 Value::Array(arr) => {
1056 if !arr.is_empty() {
1058 if let Some(op_str) = arr[0].as_str() {
1059 let is_comparison = matches!(
1061 op_str,
1062 "==" | "!=" | "===" | "!==" | "<" | ">" | "<=" | ">="
1063 );
1064
1065 if is_comparison && arr.len() >= 3 {
1066 let value_arg = arr[1].clone();
1070
1071 let col_arg = if let Value::String(col) = &arr[2] {
1073 serde_json::json!({"var": col})
1075 } else {
1076 Self::preprocess_table_condition(&arr[2])
1078 };
1079
1080 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 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 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 Value::Array(arr.iter().map(Self::preprocess_table_condition).collect())
1108 }
1109 Value::Object(obj) => {
1110 let mut new_obj = serde_json::Map::new();
1112 for (key, val) in obj {
1113 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 pub fn is_simple_ref(&self) -> bool {
1128 matches!(
1129 self,
1130 CompiledLogic::Ref(_, None) | CompiledLogic::Var(_, None)
1131 )
1132 }
1133
1134 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 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 flattened.extend(Self::flatten_and(nested));
1151 }
1152 _ => flattened.push(item),
1153 }
1154 }
1155 flattened
1156 }
1157
1158 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 flattened.extend(Self::flatten_or(nested));
1166 }
1167 _ => flattened.push(item),
1168 }
1169 }
1170 flattened
1171 }
1172
1173 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 flattened.extend(Self::flatten_add(nested));
1181 }
1182 _ => flattened.push(item),
1183 }
1184 }
1185 flattened
1186 }
1187
1188 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 flattened.extend(Self::flatten_multiply(nested));
1196 }
1197 _ => flattened.push(item),
1198 }
1199 }
1200 flattened
1201 }
1202
1203 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 flattened.extend(Self::flatten_cat(nested.clone()));
1212 }
1213 _ => flattened.push(item),
1214 }
1215 }
1216 flattened
1217 }
1218
1219 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 CompiledLogic::ValueAt(table, idx_logic, col_name) => {
1230 let has_fwd = idx_logic.contains_iteration_plus_positive();
1232 if has_fwd {
1233 return true;
1234 }
1235 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 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, 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 fn contains_iteration_plus_positive(&self) -> bool {
1395 match self {
1396 CompiledLogic::Add(items) => {
1397 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 fn normalize_ref_path(path: &str) -> String {
1418 let mut normalized = path.to_string();
1419
1420 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 normalized = normalized.replace('/', ".");
1429
1430 normalized = normalized.replace("properties.", "");
1432 normalized = normalized.replace(".properties.", ".");
1433
1434 while normalized.contains("..") {
1436 normalized = normalized.replace("..", ".");
1437 }
1438
1439 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 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(_) => {} 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) | CompiledLogic::Right(a, opt) => {
1574 a.collect_vars(vars);
1575 if let Some(o) = opt {
1576 o.collect_vars(vars);
1577 }
1578 }
1579 CompiledLogic::ValueAt(a, idx, opt) => {
1580 a.collect_vars(vars);
1581 idx.collect_vars(vars);
1582 if let Some(o) = opt {
1583 o.collect_vars(vars);
1584 }
1585 }
1586 CompiledLogic::Sum(a, opt1, opt2) => {
1587 a.collect_vars(vars);
1588 if let Some(o) = opt1 {
1589 o.collect_vars(vars);
1590 }
1591 if let Some(o) = opt2 {
1592 o.collect_vars(vars);
1593 }
1594 }
1595 CompiledLogic::IndexAt(a, b, c, opt) => {
1596 a.collect_vars(vars);
1597 b.collect_vars(vars);
1598 c.collect_vars(vars);
1599 if let Some(o) = opt {
1600 o.collect_vars(vars);
1601 }
1602 }
1603 CompiledLogic::Match(table, conds)
1604 | CompiledLogic::MatchRange(table, conds)
1605 | CompiledLogic::Choose(table, conds)
1606 | CompiledLogic::FindIndex(table, conds) => {
1607 table.collect_vars(vars);
1608 for cond in conds {
1609 cond.collect_vars(vars);
1610 }
1611 }
1612 CompiledLogic::Multiplies(items) | CompiledLogic::Divides(items) => {
1613 for item in items {
1614 item.collect_vars(vars);
1615 }
1616 }
1617 CompiledLogic::MapOptionsIf(table, label, value, conds) => {
1618 table.collect_vars(vars);
1619 label.collect_vars(vars);
1620 value.collect_vars(vars);
1621 for cond in conds {
1622 cond.collect_vars(vars);
1623 }
1624 }
1625 CompiledLogic::MissingSome(min, _) => {
1626 min.collect_vars(vars);
1627 }
1628 _ => {}
1629 }
1630 }
1631
1632 pub fn has_table_variant_refs(&self, table_path: &str, table_no_hash: &str) -> bool {
1634 match self {
1635 CompiledLogic::Null
1636 | CompiledLogic::Bool(_)
1637 | CompiledLogic::Number(_)
1638 | CompiledLogic::String(_)
1639 | CompiledLogic::Empty => false,
1640
1641 CompiledLogic::Today | CompiledLogic::Now => true,
1642
1643 CompiledLogic::Var(name, default) => {
1644 if name.starts_with('$')
1645 || name.starts_with("/$")
1646 || name == table_path
1647 || name == table_no_hash
1648 {
1649 return true;
1650 }
1651 default.as_ref().map_or(false, |d| {
1652 d.has_table_variant_refs(table_path, table_no_hash)
1653 })
1654 }
1655
1656 CompiledLogic::Ref(path, default) => {
1657 if path.contains('$')
1658 || path == table_path
1659 || path == table_no_hash
1660 || path.strip_prefix('#').unwrap_or(path) == table_no_hash
1661 {
1662 return true;
1663 }
1664 default.as_ref().map_or(false, |d| {
1665 d.has_table_variant_refs(table_path, table_no_hash)
1666 })
1667 }
1668
1669 CompiledLogic::ValueAt(table, row_idx, col_name) => {
1670 let table_name = table_no_hash.rsplit('/').next().unwrap_or(table_no_hash);
1671 let is_self_table = match table.as_ref() {
1672 CompiledLogic::Var(name, _) | CompiledLogic::Ref(name, _) => {
1673 name == table_path
1674 || name == table_no_hash
1675 || name.strip_prefix('#').unwrap_or(name) == table_no_hash
1676 || (!table_name.is_empty()
1677 && (name == table_name
1678 || name.ends_with(&format!("/{}", table_name))
1679 || name.ends_with(&format!(".{}", table_name))))
1680 }
1681 _ => false,
1682 };
1683 if is_self_table {
1684 return true;
1685 }
1686 table.has_table_variant_refs(table_path, table_no_hash)
1687 || row_idx.has_table_variant_refs(table_path, table_no_hash)
1688 || col_name.as_ref().map_or(false, |c| {
1689 c.has_table_variant_refs(table_path, table_no_hash)
1690 })
1691 }
1692
1693 CompiledLogic::For(_, _, _) => true,
1694
1695 CompiledLogic::Array(arr)
1696 | CompiledLogic::And(arr)
1697 | CompiledLogic::Or(arr)
1698 | CompiledLogic::Add(arr)
1699 | CompiledLogic::Subtract(arr)
1700 | CompiledLogic::Multiply(arr)
1701 | CompiledLogic::Divide(arr)
1702 | CompiledLogic::Merge(arr)
1703 | CompiledLogic::Cat(arr)
1704 | CompiledLogic::Max(arr)
1705 | CompiledLogic::Min(arr)
1706 | CompiledLogic::Concat(arr)
1707 | CompiledLogic::Multiplies(arr)
1708 | CompiledLogic::Divides(arr) => arr
1709 .iter()
1710 .any(|item| item.has_table_variant_refs(table_path, table_no_hash)),
1711
1712 CompiledLogic::Not(a)
1713 | CompiledLogic::Abs(a)
1714 | CompiledLogic::Length(a)
1715 | CompiledLogic::Len(a)
1716 | CompiledLogic::IsEmpty(a)
1717 | CompiledLogic::Year(a)
1718 | CompiledLogic::Month(a)
1719 | CompiledLogic::Day(a) => a.has_table_variant_refs(table_path, table_no_hash),
1720
1721 CompiledLogic::Equal(a, b)
1722 | CompiledLogic::StrictEqual(a, b)
1723 | CompiledLogic::NotEqual(a, b)
1724 | CompiledLogic::StrictNotEqual(a, b)
1725 | CompiledLogic::LessThan(a, b)
1726 | CompiledLogic::LessThanOrEqual(a, b)
1727 | CompiledLogic::GreaterThan(a, b)
1728 | CompiledLogic::GreaterThanOrEqual(a, b)
1729 | CompiledLogic::Modulo(a, b)
1730 | CompiledLogic::Power(a, b)
1731 | CompiledLogic::Pow(a, b)
1732 | CompiledLogic::Mround(a, b)
1733 | CompiledLogic::SplitValue(a, b)
1734 | CompiledLogic::Xor(a, b)
1735 | CompiledLogic::IfNull(a, b)
1736 | CompiledLogic::Days(a, b)
1737 | CompiledLogic::In(a, b)
1738 | CompiledLogic::MaxAt(a, b) => {
1739 a.has_table_variant_refs(table_path, table_no_hash)
1740 || b.has_table_variant_refs(table_path, table_no_hash)
1741 }
1742
1743 CompiledLogic::If(c, t, e)
1744 | CompiledLogic::Date(c, t, e)
1745 | CompiledLogic::Mid(c, t, e) => {
1746 c.has_table_variant_refs(table_path, table_no_hash)
1747 || t.has_table_variant_refs(table_path, table_no_hash)
1748 || e.has_table_variant_refs(table_path, table_no_hash)
1749 }
1750
1751 CompiledLogic::Round(a, opt)
1752 | CompiledLogic::RoundUp(a, opt)
1753 | CompiledLogic::RoundDown(a, opt)
1754 | CompiledLogic::Ceiling(a, opt)
1755 | CompiledLogic::Floor(a, opt)
1756 | CompiledLogic::Trunc(a, opt)
1757 | CompiledLogic::Left(a, opt)
1758 | CompiledLogic::Right(a, opt)
1759 | CompiledLogic::DateFormat(a, opt) => {
1760 a.has_table_variant_refs(table_path, table_no_hash)
1761 || opt.as_ref().map_or(false, |d| {
1762 d.has_table_variant_refs(table_path, table_no_hash)
1763 })
1764 }
1765
1766 _ => true,
1767 }
1768 }
1769
1770 pub fn fold_table_invariants(
1772 &self,
1773 evaluator: &crate::rlogic::Evaluator,
1774 user_data: &serde_json::Value,
1775 internal_context: &serde_json::Value,
1776 table_path: &str,
1777 table_no_hash: &str,
1778 ) -> CompiledLogic {
1779 if !self.has_table_variant_refs(table_path, table_no_hash) {
1781 match self {
1782 CompiledLogic::Null
1783 | CompiledLogic::Bool(_)
1784 | CompiledLogic::Number(_)
1785 | CompiledLogic::String(_)
1786 | CompiledLogic::Empty
1787 | CompiledLogic::Var(_, _)
1788 | CompiledLogic::Ref(_, _) => return self.clone(),
1789 _ => {
1790 if let Ok(val) =
1791 evaluator.evaluate_with_internal_context(self, user_data, internal_context)
1792 {
1793 match val {
1794 serde_json::Value::Number(n) => {
1795 if let Some(f) = n.as_f64() {
1796 return CompiledLogic::Number(f);
1797 }
1798 }
1799 serde_json::Value::Bool(b) => return CompiledLogic::Bool(b),
1800 serde_json::Value::String(s) => return CompiledLogic::String(s),
1801 _ => {} }
1803 }
1804 }
1805 }
1806 }
1807
1808 match self {
1810 CompiledLogic::Add(items) => CompiledLogic::Add(
1811 items
1812 .iter()
1813 .map(|item| {
1814 item.fold_table_invariants(
1815 evaluator,
1816 user_data,
1817 internal_context,
1818 table_path,
1819 table_no_hash,
1820 )
1821 })
1822 .collect(),
1823 ),
1824 CompiledLogic::Subtract(items) => CompiledLogic::Subtract(
1825 items
1826 .iter()
1827 .map(|item| {
1828 item.fold_table_invariants(
1829 evaluator,
1830 user_data,
1831 internal_context,
1832 table_path,
1833 table_no_hash,
1834 )
1835 })
1836 .collect(),
1837 ),
1838 CompiledLogic::Multiply(items) => CompiledLogic::Multiply(
1839 items
1840 .iter()
1841 .map(|item| {
1842 item.fold_table_invariants(
1843 evaluator,
1844 user_data,
1845 internal_context,
1846 table_path,
1847 table_no_hash,
1848 )
1849 })
1850 .collect(),
1851 ),
1852 CompiledLogic::Divide(items) => CompiledLogic::Divide(
1853 items
1854 .iter()
1855 .map(|item| {
1856 item.fold_table_invariants(
1857 evaluator,
1858 user_data,
1859 internal_context,
1860 table_path,
1861 table_no_hash,
1862 )
1863 })
1864 .collect(),
1865 ),
1866 CompiledLogic::If(cond, then_expr, else_expr) => {
1867 if !cond.has_table_variant_refs(table_path, table_no_hash) {
1869 if let Ok(val) =
1870 evaluator.evaluate_with_internal_context(cond, user_data, internal_context)
1871 {
1872 let is_truthy = crate::rlogic::evaluator::helpers::is_truthy(&val);
1873 if is_truthy {
1874 return then_expr.fold_table_invariants(
1875 evaluator,
1876 user_data,
1877 internal_context,
1878 table_path,
1879 table_no_hash,
1880 );
1881 } else {
1882 return else_expr.fold_table_invariants(
1883 evaluator,
1884 user_data,
1885 internal_context,
1886 table_path,
1887 table_no_hash,
1888 );
1889 }
1890 }
1891 }
1892 CompiledLogic::If(
1893 Box::new(cond.fold_table_invariants(
1894 evaluator,
1895 user_data,
1896 internal_context,
1897 table_path,
1898 table_no_hash,
1899 )),
1900 Box::new(then_expr.fold_table_invariants(
1901 evaluator,
1902 user_data,
1903 internal_context,
1904 table_path,
1905 table_no_hash,
1906 )),
1907 Box::new(else_expr.fold_table_invariants(
1908 evaluator,
1909 user_data,
1910 internal_context,
1911 table_path,
1912 table_no_hash,
1913 )),
1914 )
1915 }
1916 CompiledLogic::Round(expr, decimals) => CompiledLogic::Round(
1917 Box::new(expr.fold_table_invariants(
1918 evaluator,
1919 user_data,
1920 internal_context,
1921 table_path,
1922 table_no_hash,
1923 )),
1924 decimals.as_ref().map(|d| {
1925 Box::new(d.fold_table_invariants(
1926 evaluator,
1927 user_data,
1928 internal_context,
1929 table_path,
1930 table_no_hash,
1931 ))
1932 }),
1933 ),
1934 CompiledLogic::RoundUp(expr, decimals) => CompiledLogic::RoundUp(
1935 Box::new(expr.fold_table_invariants(
1936 evaluator,
1937 user_data,
1938 internal_context,
1939 table_path,
1940 table_no_hash,
1941 )),
1942 decimals.as_ref().map(|d| {
1943 Box::new(d.fold_table_invariants(
1944 evaluator,
1945 user_data,
1946 internal_context,
1947 table_path,
1948 table_no_hash,
1949 ))
1950 }),
1951 ),
1952 CompiledLogic::RoundDown(expr, decimals) => CompiledLogic::RoundDown(
1953 Box::new(expr.fold_table_invariants(
1954 evaluator,
1955 user_data,
1956 internal_context,
1957 table_path,
1958 table_no_hash,
1959 )),
1960 decimals.as_ref().map(|d| {
1961 Box::new(d.fold_table_invariants(
1962 evaluator,
1963 user_data,
1964 internal_context,
1965 table_path,
1966 table_no_hash,
1967 ))
1968 }),
1969 ),
1970 CompiledLogic::Min(items) => CompiledLogic::Min(
1971 items
1972 .iter()
1973 .map(|item| {
1974 item.fold_table_invariants(
1975 evaluator,
1976 user_data,
1977 internal_context,
1978 table_path,
1979 table_no_hash,
1980 )
1981 })
1982 .collect(),
1983 ),
1984 CompiledLogic::Max(items) => CompiledLogic::Max(
1985 items
1986 .iter()
1987 .map(|item| {
1988 item.fold_table_invariants(
1989 evaluator,
1990 user_data,
1991 internal_context,
1992 table_path,
1993 table_no_hash,
1994 )
1995 })
1996 .collect(),
1997 ),
1998 CompiledLogic::ValueAt(table, row_idx, col_name) => CompiledLogic::ValueAt(
1999 table.clone(),
2000 Box::new(row_idx.fold_table_invariants(
2001 evaluator,
2002 user_data,
2003 internal_context,
2004 table_path,
2005 table_no_hash,
2006 )),
2007 col_name.as_ref().map(|c| {
2008 Box::new(c.fold_table_invariants(
2009 evaluator,
2010 user_data,
2011 internal_context,
2012 table_path,
2013 table_no_hash,
2014 ))
2015 }),
2016 ),
2017 _ => self.clone(),
2018 }
2019 }
2020}
2021
2022pub struct CompiledLogicStore {
2028 next_id: u64,
2029 store: RapidHashMap<LogicId, CompiledLogic>,
2030 dependencies: RapidHashMap<LogicId, Vec<String>>,
2031}
2032
2033impl CompiledLogicStore {
2034 pub fn new() -> Self {
2035 Self {
2036 next_id: 0,
2037 store: RapidHashMap::default(),
2038 dependencies: RapidHashMap::default(),
2039 }
2040 }
2041
2042 pub fn compile(&mut self, logic: &Value) -> Result<LogicId, String> {
2048 let _global_id = super::compiled_logic_store::compile_logic_value(logic)?;
2050
2051 let compiled = super::compiled_logic_store::get_compiled_logic(_global_id)
2053 .ok_or_else(|| "Failed to retrieve compiled logic from global store".to_string())?;
2054
2055 let deps = compiled.referenced_vars();
2057
2058 let id = LogicId(self.next_id);
2060 self.next_id += 1;
2061
2062 self.store.insert(id, compiled);
2064 self.dependencies.insert(id, deps);
2065
2066 Ok(id)
2067 }
2068
2069 pub fn get(&self, id: &LogicId) -> Option<&CompiledLogic> {
2071 self.store.get(id)
2072 }
2073
2074 pub fn remove(&mut self, id: &LogicId) -> Option<CompiledLogic> {
2076 self.dependencies.remove(id);
2077 self.store.remove(id)
2078 }
2079
2080 pub fn get_dependencies(&self, id: &LogicId) -> Option<&[String]> {
2082 self.dependencies.get(id).map(|v| v.as_slice())
2083 }
2084}
2085
2086impl Default for CompiledLogicStore {
2087 fn default() -> Self {
2088 Self::new()
2089 }
2090}
2091
2092#[cfg(test)]
2093mod tests {
2094 use super::*;
2095 use serde_json::json;
2096
2097 fn is_ok(json_value: serde_json::Value) -> bool {
2098 CompiledLogic::compile(&json_value).is_ok()
2099 }
2100
2101 #[test]
2102 fn test_compile_literals() {
2103 assert!(matches!(
2104 CompiledLogic::compile(&json!(null)).unwrap(),
2105 CompiledLogic::Null
2106 ));
2107 assert!(matches!(
2108 CompiledLogic::compile(&json!(true)).unwrap(),
2109 CompiledLogic::Bool(true)
2110 ));
2111 assert!(matches!(
2112 CompiledLogic::compile(&json!(false)).unwrap(),
2113 CompiledLogic::Bool(false)
2114 ));
2115 assert!(
2116 matches!(CompiledLogic::compile(&json!(42.5)).unwrap(), CompiledLogic::Number(n) if n == 42.5)
2117 );
2118 assert!(
2119 matches!(CompiledLogic::compile(&json!("hello")).unwrap(), CompiledLogic::String(ref s) if s == "hello")
2120 );
2121 assert!(matches!(
2122 CompiledLogic::compile(&json!([1, 2, 3])).unwrap(),
2123 CompiledLogic::Array(_)
2124 ));
2125 }
2126
2127 #[test]
2128 fn test_compile_variables() {
2129 assert!(is_ok(json!({"var": "a"})));
2130 assert!(is_ok(json!({"var": ["a", 1]})));
2131 assert!(!is_ok(json!({"var": {}}))); assert!(is_ok(json!({"$ref": "#/path"})));
2133 }
2134
2135 #[test]
2136 fn test_compile_logical() {
2137 assert!(is_ok(json!({"and": [true, false]})));
2138 assert!(is_ok(json!({"or": [true, false]})));
2139 assert!(is_ok(json!({"not": [true]})));
2140 assert!(is_ok(json!({"!": [true]})));
2141 assert!(is_ok(json!({"if": [true, 1, 0]})));
2142 assert!(is_ok(json!({"xor": [true, false]})));
2143 assert!(is_ok(json!({"ifnull": [{"var": "a"}, 0]})));
2144
2145 assert!(!is_ok(json!({"if": []})));
2147 assert!(!is_ok(json!({"and": true})));
2148 }
2149
2150 #[test]
2151 fn test_compile_comparison() {
2152 assert!(is_ok(json!({"==": [1, 1]})));
2153 assert!(is_ok(json!({"===": [1, 1]})));
2154 assert!(is_ok(json!({"!=": [1, 2]})));
2155 assert!(is_ok(json!({"!==": [1, 2]})));
2156 assert!(is_ok(json!({">": [2, 1]})));
2157 assert!(is_ok(json!({">=": [2, 2]})));
2158 assert!(is_ok(json!({"<": [1, 2]})));
2159 assert!(is_ok(json!({"<=": [2, 2]})));
2160
2161 assert!(!is_ok(json!({"==": 1})));
2163 }
2164
2165 #[test]
2166 fn test_compile_arithmetic() {
2167 assert!(is_ok(json!({"+": [1, 2]})));
2168 assert!(is_ok(json!({"-": [1, 2]})));
2169 assert!(is_ok(json!({"*": [1, 2]})));
2170 assert!(is_ok(json!({"/": [1, 2]})));
2171 assert!(is_ok(json!({"%": [1, 2]})));
2172 assert!(is_ok(json!({"pow": [2, 3]})));
2173
2174 assert!(!is_ok(json!({"-": "a"}))); }
2177
2178 #[test]
2179 fn test_compile_array_ops() {
2180 assert!(is_ok(json!({"map": [[1, 2], {"+": [{"var": ""}, 1]}]})));
2181 assert!(is_ok(json!({"filter": [[1, 2], {">": [{"var": ""}, 1]}]})));
2182 assert!(is_ok(
2183 json!({"reduce": [[1, 2], {"+": [{"var": "current"}, {"var": "accumulator"}]}, 0]})
2184 ));
2185 assert!(is_ok(json!({"all": [[true, false], {"var": ""}]})));
2186 assert!(is_ok(json!({"some": [[true, false], {"var": ""}]})));
2187 assert!(is_ok(json!({"none": [[true, false], {"var": ""}]})));
2188 assert!(is_ok(json!({"merge": [[1], [2]]})));
2189 assert!(is_ok(json!({"in": [1, [1, 2, 3]]})));
2190 assert!(is_ok(json!({"sum": [{"var": "data"}, "a", 0]})));
2191 }
2192
2193 #[test]
2194 fn test_compile_string_ops() {
2195 assert!(is_ok(json!({"cat": ["a", "b"]})));
2196 assert!(is_ok(json!({"substr": ["hello", 0, 2]})));
2197 assert!(is_ok(json!({"search": ["a", "abc", 0]})));
2198 assert!(is_ok(json!({"SPLITTEXT": ["a,b", ",", 0]})));
2199 assert!(is_ok(json!({"left": ["abc", 2]})));
2200 assert!(is_ok(json!({"right": ["abc", 2]})));
2201
2202 assert!(!is_ok(json!({"substr": "hello"})));
2204 }
2205
2206 #[test]
2207 fn test_compile_math_ops() {
2208 assert!(is_ok(json!({"max": [1, 2, 3]})));
2209 assert!(is_ok(json!({"min": [1, 2, 3]})));
2210 assert!(is_ok(json!({"abs": -1})));
2211 assert!(is_ok(json!({"round": [1.234, 2]})));
2212 assert!(is_ok(json!({"roundup": [1.234, 2]})));
2213 assert!(is_ok(json!({"rounddown": [1.234, 2]})));
2214 assert!(is_ok(json!({"ceiling": [1.234, 1]})));
2215 assert!(is_ok(json!({"floor": [1.234, 1]})));
2216 assert!(is_ok(json!({"trunc": [1.234, 2]})));
2217 }
2218
2219 #[test]
2220 fn test_compile_date_ops() {
2221 assert!(is_ok(json!({"today": []})));
2222 assert!(is_ok(json!({"date": [2024, 12, 1]})));
2223 assert!(is_ok(json!({"days": ["2024-12-02", "2024-12-01"]})));
2224 assert!(is_ok(json!({"year": ["2024-12-01"]})));
2225 assert!(is_ok(json!({"month": ["2024-12-01"]})));
2226 assert!(is_ok(json!({"day": ["2024-12-01"]})));
2227 }
2228
2229 #[test]
2230 fn test_compile_table_ops() {
2231 assert!(is_ok(json!({"VALUEAT": [{"var": "t"}, 0, "a"]})));
2232 assert!(is_ok(json!({"INDEXAT": ["a", {"var": "t"}, "b", true]})));
2233 assert!(is_ok(json!({"MAXAT": [{"var": "t"}, "a"]})));
2234 assert!(is_ok(json!({"MATCH": [{"var": "t"}, "==", 1, "a"]})));
2235 assert!(is_ok(json!({"MATCHRANGE": [{"var": "t"}, "==", 1, "a"]})));
2236 assert!(is_ok(json!({"CHOOSE": [{"var": "t"}, "==", 1, "a"]})));
2237 assert!(is_ok(json!({"FINDINDEX": [{"var": "t"}, "==", 1, "a"]})));
2238 assert!(is_ok(json!({"MAPOPTIONS": [{"var": "t"}, "a", "b"]})));
2239 assert!(is_ok(
2240 json!({"MAPOPTIONSIF": [{"var": "t"}, "a", "b", [true, "==", "c"]]})
2241 ));
2242
2243 assert!(!is_ok(json!({"VALUEAT": [[{"a": 1}], 0]})));
2245 assert!(!is_ok(json!({"MAPOPTIONSIF": [[], "a"]})));
2246 }
2247
2248 #[test]
2249 fn test_compile_util_ops() {
2250 assert!(is_ok(json!({"missing": ["a", "b"]})));
2251 assert!(is_ok(json!({"missing_some": [1, ["a", "b"]]})));
2252 assert!(is_ok(json!({"empty": []})));
2253 assert!(is_ok(json!({"return": 5})));
2254 assert!(is_ok(json!({"RANGEOPTIONS": [1, 5]})));
2255 }
2256
2257 #[test]
2258 fn test_compile_unknown() {
2259 assert!(!is_ok(json!({"UNKNOWN_OP": [1, 2]})));
2260 assert!(!is_ok(json!({"UNKNOWN_NON_ARRAY": 1})));
2261 }
2262}