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