Skip to main content

zen_expression/functions/
internal.rs

1use crate::functions::defs::{
2    CompositeFunction, FunctionDefinition, FunctionSignature, StaticFunction,
3};
4use std::rc::Rc;
5use strum_macros::{Display, EnumIter, EnumString, IntoStaticStr};
6
7#[derive(Debug, PartialEq, Eq, Hash, Display, EnumString, EnumIter, IntoStaticStr, Clone, Copy)]
8#[strum(serialize_all = "camelCase")]
9pub enum InternalFunction {
10    // General
11    Len,
12    Contains,
13    Flatten,
14    Merge,
15    MergeDeep,
16
17    // String
18    Upper,
19    Lower,
20    Trim,
21    StartsWith,
22    EndsWith,
23    Matches,
24    Extract,
25    FuzzyMatch,
26    Split,
27
28    // Math
29    Abs,
30    Sum,
31    Avg,
32    Min,
33    Max,
34    Rand,
35    Median,
36    Mode,
37    Floor,
38    Ceil,
39    Round,
40    Trunc,
41
42    // Type
43    IsNumeric,
44    String,
45    Number,
46    Bool,
47    Type,
48
49    // Map
50    Keys,
51    Values,
52
53    #[strum(serialize = "d")]
54    Date,
55}
56
57impl From<&InternalFunction> for Rc<dyn FunctionDefinition> {
58    fn from(value: &InternalFunction) -> Self {
59        use crate::variable::VariableType as VT;
60        use InternalFunction as IF;
61
62        let s: Rc<dyn FunctionDefinition> = match value {
63            IF::Len => Rc::new(CompositeFunction {
64                implementation: Rc::new(imp::len),
65                signatures: vec![
66                    FunctionSignature::single(VT::String, VT::Number),
67                    FunctionSignature::single(VT::Any.array(), VT::Number),
68                ],
69            }),
70
71            IF::Contains => Rc::new(CompositeFunction {
72                implementation: Rc::new(imp::contains),
73                signatures: vec![
74                    FunctionSignature {
75                        parameters: vec![VT::String, VT::String],
76                        return_type: VT::Bool,
77                    },
78                    FunctionSignature {
79                        parameters: vec![VT::Any.array(), VT::Any],
80                        return_type: VT::Bool,
81                    },
82                ],
83            }),
84
85            IF::Flatten => Rc::new(StaticFunction {
86                implementation: Rc::new(imp::flatten),
87                signature: FunctionSignature::single(VT::Any.array(), VT::Any.array()),
88            }),
89
90            IF::Merge => Rc::new(CompositeFunction {
91                implementation: Rc::new(imp::merge),
92                signatures: vec![
93                    FunctionSignature::single(VT::Any.array(), VT::Any.array()),
94                    FunctionSignature::single(
95                        VT::Object(Default::default()).array(),
96                        VT::Object(Default::default()),
97                    ),
98                ],
99            }),
100
101            IF::MergeDeep => Rc::new(StaticFunction {
102                implementation: Rc::new(imp::merge_deep),
103                signature: FunctionSignature::single(
104                    VT::Object(Default::default()).array(),
105                    VT::Object(Default::default()),
106                ),
107            }),
108
109            IF::Upper => Rc::new(StaticFunction {
110                implementation: Rc::new(imp::upper),
111                signature: FunctionSignature::single(VT::String, VT::String),
112            }),
113
114            IF::Lower => Rc::new(StaticFunction {
115                implementation: Rc::new(imp::lower),
116                signature: FunctionSignature::single(VT::String, VT::String),
117            }),
118
119            IF::Trim => Rc::new(StaticFunction {
120                implementation: Rc::new(imp::trim),
121                signature: FunctionSignature::single(VT::String, VT::String),
122            }),
123
124            IF::StartsWith => Rc::new(StaticFunction {
125                implementation: Rc::new(imp::starts_with),
126                signature: FunctionSignature {
127                    parameters: vec![VT::String, VT::String],
128                    return_type: VT::Bool,
129                },
130            }),
131
132            IF::EndsWith => Rc::new(StaticFunction {
133                implementation: Rc::new(imp::ends_with),
134                signature: FunctionSignature {
135                    parameters: vec![VT::String, VT::String],
136                    return_type: VT::Bool,
137                },
138            }),
139
140            IF::Matches => Rc::new(StaticFunction {
141                implementation: Rc::new(imp::matches),
142                signature: FunctionSignature {
143                    parameters: vec![VT::String, VT::String],
144                    return_type: VT::Bool,
145                },
146            }),
147
148            IF::Extract => Rc::new(StaticFunction {
149                implementation: Rc::new(imp::extract),
150                signature: FunctionSignature {
151                    parameters: vec![VT::String, VT::String],
152                    return_type: VT::String.array(),
153                },
154            }),
155
156            IF::Split => Rc::new(StaticFunction {
157                implementation: Rc::new(imp::split),
158                signature: FunctionSignature {
159                    parameters: vec![VT::String, VT::String],
160                    return_type: VT::String.array(),
161                },
162            }),
163
164            IF::FuzzyMatch => Rc::new(CompositeFunction {
165                implementation: Rc::new(imp::fuzzy_match),
166                signatures: vec![
167                    FunctionSignature {
168                        parameters: vec![VT::String, VT::String],
169                        return_type: VT::Number,
170                    },
171                    FunctionSignature {
172                        parameters: vec![VT::String.array(), VT::String],
173                        return_type: VT::Number.array(),
174                    },
175                ],
176            }),
177
178            IF::Abs => Rc::new(StaticFunction {
179                implementation: Rc::new(imp::abs),
180                signature: FunctionSignature::single(VT::Number, VT::Number),
181            }),
182
183            IF::Rand => Rc::new(StaticFunction {
184                implementation: Rc::new(imp::rand),
185                signature: FunctionSignature::single(VT::Number, VT::Number),
186            }),
187
188            IF::Floor => Rc::new(StaticFunction {
189                implementation: Rc::new(imp::floor),
190                signature: FunctionSignature::single(VT::Number, VT::Number),
191            }),
192
193            IF::Ceil => Rc::new(StaticFunction {
194                implementation: Rc::new(imp::ceil),
195                signature: FunctionSignature::single(VT::Number, VT::Number),
196            }),
197
198            IF::Round => Rc::new(CompositeFunction {
199                implementation: Rc::new(imp::round),
200                signatures: vec![
201                    FunctionSignature {
202                        parameters: vec![VT::Number],
203                        return_type: VT::Number,
204                    },
205                    FunctionSignature {
206                        parameters: vec![VT::Number, VT::Number],
207                        return_type: VT::Number,
208                    },
209                ],
210            }),
211
212            IF::Trunc => Rc::new(CompositeFunction {
213                implementation: Rc::new(imp::trunc),
214                signatures: vec![
215                    FunctionSignature {
216                        parameters: vec![VT::Number],
217                        return_type: VT::Number,
218                    },
219                    FunctionSignature {
220                        parameters: vec![VT::Number, VT::Number],
221                        return_type: VT::Number,
222                    },
223                ],
224            }),
225
226            IF::Sum => Rc::new(StaticFunction {
227                implementation: Rc::new(imp::sum),
228                signature: FunctionSignature::single(VT::Number.array(), VT::Number),
229            }),
230
231            IF::Avg => Rc::new(StaticFunction {
232                implementation: Rc::new(imp::avg),
233                signature: FunctionSignature::single(VT::Number.array(), VT::Number),
234            }),
235
236            IF::Min => Rc::new(CompositeFunction {
237                implementation: Rc::new(imp::min),
238                signatures: vec![
239                    FunctionSignature::single(VT::Number.array(), VT::Number),
240                    FunctionSignature::single(VT::Date.array(), VT::Date),
241                ],
242            }),
243
244            IF::Max => Rc::new(CompositeFunction {
245                implementation: Rc::new(imp::max),
246                signatures: vec![
247                    FunctionSignature::single(VT::Number.array(), VT::Number),
248                    FunctionSignature::single(VT::Date.array(), VT::Date),
249                ],
250            }),
251
252            IF::Median => Rc::new(StaticFunction {
253                implementation: Rc::new(imp::median),
254                signature: FunctionSignature::single(VT::Number.array(), VT::Number),
255            }),
256
257            IF::Mode => Rc::new(StaticFunction {
258                implementation: Rc::new(imp::mode),
259                signature: FunctionSignature::single(VT::Number.array(), VT::Number),
260            }),
261
262            IF::Type => Rc::new(StaticFunction {
263                implementation: Rc::new(imp::to_type),
264                signature: FunctionSignature::single(VT::Any, VT::String),
265            }),
266
267            IF::String => Rc::new(StaticFunction {
268                implementation: Rc::new(imp::to_string),
269                signature: FunctionSignature::single(VT::Any, VT::String),
270            }),
271
272            IF::Bool => Rc::new(StaticFunction {
273                implementation: Rc::new(imp::to_bool),
274                signature: FunctionSignature::single(VT::Any, VT::Bool),
275            }),
276
277            IF::IsNumeric => Rc::new(StaticFunction {
278                implementation: Rc::new(imp::is_numeric),
279                signature: FunctionSignature::single(VT::Any, VT::Bool),
280            }),
281
282            IF::Number => Rc::new(StaticFunction {
283                implementation: Rc::new(imp::to_number),
284                signature: FunctionSignature::single(VT::Any, VT::Number),
285            }),
286
287            IF::Keys => Rc::new(CompositeFunction {
288                implementation: Rc::new(imp::keys),
289                signatures: vec![
290                    FunctionSignature::single(VT::Object(Default::default()), VT::String.array()),
291                    FunctionSignature::single(VT::Any.array(), VT::Number.array()),
292                ],
293            }),
294
295            IF::Values => Rc::new(StaticFunction {
296                implementation: Rc::new(imp::values),
297                signature: FunctionSignature::single(
298                    VT::Object(Default::default()),
299                    VT::Any.array(),
300                ),
301            }),
302
303            IF::Date => Rc::new(CompositeFunction {
304                implementation: Rc::new(imp::date),
305                signatures: vec![
306                    FunctionSignature {
307                        parameters: vec![],
308                        return_type: VT::Date,
309                    },
310                    FunctionSignature {
311                        parameters: vec![VT::Any],
312                        return_type: VT::Date,
313                    },
314                    FunctionSignature {
315                        parameters: vec![VT::Any, VT::String],
316                        return_type: VT::Date,
317                    },
318                ],
319            }),
320        };
321
322        s
323    }
324}
325
326pub(crate) mod imp {
327    use crate::functions::arguments::Arguments;
328    use crate::vm::date::DynamicVariableExt;
329    use crate::vm::VmDate;
330    use crate::{Variable as V, Variable};
331    use ahash::HashMapExt;
332    use anyhow::{anyhow, Context};
333    use chrono_tz::Tz;
334    #[cfg(not(feature = "regex-lite"))]
335    use regex::Regex;
336    #[cfg(feature = "regex-lite")]
337    use regex_lite::Regex;
338    use rust_decimal::prelude::{FromPrimitive, ToPrimitive};
339    use rust_decimal::{Decimal, RoundingStrategy};
340    use rust_decimal_macros::dec;
341    use std::collections::BTreeMap;
342    use std::rc::Rc;
343    use std::str::FromStr;
344
345    fn __internal_number_array(args: &Arguments, pos: usize) -> anyhow::Result<Vec<Decimal>> {
346        let a = args.array(pos)?;
347        let arr = a.borrow();
348
349        arr.iter()
350            .map(|v| v.as_number())
351            .collect::<Option<Vec<_>>>()
352            .context("Expected a number array")
353    }
354
355    enum Either<A, B> {
356        Left(A),
357        Right(B),
358    }
359
360    fn __internal_number_or_date_array(
361        args: &Arguments,
362        pos: usize,
363    ) -> anyhow::Result<Either<Vec<Decimal>, Vec<VmDate>>> {
364        let a = args.array(pos)?;
365        let arr = a.borrow();
366
367        let is_number = arr.first().map(|v| v.as_number()).flatten().is_some();
368        if is_number {
369            Ok(Either::Left(
370                arr.iter()
371                    .map(|v| v.as_number())
372                    .collect::<Option<Vec<_>>>()
373                    .context("Expected a number array")?,
374            ))
375        } else {
376            Ok(Either::Right(
377                arr.iter()
378                    .map(|v| match v {
379                        Variable::Dynamic(d) => d.as_date().cloned(),
380                        _ => None,
381                    })
382                    .collect::<Option<Vec<_>>>()
383                    .context("Expected a number array")?,
384            ))
385        }
386    }
387
388    pub fn starts_with(args: Arguments) -> anyhow::Result<V> {
389        let a = args.str(0)?;
390        let b = args.str(1)?;
391
392        Ok(V::Bool(a.starts_with(b)))
393    }
394
395    pub fn ends_with(args: Arguments) -> anyhow::Result<V> {
396        let a = args.str(0)?;
397        let b = args.str(1)?;
398
399        Ok(V::Bool(a.ends_with(b)))
400    }
401
402    pub fn matches(args: Arguments) -> anyhow::Result<V> {
403        let a = args.str(0)?;
404        let b = args.str(1)?;
405
406        let regex = Regex::new(b.as_ref()).context("Invalid regular expression")?;
407
408        Ok(V::Bool(regex.is_match(a.as_ref())))
409    }
410
411    pub fn upper(args: Arguments) -> anyhow::Result<V> {
412        let a = args.str(0)?;
413        Ok(V::String(a.to_uppercase().into()))
414    }
415
416    pub fn lower(args: Arguments) -> anyhow::Result<V> {
417        let a = args.str(0)?;
418        Ok(V::String(a.to_lowercase().into()))
419    }
420
421    pub fn trim(args: Arguments) -> anyhow::Result<V> {
422        let a = args.str(0)?;
423        Ok(V::String(a.trim().into()))
424    }
425
426    pub fn extract(args: Arguments) -> anyhow::Result<V> {
427        let a = args.str(0)?;
428        let b = args.str(1)?;
429
430        let regex = Regex::new(b.as_ref()).context("Invalid regular expression")?;
431
432        let captures = regex
433            .captures(a.as_ref())
434            .map(|capture| {
435                capture
436                    .iter()
437                    .map(|c| c.map(|c| c.as_str()))
438                    .filter_map(|c| c)
439                    .map(|s| V::String(Rc::from(s)))
440                    .collect()
441            })
442            .unwrap_or_default();
443
444        Ok(V::from_array(captures))
445    }
446
447    pub fn split(args: Arguments) -> anyhow::Result<V> {
448        let a = args.str(0)?;
449        let b = args.str(1)?;
450
451        let arr = Vec::from_iter(
452            a.split(b)
453                .into_iter()
454                .map(|s| V::String(s.to_string().into())),
455        );
456
457        Ok(V::from_array(arr))
458    }
459
460    pub fn flatten(args: Arguments) -> anyhow::Result<V> {
461        let a = args.array(0)?;
462
463        let arr = a.borrow();
464        let mut flat_arr = Vec::with_capacity(arr.len());
465        arr.iter().for_each(|v| match v {
466            V::Array(b) => {
467                let arr = b.borrow();
468                arr.iter().for_each(|v| flat_arr.push(v.clone()))
469            }
470            _ => flat_arr.push(v.clone()),
471        });
472
473        Ok(V::from_array(flat_arr))
474    }
475
476    pub fn merge(args: Arguments) -> anyhow::Result<V> {
477        let a = args.array(0)?;
478        let arr = a.borrow();
479
480        let Some(first) = arr.first() else {
481            return Ok(V::empty_object());
482        };
483
484        let capacity = arr
485            .iter()
486            .map(|item| match item {
487                V::Object(obj) => obj.borrow().len(),
488                V::Array(arr) => arr.borrow().len(),
489                _ => 0,
490            })
491            .sum();
492
493        match first {
494            V::Array(_) | V::Null => {
495                let mut merged = Vec::with_capacity(capacity);
496
497                for item in arr.iter() {
498                    match item {
499                        V::Array(inner) => {
500                            let inner = inner.borrow();
501                            merged.extend(inner.iter().cloned());
502                        }
503                        V::Null => {}
504                        _ => return Err(anyhow!("Expected array of arrays")),
505                    }
506                }
507
508                Ok(V::from_array(merged))
509            }
510            V::Object(_) => {
511                let mut merged: ahash::HashMap<Rc<str>, V> =
512                    ahash::HashMap::with_capacity(capacity);
513                for item in arr.iter() {
514                    match item {
515                        V::Object(obj) => {
516                            let obj = obj.borrow();
517                            for (key, value) in obj.iter() {
518                                merged.insert(key.clone(), value.clone());
519                            }
520                        }
521                        V::Null => {}
522                        _ => return Err(anyhow!("Expected array of objects")),
523                    }
524                }
525
526                Ok(V::from_object(merged))
527            }
528            other => Err(anyhow!(
529                "merge expects an array of arrays or objects, got {}",
530                other.type_name()
531            )),
532        }
533    }
534
535    pub fn merge_deep(args: Arguments) -> anyhow::Result<V> {
536        let a = args.array(0)?;
537        let arr = a.borrow();
538
539        let mut result = V::empty_object();
540        for item in arr.iter() {
541            match item {
542                V::Object(_) => {
543                    result = deep_merge_variables(&result, item);
544                }
545                V::Null => {}
546                _ => return Err(anyhow!("Expected array of objects")),
547            }
548        }
549
550        Ok(result)
551    }
552
553    fn deep_merge_variables(base: &V, patch: &V) -> V {
554        match (base, patch) {
555            (V::Object(a), V::Object(b)) => {
556                let a = a.borrow();
557                let b = b.borrow();
558                let mut merged: ahash::HashMap<Rc<str>, V> =
559                    ahash::HashMap::with_capacity(a.len() + b.len());
560
561                for (key, value) in a.iter() {
562                    merged.insert(key.clone(), value.clone());
563                }
564
565                for (key, value) in b.iter() {
566                    let entry = merged
567                        .get(key)
568                        .map(|existing| deep_merge_variables(existing, value))
569                        .unwrap_or_else(|| value.clone());
570                    merged.insert(key.clone(), entry);
571                }
572
573                V::from_object(merged)
574            }
575            (V::Array(a), V::Array(b)) => {
576                let a = a.borrow();
577                let b = b.borrow();
578                let mut merged = Vec::with_capacity(a.len() + b.len());
579                merged.extend(a.iter().cloned());
580                merged.extend(b.iter().cloned());
581                V::from_array(merged)
582            }
583            (_, patch) => patch.clone(),
584        }
585    }
586
587    pub fn abs(args: Arguments) -> anyhow::Result<V> {
588        let a = args.number(0)?;
589        Ok(V::Number(a.abs()))
590    }
591
592    pub fn ceil(args: Arguments) -> anyhow::Result<V> {
593        let a = args.number(0)?;
594        Ok(V::Number(a.ceil()))
595    }
596
597    pub fn floor(args: Arguments) -> anyhow::Result<V> {
598        let a = args.number(0)?;
599        Ok(V::Number(a.floor()))
600    }
601
602    pub fn round(args: Arguments) -> anyhow::Result<V> {
603        let a = args.number(0)?;
604        let dp = args
605            .onumber(1)?
606            .map(|v| v.to_u32().context("Invalid number of decimal places"))
607            .transpose()?
608            .unwrap_or(0);
609
610        Ok(V::Number(a.round_dp_with_strategy(
611            dp,
612            RoundingStrategy::MidpointAwayFromZero,
613        )))
614    }
615
616    pub fn trunc(args: Arguments) -> anyhow::Result<V> {
617        let a = args.number(0)?;
618        let dp = args
619            .onumber(1)?
620            .map(|v| v.to_u32().context("Invalid number of decimal places"))
621            .transpose()?
622            .unwrap_or(0);
623
624        Ok(V::Number(a.trunc_with_scale(dp)))
625    }
626
627    pub fn rand(args: Arguments) -> anyhow::Result<V> {
628        let a = args.number(0)?;
629        let upper_range = a.round().to_i64().context("Invalid upper range")?;
630
631        let random_number = fastrand::i64(0..=upper_range);
632        Ok(V::Number(Decimal::from(random_number)))
633    }
634
635    pub fn min(args: Arguments) -> anyhow::Result<V> {
636        let a = __internal_number_or_date_array(&args, 0)?;
637
638        match a {
639            Either::Left(arr) => {
640                let max = arr.into_iter().min().context("Empty array")?;
641                Ok(V::Number(Decimal::from(max)))
642            }
643            Either::Right(arr) => {
644                let max = arr.into_iter().min().context("Empty array")?;
645                Ok(V::Dynamic(Rc::new(max)))
646            }
647        }
648    }
649
650    pub fn max(args: Arguments) -> anyhow::Result<V> {
651        let a = __internal_number_or_date_array(&args, 0)?;
652
653        match a {
654            Either::Left(arr) => {
655                let max = arr.into_iter().max().context("Empty array")?;
656                Ok(V::Number(Decimal::from(max)))
657            }
658            Either::Right(arr) => {
659                let max = arr.into_iter().max().context("Empty array")?;
660                Ok(V::Dynamic(Rc::new(max)))
661            }
662        }
663    }
664
665    pub fn avg(args: Arguments) -> anyhow::Result<V> {
666        let a = __internal_number_array(&args, 0)?;
667        let sum = a.iter().fold(Decimal::ZERO, |acc, x| acc + x);
668
669        Ok(V::Number(Decimal::from(
670            sum.checked_div(Decimal::from(a.len()))
671                .context("Empty array")?,
672        )))
673    }
674
675    pub fn sum(args: Arguments) -> anyhow::Result<V> {
676        let a = __internal_number_array(&args, 0)?;
677        let sum = a.iter().fold(Decimal::ZERO, |acc, v| acc + v);
678
679        Ok(V::Number(Decimal::from(sum)))
680    }
681
682    pub fn median(args: Arguments) -> anyhow::Result<V> {
683        let mut a = __internal_number_array(&args, 0)?;
684        a.sort();
685
686        let center = a.len() / 2;
687        if a.len() % 2 == 1 {
688            let center_num = a.get(center).context("Index out of bounds")?;
689            Ok(V::Number(*center_num))
690        } else {
691            let center_left = a.get(center - 1).context("Index out of bounds")?;
692            let center_right = a.get(center).context("Index out of bounds")?;
693
694            let median = ((*center_left) + (*center_right)) / dec!(2);
695            Ok(V::Number(median))
696        }
697    }
698
699    pub fn mode(args: Arguments) -> anyhow::Result<V> {
700        let a = __internal_number_array(&args, 0)?;
701        let mut counts = BTreeMap::new();
702        for num in a {
703            *counts.entry(num).or_insert(0) += 1;
704        }
705
706        let most_common = counts
707            .into_iter()
708            .max_by_key(|&(_, count)| count)
709            .map(|(num, _)| num)
710            .context("Empty array")?;
711
712        Ok(V::Number(most_common))
713    }
714
715    pub fn to_type(args: Arguments) -> anyhow::Result<V> {
716        let a = args.var(0)?;
717        Ok(V::String(a.type_name().into()))
718    }
719
720    pub fn to_bool(args: Arguments) -> anyhow::Result<V> {
721        let a = args.var(0)?;
722        let val = match a {
723            V::Null => false,
724            V::Bool(v) => *v,
725            V::Number(n) => !n.is_zero(),
726            V::Array(_) | V::Object(_) | V::Dynamic(_) => true,
727            V::String(s) => match (*s).trim() {
728                "true" => true,
729                "false" => false,
730                _ => s.is_empty(),
731            },
732        };
733
734        Ok(V::Bool(val))
735    }
736
737    pub fn to_string(args: Arguments) -> anyhow::Result<V> {
738        let a = args.var(0)?;
739        let val = match a {
740            V::Null => Rc::from("null"),
741            V::Bool(v) => Rc::from(v.to_string().as_str()),
742            V::Number(n) => Rc::from(n.to_string().as_str()),
743            V::String(s) => s.clone(),
744            _ => return Err(anyhow!("Cannot convert type {} to string", a.type_name())),
745        };
746
747        Ok(V::String(val))
748    }
749
750    pub fn to_number(args: Arguments) -> anyhow::Result<V> {
751        let a = args.var(0)?;
752        let val = match a {
753            V::Number(n) => *n,
754            V::String(str) => {
755                let s = str.trim();
756                Decimal::from_str_exact(s)
757                    .or_else(|_| Decimal::from_scientific(s))
758                    .context("Invalid number")?
759            }
760            V::Bool(b) => match *b {
761                true => Decimal::ONE,
762                false => Decimal::ZERO,
763            },
764            _ => return Err(anyhow!("Cannot convert type {} to number", a.type_name())),
765        };
766
767        Ok(V::Number(val))
768    }
769
770    pub fn is_numeric(args: Arguments) -> anyhow::Result<V> {
771        let a = args.var(0)?;
772        let is_ok = match a {
773            V::Number(_) => true,
774            V::String(str) => {
775                let s = str.trim();
776                Decimal::from_str_exact(s)
777                    .or_else(|_| Decimal::from_scientific(s))
778                    .is_ok()
779            }
780            _ => false,
781        };
782
783        Ok(V::Bool(is_ok))
784    }
785
786    pub fn len(args: Arguments) -> anyhow::Result<V> {
787        let a = args.var(0)?;
788        let len = match a {
789            V::String(s) => s.len(),
790            V::Array(s) => {
791                let arr = s.borrow();
792                arr.len()
793            }
794            _ => {
795                return Err(anyhow!("Cannot determine len of type {}", a.type_name()));
796            }
797        };
798
799        Ok(V::Number(len.into()))
800    }
801
802    pub fn contains(args: Arguments) -> anyhow::Result<V> {
803        let a = args.var(0)?;
804        let b = args.var(1)?;
805
806        let val = match (a, b) {
807            (V::String(a), V::String(b)) => a.contains(b.as_ref()),
808            (V::Array(a), _) => {
809                let arr = a.borrow();
810
811                arr.iter().any(|a| match (a, b) {
812                    (V::Number(a), V::Number(b)) => a == b,
813                    (V::String(a), V::String(b)) => a == b,
814                    (V::Bool(a), V::Bool(b)) => a == b,
815                    (V::Null, V::Null) => true,
816                    _ => false,
817                })
818            }
819            _ => {
820                return Err(anyhow!(
821                    "Cannot determine contains for type {} and {}",
822                    a.type_name(),
823                    b.type_name()
824                ));
825            }
826        };
827
828        Ok(V::Bool(val))
829    }
830
831    pub fn fuzzy_match(args: Arguments) -> anyhow::Result<V> {
832        let a = args.var(0)?;
833        let b = args.str(1)?;
834
835        let val = match a {
836            V::String(a) => {
837                let sim = strsim::normalized_damerau_levenshtein(a.as_ref(), b.as_ref());
838                // This is okay, as NDL will return [0, 1]
839                V::Number(Decimal::from_f64(sim).unwrap_or(dec!(0)))
840            }
841            V::Array(_a) => {
842                let a = _a.borrow();
843                let mut sims = Vec::with_capacity(a.len());
844                for v in a.iter() {
845                    let s = v.as_str().context("Expected string array")?;
846
847                    let sim = Decimal::from_f64(strsim::normalized_damerau_levenshtein(
848                        s.as_ref(),
849                        b.as_ref(),
850                    ))
851                    .unwrap_or(dec!(0));
852                    sims.push(V::Number(sim));
853                }
854
855                V::from_array(sims)
856            }
857            _ => return Err(anyhow!("Fuzzy match not available for type")),
858        };
859
860        Ok(val)
861    }
862
863    pub fn keys(args: Arguments) -> anyhow::Result<V> {
864        let a = args.var(0)?;
865        let var = match a {
866            V::Array(a) => {
867                let arr = a.borrow();
868                let indices = arr
869                    .iter()
870                    .enumerate()
871                    .map(|(index, _)| V::Number(index.into()))
872                    .collect();
873
874                V::from_array(indices)
875            }
876            V::Object(a) => {
877                let obj = a.borrow();
878                let keys = obj.iter().map(|(key, _)| V::String(key.clone())).collect();
879
880                V::from_array(keys)
881            }
882            _ => {
883                return Err(anyhow!("Cannot determine keys of type {}", a.type_name()));
884            }
885        };
886
887        Ok(var)
888    }
889
890    pub fn values(args: Arguments) -> anyhow::Result<V> {
891        let a = args.object(0)?;
892        let obj = a.borrow();
893        let values: Vec<_> = obj.values().cloned().collect();
894
895        Ok(V::from_array(values))
896    }
897
898    pub fn date(args: Arguments) -> anyhow::Result<V> {
899        let provided = args.ovar(0);
900        let tz = args
901            .ostr(1)?
902            .map(|v| Tz::from_str(v).context("Invalid timezone"))
903            .transpose()?;
904
905        let date_time = match provided {
906            Some(v) => VmDate::new(v.clone(), tz),
907            None => VmDate::now(),
908        };
909
910        Ok(V::Dynamic(Rc::new(date_time)))
911    }
912}