jmespath_extensions/
math.rs

1//! Mathematical functions.
2//!
3//! This module provides math functions for JMESPath queries.
4//!
5//! For complete function reference with signatures and examples, see the
6//! [`functions`](crate::functions) module documentation or use `jpx --list-category math`.
7//!
8//! # Example
9//!
10//! ```rust
11//! use jmespath::{Runtime, Variable};
12//! use jmespath_extensions::math;
13//!
14//! let mut runtime = Runtime::new();
15//! runtime.register_builtin_functions();
16//! math::register(&mut runtime);
17//! ```
18
19use std::rc::Rc;
20
21use crate::common::{
22    ArgumentType, Context, ErrorReason, Function, JmespathError, Rcvar, Runtime, Variable,
23};
24use crate::define_function;
25
26/// Register all math functions with the runtime.
27pub fn register(runtime: &mut Runtime) {
28    runtime.register_function("round", Box::new(RoundFn::new()));
29    runtime.register_function("floor_fn", Box::new(FloorFn::new()));
30    runtime.register_function("ceil_fn", Box::new(CeilFn::new()));
31    runtime.register_function("abs_fn", Box::new(AbsFn::new()));
32    runtime.register_function("mod_fn", Box::new(ModFn::new()));
33    runtime.register_function("pow", Box::new(PowFn::new()));
34    runtime.register_function("sqrt", Box::new(SqrtFn::new()));
35    runtime.register_function("log", Box::new(LogFn::new()));
36    runtime.register_function("clamp", Box::new(ClampFn::new()));
37    runtime.register_function("median", Box::new(MedianFn::new()));
38    runtime.register_function("percentile", Box::new(PercentileFn::new()));
39    runtime.register_function("variance", Box::new(VarianceFn::new()));
40    runtime.register_function("stddev", Box::new(StddevFn::new()));
41    runtime.register_function("sin", Box::new(SinFn::new()));
42    runtime.register_function("cos", Box::new(CosFn::new()));
43    runtime.register_function("tan", Box::new(TanFn::new()));
44    runtime.register_function("asin", Box::new(AsinFn::new()));
45    runtime.register_function("acos", Box::new(AcosFn::new()));
46    runtime.register_function("atan", Box::new(AtanFn::new()));
47    runtime.register_function("atan2", Box::new(Atan2Fn::new()));
48    runtime.register_function("deg_to_rad", Box::new(DegToRadFn::new()));
49    runtime.register_function("rad_to_deg", Box::new(RadToDegFn::new()));
50    runtime.register_function("sign", Box::new(SignFn::new()));
51    runtime.register_function("add", Box::new(AddFn::new()));
52    runtime.register_function("subtract", Box::new(SubtractFn::new()));
53    runtime.register_function("multiply", Box::new(MultiplyFn::new()));
54    runtime.register_function("divide", Box::new(DivideFn::new()));
55    runtime.register_function("mode", Box::new(ModeFn::new()));
56    runtime.register_function("to_fixed", Box::new(ToFixedFn::new()));
57    runtime.register_function("format_number", Box::new(FormatNumberFn::new()));
58    runtime.register_function("histogram", Box::new(HistogramFn::new()));
59    runtime.register_function("normalize", Box::new(NormalizeFn::new()));
60    runtime.register_function("z_score", Box::new(ZScoreFn::new()));
61    runtime.register_function("correlation", Box::new(CorrelationFn::new()));
62    runtime.register_function("quantile", Box::new(QuantileFn::new()));
63    runtime.register_function("moving_avg", Box::new(MovingAvgFn::new()));
64    runtime.register_function("ewma", Box::new(EwmaFn::new()));
65    runtime.register_function("covariance", Box::new(CovarianceFn::new()));
66    runtime.register_function("standardize", Box::new(StandardizeFn::new()));
67}
68
69// =============================================================================
70// round(number, precision?) -> number
71// =============================================================================
72
73define_function!(
74    RoundFn,
75    vec![ArgumentType::Number],
76    Some(ArgumentType::Number)
77);
78
79impl Function for RoundFn {
80    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
81        self.signature.validate(args, ctx)?;
82
83        let n = args[0].as_number().ok_or_else(|| {
84            JmespathError::new(
85                ctx.expression,
86                0,
87                ErrorReason::Parse("Expected number argument".to_owned()),
88            )
89        })?;
90
91        let precision = if args.len() > 1 {
92            args[1].as_number().map(|p| p as i32).unwrap_or(0)
93        } else {
94            0
95        };
96
97        let result = if precision == 0 {
98            n.round()
99        } else {
100            let multiplier = 10_f64.powi(precision);
101            (n * multiplier).round() / multiplier
102        };
103
104        Ok(Rc::new(Variable::Number(
105            serde_json::Number::from_f64(result).unwrap_or_else(|| serde_json::Number::from(0)),
106        )))
107    }
108}
109
110// =============================================================================
111// floor_fn(number) -> number
112// =============================================================================
113
114define_function!(FloorFn, vec![ArgumentType::Number], None);
115
116impl Function for FloorFn {
117    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
118        self.signature.validate(args, ctx)?;
119
120        let n = args[0].as_number().ok_or_else(|| {
121            JmespathError::new(
122                ctx.expression,
123                0,
124                ErrorReason::Parse("Expected number argument".to_owned()),
125            )
126        })?;
127
128        Ok(Rc::new(Variable::Number(serde_json::Number::from(
129            n.floor() as i64,
130        ))))
131    }
132}
133
134// =============================================================================
135// ceil_fn(number) -> number
136// =============================================================================
137
138define_function!(CeilFn, vec![ArgumentType::Number], None);
139
140impl Function for CeilFn {
141    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
142        self.signature.validate(args, ctx)?;
143
144        let n = args[0].as_number().ok_or_else(|| {
145            JmespathError::new(
146                ctx.expression,
147                0,
148                ErrorReason::Parse("Expected number argument".to_owned()),
149            )
150        })?;
151
152        Ok(Rc::new(Variable::Number(serde_json::Number::from(
153            n.ceil() as i64,
154        ))))
155    }
156}
157
158// =============================================================================
159// abs_fn(number) -> number
160// =============================================================================
161
162define_function!(AbsFn, vec![ArgumentType::Number], None);
163
164impl Function for AbsFn {
165    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
166        self.signature.validate(args, ctx)?;
167
168        let n = args[0].as_number().ok_or_else(|| {
169            JmespathError::new(
170                ctx.expression,
171                0,
172                ErrorReason::Parse("Expected number argument".to_owned()),
173            )
174        })?;
175
176        Ok(Rc::new(Variable::Number(
177            serde_json::Number::from_f64(n.abs()).unwrap_or_else(|| serde_json::Number::from(0)),
178        )))
179    }
180}
181
182// =============================================================================
183// mod_fn(number, divisor) -> number
184// =============================================================================
185
186define_function!(
187    ModFn,
188    vec![ArgumentType::Number, ArgumentType::Number],
189    None
190);
191
192impl Function for ModFn {
193    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
194        self.signature.validate(args, ctx)?;
195
196        let n = args[0].as_number().ok_or_else(|| {
197            JmespathError::new(
198                ctx.expression,
199                0,
200                ErrorReason::Parse("Expected number argument".to_owned()),
201            )
202        })?;
203
204        let divisor = args[1].as_number().ok_or_else(|| {
205            JmespathError::new(
206                ctx.expression,
207                0,
208                ErrorReason::Parse("Expected divisor argument".to_owned()),
209            )
210        })?;
211
212        if divisor == 0.0 {
213            return Err(JmespathError::new(
214                ctx.expression,
215                0,
216                ErrorReason::Parse("Division by zero".to_owned()),
217            ));
218        }
219
220        Ok(Rc::new(Variable::Number(
221            serde_json::Number::from_f64(n % divisor)
222                .unwrap_or_else(|| serde_json::Number::from(0)),
223        )))
224    }
225}
226
227// =============================================================================
228// pow(base, exponent) -> number
229// =============================================================================
230
231define_function!(
232    PowFn,
233    vec![ArgumentType::Number, ArgumentType::Number],
234    None
235);
236
237impl Function for PowFn {
238    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
239        self.signature.validate(args, ctx)?;
240
241        let base = args[0].as_number().ok_or_else(|| {
242            JmespathError::new(
243                ctx.expression,
244                0,
245                ErrorReason::Parse("Expected base number".to_owned()),
246            )
247        })?;
248
249        let exp = args[1].as_number().ok_or_else(|| {
250            JmespathError::new(
251                ctx.expression,
252                0,
253                ErrorReason::Parse("Expected exponent number".to_owned()),
254            )
255        })?;
256
257        Ok(Rc::new(Variable::Number(
258            serde_json::Number::from_f64(base.powf(exp))
259                .unwrap_or_else(|| serde_json::Number::from(0)),
260        )))
261    }
262}
263
264// =============================================================================
265// sqrt(number) -> number
266// =============================================================================
267
268define_function!(SqrtFn, vec![ArgumentType::Number], None);
269
270impl Function for SqrtFn {
271    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
272        self.signature.validate(args, ctx)?;
273
274        let n = args[0].as_number().ok_or_else(|| {
275            JmespathError::new(
276                ctx.expression,
277                0,
278                ErrorReason::Parse("Expected number argument".to_owned()),
279            )
280        })?;
281
282        if n < 0.0 {
283            return Err(JmespathError::new(
284                ctx.expression,
285                0,
286                ErrorReason::Parse("Cannot take square root of negative number".to_owned()),
287            ));
288        }
289
290        Ok(Rc::new(Variable::Number(
291            serde_json::Number::from_f64(n.sqrt()).unwrap_or_else(|| serde_json::Number::from(0)),
292        )))
293    }
294}
295
296// =============================================================================
297// log(number, base?) -> number (default base e)
298// =============================================================================
299
300define_function!(
301    LogFn,
302    vec![ArgumentType::Number],
303    Some(ArgumentType::Number)
304);
305
306impl Function for LogFn {
307    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
308        self.signature.validate(args, ctx)?;
309
310        let n = args[0].as_number().ok_or_else(|| {
311            JmespathError::new(
312                ctx.expression,
313                0,
314                ErrorReason::Parse("Expected number argument".to_owned()),
315            )
316        })?;
317
318        if n <= 0.0 {
319            return Err(JmespathError::new(
320                ctx.expression,
321                0,
322                ErrorReason::Parse("Logarithm requires positive number".to_owned()),
323            ));
324        }
325
326        let result = if args.len() > 1 {
327            let base = args[1].as_number().ok_or_else(|| {
328                JmespathError::new(
329                    ctx.expression,
330                    0,
331                    ErrorReason::Parse("Expected base number".to_owned()),
332                )
333            })?;
334            n.log(base)
335        } else {
336            n.ln()
337        };
338
339        Ok(Rc::new(Variable::Number(
340            serde_json::Number::from_f64(result).unwrap_or_else(|| serde_json::Number::from(0)),
341        )))
342    }
343}
344
345// =============================================================================
346// clamp(number, min, max) -> number
347// =============================================================================
348
349define_function!(
350    ClampFn,
351    vec![
352        ArgumentType::Number,
353        ArgumentType::Number,
354        ArgumentType::Number
355    ],
356    None
357);
358
359impl Function for ClampFn {
360    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
361        self.signature.validate(args, ctx)?;
362
363        let n = args[0].as_number().ok_or_else(|| {
364            JmespathError::new(
365                ctx.expression,
366                0,
367                ErrorReason::Parse("Expected number argument".to_owned()),
368            )
369        })?;
370
371        let min = args[1].as_number().ok_or_else(|| {
372            JmespathError::new(
373                ctx.expression,
374                0,
375                ErrorReason::Parse("Expected min number".to_owned()),
376            )
377        })?;
378
379        let max = args[2].as_number().ok_or_else(|| {
380            JmespathError::new(
381                ctx.expression,
382                0,
383                ErrorReason::Parse("Expected max number".to_owned()),
384            )
385        })?;
386
387        let result = n.max(min).min(max);
388
389        Ok(Rc::new(Variable::Number(
390            serde_json::Number::from_f64(result).unwrap_or_else(|| serde_json::Number::from(0)),
391        )))
392    }
393}
394
395// =============================================================================
396// median(array) -> number
397// =============================================================================
398
399define_function!(MedianFn, vec![ArgumentType::Array], None);
400
401impl Function for MedianFn {
402    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
403        self.signature.validate(args, ctx)?;
404
405        let arr = args[0].as_array().ok_or_else(|| {
406            JmespathError::new(
407                ctx.expression,
408                0,
409                ErrorReason::Parse("Expected array argument".to_owned()),
410            )
411        })?;
412
413        let mut numbers: Vec<f64> = arr.iter().filter_map(|v| v.as_number()).collect();
414
415        if numbers.is_empty() {
416            return Ok(Rc::new(Variable::Null));
417        }
418
419        numbers.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
420
421        let len = numbers.len();
422        let median = if len % 2 == 0 {
423            (numbers[len / 2 - 1] + numbers[len / 2]) / 2.0
424        } else {
425            numbers[len / 2]
426        };
427
428        Ok(Rc::new(Variable::Number(
429            serde_json::Number::from_f64(median).unwrap_or_else(|| serde_json::Number::from(0)),
430        )))
431    }
432}
433
434// =============================================================================
435// percentile(array, p) -> number (pth percentile, p in 0-100)
436// =============================================================================
437
438define_function!(
439    PercentileFn,
440    vec![ArgumentType::Array, ArgumentType::Number],
441    None
442);
443
444impl Function for PercentileFn {
445    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
446        self.signature.validate(args, ctx)?;
447
448        let arr = args[0].as_array().ok_or_else(|| {
449            JmespathError::new(
450                ctx.expression,
451                0,
452                ErrorReason::Parse("Expected array argument".to_owned()),
453            )
454        })?;
455
456        let p = args[1].as_number().ok_or_else(|| {
457            JmespathError::new(
458                ctx.expression,
459                0,
460                ErrorReason::Parse("Expected percentile value".to_owned()),
461            )
462        })?;
463
464        if !(0.0..=100.0).contains(&p) {
465            return Err(JmespathError::new(
466                ctx.expression,
467                0,
468                ErrorReason::Parse("Percentile must be between 0 and 100".to_owned()),
469            ));
470        }
471
472        let mut numbers: Vec<f64> = arr.iter().filter_map(|v| v.as_number()).collect();
473
474        if numbers.is_empty() {
475            return Ok(Rc::new(Variable::Null));
476        }
477
478        numbers.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
479
480        let len = numbers.len();
481        if len == 1 {
482            return Ok(Rc::new(Variable::Number(
483                serde_json::Number::from_f64(numbers[0])
484                    .unwrap_or_else(|| serde_json::Number::from(0)),
485            )));
486        }
487
488        let rank = (p / 100.0) * (len - 1) as f64;
489        let lower_idx = rank.floor() as usize;
490        let upper_idx = rank.ceil() as usize;
491        let fraction = rank - lower_idx as f64;
492
493        let result = if lower_idx == upper_idx {
494            numbers[lower_idx]
495        } else {
496            numbers[lower_idx] * (1.0 - fraction) + numbers[upper_idx] * fraction
497        };
498
499        Ok(Rc::new(Variable::Number(
500            serde_json::Number::from_f64(result).unwrap_or_else(|| serde_json::Number::from(0)),
501        )))
502    }
503}
504
505// =============================================================================
506// variance(array) -> number (population variance)
507// =============================================================================
508
509define_function!(VarianceFn, vec![ArgumentType::Array], None);
510
511impl Function for VarianceFn {
512    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
513        self.signature.validate(args, ctx)?;
514
515        let arr = args[0].as_array().ok_or_else(|| {
516            JmespathError::new(
517                ctx.expression,
518                0,
519                ErrorReason::Parse("Expected array argument".to_owned()),
520            )
521        })?;
522
523        let numbers: Vec<f64> = arr.iter().filter_map(|v| v.as_number()).collect();
524
525        if numbers.is_empty() {
526            return Ok(Rc::new(Variable::Null));
527        }
528
529        let mean = numbers.iter().sum::<f64>() / numbers.len() as f64;
530        let variance =
531            numbers.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / numbers.len() as f64;
532
533        Ok(Rc::new(Variable::Number(
534            serde_json::Number::from_f64(variance).unwrap_or_else(|| serde_json::Number::from(0)),
535        )))
536    }
537}
538
539// =============================================================================
540// stddev(array) -> number (population standard deviation)
541// =============================================================================
542
543define_function!(StddevFn, vec![ArgumentType::Array], None);
544
545impl Function for StddevFn {
546    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
547        self.signature.validate(args, ctx)?;
548
549        let arr = args[0].as_array().ok_or_else(|| {
550            JmespathError::new(
551                ctx.expression,
552                0,
553                ErrorReason::Parse("Expected array argument".to_owned()),
554            )
555        })?;
556
557        let numbers: Vec<f64> = arr.iter().filter_map(|v| v.as_number()).collect();
558
559        if numbers.is_empty() {
560            return Ok(Rc::new(Variable::Null));
561        }
562
563        let mean = numbers.iter().sum::<f64>() / numbers.len() as f64;
564        let variance =
565            numbers.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / numbers.len() as f64;
566        let stddev = variance.sqrt();
567
568        Ok(Rc::new(Variable::Number(
569            serde_json::Number::from_f64(stddev).unwrap_or_else(|| serde_json::Number::from(0)),
570        )))
571    }
572}
573
574// =============================================================================
575// Trigonometric functions
576// =============================================================================
577
578define_function!(SinFn, vec![ArgumentType::Number], None);
579
580impl Function for SinFn {
581    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
582        self.signature.validate(args, ctx)?;
583        let n = args[0].as_number().ok_or_else(|| {
584            JmespathError::new(
585                ctx.expression,
586                0,
587                ErrorReason::Parse("Expected number".to_owned()),
588            )
589        })?;
590        Ok(Rc::new(Variable::Number(
591            serde_json::Number::from_f64(n.sin()).unwrap_or_else(|| serde_json::Number::from(0)),
592        )))
593    }
594}
595
596define_function!(CosFn, vec![ArgumentType::Number], None);
597
598impl Function for CosFn {
599    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
600        self.signature.validate(args, ctx)?;
601        let n = args[0].as_number().ok_or_else(|| {
602            JmespathError::new(
603                ctx.expression,
604                0,
605                ErrorReason::Parse("Expected number".to_owned()),
606            )
607        })?;
608        Ok(Rc::new(Variable::Number(
609            serde_json::Number::from_f64(n.cos()).unwrap_or_else(|| serde_json::Number::from(0)),
610        )))
611    }
612}
613
614define_function!(TanFn, vec![ArgumentType::Number], None);
615
616impl Function for TanFn {
617    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
618        self.signature.validate(args, ctx)?;
619        let n = args[0].as_number().ok_or_else(|| {
620            JmespathError::new(
621                ctx.expression,
622                0,
623                ErrorReason::Parse("Expected number".to_owned()),
624            )
625        })?;
626        Ok(Rc::new(Variable::Number(
627            serde_json::Number::from_f64(n.tan()).unwrap_or_else(|| serde_json::Number::from(0)),
628        )))
629    }
630}
631
632define_function!(AsinFn, vec![ArgumentType::Number], None);
633
634impl Function for AsinFn {
635    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
636        self.signature.validate(args, ctx)?;
637        let n = args[0].as_number().ok_or_else(|| {
638            JmespathError::new(
639                ctx.expression,
640                0,
641                ErrorReason::Parse("Expected number".to_owned()),
642            )
643        })?;
644        let result = n.asin();
645        // Return null for out-of-domain values (|n| > 1 produces NaN)
646        if result.is_nan() {
647            Ok(Rc::new(Variable::Null))
648        } else {
649            Ok(Rc::new(Variable::Number(
650                serde_json::Number::from_f64(result).unwrap_or_else(|| serde_json::Number::from(0)),
651            )))
652        }
653    }
654}
655
656define_function!(AcosFn, vec![ArgumentType::Number], None);
657
658impl Function for AcosFn {
659    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
660        self.signature.validate(args, ctx)?;
661        let n = args[0].as_number().ok_or_else(|| {
662            JmespathError::new(
663                ctx.expression,
664                0,
665                ErrorReason::Parse("Expected number".to_owned()),
666            )
667        })?;
668        let result = n.acos();
669        // Return null for out-of-domain values (|n| > 1 produces NaN)
670        if result.is_nan() {
671            Ok(Rc::new(Variable::Null))
672        } else {
673            Ok(Rc::new(Variable::Number(
674                serde_json::Number::from_f64(result).unwrap_or_else(|| serde_json::Number::from(0)),
675            )))
676        }
677    }
678}
679
680define_function!(AtanFn, vec![ArgumentType::Number], None);
681
682impl Function for AtanFn {
683    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
684        self.signature.validate(args, ctx)?;
685        let n = args[0].as_number().ok_or_else(|| {
686            JmespathError::new(
687                ctx.expression,
688                0,
689                ErrorReason::Parse("Expected number".to_owned()),
690            )
691        })?;
692        Ok(Rc::new(Variable::Number(
693            serde_json::Number::from_f64(n.atan()).unwrap_or_else(|| serde_json::Number::from(0)),
694        )))
695    }
696}
697
698define_function!(
699    Atan2Fn,
700    vec![ArgumentType::Number, ArgumentType::Number],
701    None
702);
703
704impl Function for Atan2Fn {
705    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
706        self.signature.validate(args, ctx)?;
707        let y = args[0].as_number().ok_or_else(|| {
708            JmespathError::new(
709                ctx.expression,
710                0,
711                ErrorReason::Parse("Expected number".to_owned()),
712            )
713        })?;
714        let x = args[1].as_number().ok_or_else(|| {
715            JmespathError::new(
716                ctx.expression,
717                0,
718                ErrorReason::Parse("Expected number".to_owned()),
719            )
720        })?;
721        Ok(Rc::new(Variable::Number(
722            serde_json::Number::from_f64(y.atan2(x)).unwrap_or_else(|| serde_json::Number::from(0)),
723        )))
724    }
725}
726
727define_function!(DegToRadFn, vec![ArgumentType::Number], None);
728
729impl Function for DegToRadFn {
730    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
731        self.signature.validate(args, ctx)?;
732        let n = args[0].as_number().ok_or_else(|| {
733            JmespathError::new(
734                ctx.expression,
735                0,
736                ErrorReason::Parse("Expected number".to_owned()),
737            )
738        })?;
739        Ok(Rc::new(Variable::Number(
740            serde_json::Number::from_f64(n.to_radians())
741                .unwrap_or_else(|| serde_json::Number::from(0)),
742        )))
743    }
744}
745
746define_function!(RadToDegFn, vec![ArgumentType::Number], None);
747
748impl Function for RadToDegFn {
749    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
750        self.signature.validate(args, ctx)?;
751        let n = args[0].as_number().ok_or_else(|| {
752            JmespathError::new(
753                ctx.expression,
754                0,
755                ErrorReason::Parse("Expected number".to_owned()),
756            )
757        })?;
758        Ok(Rc::new(Variable::Number(
759            serde_json::Number::from_f64(n.to_degrees())
760                .unwrap_or_else(|| serde_json::Number::from(0)),
761        )))
762    }
763}
764
765define_function!(SignFn, vec![ArgumentType::Number], None);
766
767impl Function for SignFn {
768    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
769        self.signature.validate(args, ctx)?;
770        let n = args[0].as_number().ok_or_else(|| {
771            JmespathError::new(
772                ctx.expression,
773                0,
774                ErrorReason::Parse("Expected number".to_owned()),
775            )
776        })?;
777        let sign = if n > 0.0 {
778            1
779        } else if n < 0.0 {
780            -1
781        } else {
782            0
783        };
784        Ok(Rc::new(Variable::Number(serde_json::Number::from(sign))))
785    }
786}
787
788// =============================================================================
789// add(a, b) -> number
790// =============================================================================
791
792define_function!(
793    AddFn,
794    vec![ArgumentType::Number, ArgumentType::Number],
795    None
796);
797
798impl Function for AddFn {
799    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
800        self.signature.validate(args, ctx)?;
801        let a = args[0].as_number().ok_or_else(|| {
802            JmespathError::new(
803                ctx.expression,
804                0,
805                ErrorReason::Parse("Expected number".to_owned()),
806            )
807        })?;
808        let b = args[1].as_number().ok_or_else(|| {
809            JmespathError::new(
810                ctx.expression,
811                0,
812                ErrorReason::Parse("Expected number".to_owned()),
813            )
814        })?;
815        Ok(Rc::new(Variable::Number(
816            serde_json::Number::from_f64(a + b).unwrap_or_else(|| serde_json::Number::from(0)),
817        )))
818    }
819}
820
821// =============================================================================
822// subtract(a, b) -> number
823// =============================================================================
824
825define_function!(
826    SubtractFn,
827    vec![ArgumentType::Number, ArgumentType::Number],
828    None
829);
830
831impl Function for SubtractFn {
832    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
833        self.signature.validate(args, ctx)?;
834        let a = args[0].as_number().ok_or_else(|| {
835            JmespathError::new(
836                ctx.expression,
837                0,
838                ErrorReason::Parse("Expected number".to_owned()),
839            )
840        })?;
841        let b = args[1].as_number().ok_or_else(|| {
842            JmespathError::new(
843                ctx.expression,
844                0,
845                ErrorReason::Parse("Expected number".to_owned()),
846            )
847        })?;
848        Ok(Rc::new(Variable::Number(
849            serde_json::Number::from_f64(a - b).unwrap_or_else(|| serde_json::Number::from(0)),
850        )))
851    }
852}
853
854// =============================================================================
855// multiply(a, b) -> number
856// =============================================================================
857
858define_function!(
859    MultiplyFn,
860    vec![ArgumentType::Number, ArgumentType::Number],
861    None
862);
863
864impl Function for MultiplyFn {
865    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
866        self.signature.validate(args, ctx)?;
867        let a = args[0].as_number().ok_or_else(|| {
868            JmespathError::new(
869                ctx.expression,
870                0,
871                ErrorReason::Parse("Expected number".to_owned()),
872            )
873        })?;
874        let b = args[1].as_number().ok_or_else(|| {
875            JmespathError::new(
876                ctx.expression,
877                0,
878                ErrorReason::Parse("Expected number".to_owned()),
879            )
880        })?;
881        Ok(Rc::new(Variable::Number(
882            serde_json::Number::from_f64(a * b).unwrap_or_else(|| serde_json::Number::from(0)),
883        )))
884    }
885}
886
887// =============================================================================
888// divide(a, b) -> number
889// =============================================================================
890
891define_function!(
892    DivideFn,
893    vec![ArgumentType::Number, ArgumentType::Number],
894    None
895);
896
897impl Function for DivideFn {
898    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
899        self.signature.validate(args, ctx)?;
900        let a = args[0].as_number().ok_or_else(|| {
901            JmespathError::new(
902                ctx.expression,
903                0,
904                ErrorReason::Parse("Expected number".to_owned()),
905            )
906        })?;
907        let b = args[1].as_number().ok_or_else(|| {
908            JmespathError::new(
909                ctx.expression,
910                0,
911                ErrorReason::Parse("Expected number".to_owned()),
912            )
913        })?;
914        if b == 0.0 {
915            return Err(JmespathError::new(
916                ctx.expression,
917                0,
918                ErrorReason::Parse("Division by zero".to_owned()),
919            ));
920        }
921        Ok(Rc::new(Variable::Number(
922            serde_json::Number::from_f64(a / b).unwrap_or_else(|| serde_json::Number::from(0)),
923        )))
924    }
925}
926
927// =============================================================================
928// mode(array) -> any (most common value)
929// =============================================================================
930
931define_function!(ModeFn, vec![ArgumentType::Array], None);
932
933impl Function for ModeFn {
934    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
935        self.signature.validate(args, ctx)?;
936
937        let arr = args[0].as_array().ok_or_else(|| {
938            JmespathError::new(
939                ctx.expression,
940                0,
941                ErrorReason::Parse("Expected array argument".to_owned()),
942            )
943        })?;
944
945        if arr.is_empty() {
946            return Ok(Rc::new(Variable::Null));
947        }
948
949        // Count occurrences - use JSON string representation as key
950        let mut counts: std::collections::HashMap<String, (usize, Rcvar)> =
951            std::collections::HashMap::new();
952
953        for item in arr.iter() {
954            let key = serde_json::to_string(&**item).unwrap_or_default();
955            counts
956                .entry(key)
957                .and_modify(|(count, _)| *count += 1)
958                .or_insert((1, item.clone()));
959        }
960
961        // Find the value with highest count
962        let (_, (_, mode_value)) = counts
963            .into_iter()
964            .max_by_key(|(_, (count, _))| *count)
965            .unwrap();
966
967        Ok(mode_value)
968    }
969}
970
971// =============================================================================
972// to_fixed(number, precision) -> string
973// Like JavaScript's Number.toFixed() - returns string with exact decimal places
974// =============================================================================
975
976define_function!(
977    ToFixedFn,
978    vec![ArgumentType::Number, ArgumentType::Number],
979    None
980);
981
982impl Function for ToFixedFn {
983    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
984        self.signature.validate(args, ctx)?;
985
986        let num = args[0].as_number().ok_or_else(|| {
987            JmespathError::new(
988                ctx.expression,
989                0,
990                ErrorReason::Parse("Expected number argument".to_owned()),
991            )
992        })?;
993
994        let precision = args[1].as_number().ok_or_else(|| {
995            JmespathError::new(
996                ctx.expression,
997                0,
998                ErrorReason::Parse("Expected precision argument".to_owned()),
999            )
1000        })? as usize;
1001
1002        let result = format!("{:.prec$}", num, prec = precision);
1003        Ok(Rc::new(Variable::String(result)))
1004    }
1005}
1006
1007// =============================================================================
1008// format_number(number, precision?, suffix?) -> string
1009// Format a number with thousand separators and optional suffix (k, M, B, etc.)
1010// =============================================================================
1011
1012define_function!(
1013    FormatNumberFn,
1014    vec![ArgumentType::Number],
1015    Some(ArgumentType::Any)
1016);
1017
1018impl Function for FormatNumberFn {
1019    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
1020        self.signature.validate(args, ctx)?;
1021
1022        let num = args[0].as_number().ok_or_else(|| {
1023            JmespathError::new(
1024                ctx.expression,
1025                0,
1026                ErrorReason::Parse("Expected number argument".to_owned()),
1027            )
1028        })?;
1029
1030        let precision = args
1031            .get(1)
1032            .and_then(|v| v.as_number())
1033            .map(|n| n as usize)
1034            .unwrap_or(0);
1035
1036        let suffix = args
1037            .get(2)
1038            .and_then(|v| v.as_string())
1039            .map(|s| s.to_string());
1040
1041        // Handle suffix scaling (k, M, B, T)
1042        let (scaled_num, auto_suffix) = if let Some(ref s) = suffix {
1043            match s.as_str() {
1044                "k" | "K" => (num / 1_000.0, "k"),
1045                "M" => (num / 1_000_000.0, "M"),
1046                "B" => (num / 1_000_000_000.0, "B"),
1047                "T" => (num / 1_000_000_000_000.0, "T"),
1048                "auto" => {
1049                    // Auto-detect best suffix
1050                    let abs_num = num.abs();
1051                    if abs_num >= 1_000_000_000_000.0 {
1052                        (num / 1_000_000_000_000.0, "T")
1053                    } else if abs_num >= 1_000_000_000.0 {
1054                        (num / 1_000_000_000.0, "B")
1055                    } else if abs_num >= 1_000_000.0 {
1056                        (num / 1_000_000.0, "M")
1057                    } else if abs_num >= 1_000.0 {
1058                        (num / 1_000.0, "k")
1059                    } else {
1060                        (num, "")
1061                    }
1062                }
1063                _ => (num, s.as_str()),
1064            }
1065        } else {
1066            (num, "")
1067        };
1068
1069        // Format with precision
1070        let formatted = format!("{:.prec$}", scaled_num, prec = precision);
1071
1072        // Add thousand separators to the integer part
1073        let result = if suffix.is_none() || suffix.as_deref() == Some("") {
1074            add_thousand_separators(&formatted)
1075        } else {
1076            format!("{}{}", formatted, auto_suffix)
1077        };
1078
1079        Ok(Rc::new(Variable::String(result)))
1080    }
1081}
1082
1083/// Add thousand separators (commas) to a number string
1084fn add_thousand_separators(s: &str) -> String {
1085    let parts: Vec<&str> = s.split('.').collect();
1086    let int_part = parts[0];
1087    let dec_part = parts.get(1);
1088
1089    // Handle negative sign
1090    let (sign, digits) = if let Some(stripped) = int_part.strip_prefix('-') {
1091        ("-", stripped)
1092    } else {
1093        ("", int_part)
1094    };
1095
1096    // Add commas every 3 digits from the right
1097    let digit_chars: Vec<char> = digits.chars().collect();
1098    let len = digit_chars.len();
1099    let with_commas: String = digit_chars
1100        .iter()
1101        .enumerate()
1102        .map(|(i, c)| {
1103            let pos_from_right = len - 1 - i;
1104            if pos_from_right > 0 && pos_from_right % 3 == 0 {
1105                format!("{},", c)
1106            } else {
1107                c.to_string()
1108            }
1109        })
1110        .collect();
1111
1112    match dec_part {
1113        Some(dec) => format!("{}{}.{}", sign, with_commas, dec),
1114        None => format!("{}{}", sign, with_commas),
1115    }
1116}
1117
1118// =============================================================================
1119// histogram(array, bins) -> array
1120// Bucket values into histogram bins
1121// =============================================================================
1122
1123define_function!(
1124    HistogramFn,
1125    vec![ArgumentType::Array, ArgumentType::Number],
1126    None
1127);
1128
1129impl Function for HistogramFn {
1130    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
1131        self.signature.validate(args, ctx)?;
1132
1133        let arr = args[0].as_array().ok_or_else(|| {
1134            JmespathError::new(
1135                ctx.expression,
1136                0,
1137                ErrorReason::Parse("Expected array argument".to_owned()),
1138            )
1139        })?;
1140
1141        let num_bins = args[1].as_number().ok_or_else(|| {
1142            JmespathError::new(
1143                ctx.expression,
1144                0,
1145                ErrorReason::Parse("Expected number of bins".to_owned()),
1146            )
1147        })? as usize;
1148
1149        if num_bins == 0 {
1150            return Err(JmespathError::new(
1151                ctx.expression,
1152                0,
1153                ErrorReason::Parse("Number of bins must be greater than 0".to_owned()),
1154            ));
1155        }
1156
1157        // Extract numeric values
1158        let values: Vec<f64> = arr.iter().filter_map(|v| v.as_number()).collect();
1159
1160        if values.is_empty() {
1161            return Ok(Rc::new(Variable::Array(vec![])));
1162        }
1163
1164        let min_val = values.iter().cloned().fold(f64::INFINITY, f64::min);
1165        let max_val = values.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
1166
1167        // Handle case where all values are the same
1168        let bin_width = if (max_val - min_val).abs() < f64::EPSILON {
1169            1.0
1170        } else {
1171            (max_val - min_val) / num_bins as f64
1172        };
1173
1174        // Initialize bins
1175        let mut bins: Vec<(f64, f64, usize)> = (0..num_bins)
1176            .map(|i| {
1177                let bin_min = min_val + (i as f64 * bin_width);
1178                let bin_max = if i == num_bins - 1 {
1179                    max_val
1180                } else {
1181                    min_val + ((i + 1) as f64 * bin_width)
1182                };
1183                (bin_min, bin_max, 0)
1184            })
1185            .collect();
1186
1187        // Count values in each bin
1188        for val in &values {
1189            let bin_idx = if (max_val - min_val).abs() < f64::EPSILON {
1190                0
1191            } else {
1192                let idx = ((val - min_val) / bin_width) as usize;
1193                idx.min(num_bins - 1)
1194            };
1195            bins[bin_idx].2 += 1;
1196        }
1197
1198        // Convert to array of objects
1199        let result: Vec<Rcvar> = bins
1200            .into_iter()
1201            .map(|(bin_min, bin_max, count)| {
1202                let mut map = std::collections::BTreeMap::new();
1203                map.insert(
1204                    "min".to_string(),
1205                    Rc::new(Variable::Number(
1206                        serde_json::Number::from_f64(bin_min)
1207                            .unwrap_or_else(|| serde_json::Number::from(0)),
1208                    )) as Rcvar,
1209                );
1210                map.insert(
1211                    "max".to_string(),
1212                    Rc::new(Variable::Number(
1213                        serde_json::Number::from_f64(bin_max)
1214                            .unwrap_or_else(|| serde_json::Number::from(0)),
1215                    )) as Rcvar,
1216                );
1217                map.insert(
1218                    "count".to_string(),
1219                    Rc::new(Variable::Number(serde_json::Number::from(count))) as Rcvar,
1220                );
1221                Rc::new(Variable::Object(map)) as Rcvar
1222            })
1223            .collect();
1224
1225        Ok(Rc::new(Variable::Array(result)))
1226    }
1227}
1228
1229// =============================================================================
1230// normalize(array) -> array
1231// Normalize values to 0-1 range
1232// =============================================================================
1233
1234define_function!(NormalizeFn, vec![ArgumentType::Array], None);
1235
1236impl Function for NormalizeFn {
1237    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
1238        self.signature.validate(args, ctx)?;
1239
1240        let arr = args[0].as_array().ok_or_else(|| {
1241            JmespathError::new(
1242                ctx.expression,
1243                0,
1244                ErrorReason::Parse("Expected array argument".to_owned()),
1245            )
1246        })?;
1247
1248        // Extract numeric values
1249        let values: Vec<f64> = arr.iter().filter_map(|v| v.as_number()).collect();
1250
1251        if values.is_empty() {
1252            return Ok(Rc::new(Variable::Array(vec![])));
1253        }
1254
1255        let min_val = values.iter().cloned().fold(f64::INFINITY, f64::min);
1256        let max_val = values.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
1257        let range = max_val - min_val;
1258
1259        let result: Vec<Rcvar> = values
1260            .iter()
1261            .map(|v| {
1262                let normalized = if range.abs() < f64::EPSILON {
1263                    0.0 // All values are the same
1264                } else {
1265                    (v - min_val) / range
1266                };
1267                Rc::new(Variable::Number(
1268                    serde_json::Number::from_f64(normalized)
1269                        .unwrap_or_else(|| serde_json::Number::from(0)),
1270                )) as Rcvar
1271            })
1272            .collect();
1273
1274        Ok(Rc::new(Variable::Array(result)))
1275    }
1276}
1277
1278// =============================================================================
1279// z_score(array) -> array
1280// Calculate z-scores (standard scores) for values
1281// =============================================================================
1282
1283define_function!(ZScoreFn, vec![ArgumentType::Array], None);
1284
1285impl Function for ZScoreFn {
1286    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
1287        self.signature.validate(args, ctx)?;
1288
1289        let arr = args[0].as_array().ok_or_else(|| {
1290            JmespathError::new(
1291                ctx.expression,
1292                0,
1293                ErrorReason::Parse("Expected array argument".to_owned()),
1294            )
1295        })?;
1296
1297        // Extract numeric values
1298        let values: Vec<f64> = arr.iter().filter_map(|v| v.as_number()).collect();
1299
1300        if values.is_empty() {
1301            return Ok(Rc::new(Variable::Array(vec![])));
1302        }
1303
1304        let n = values.len() as f64;
1305        let mean = values.iter().sum::<f64>() / n;
1306        let variance = values.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / n;
1307        let stddev = variance.sqrt();
1308
1309        let result: Vec<Rcvar> = values
1310            .iter()
1311            .map(|v| {
1312                let z = if stddev.abs() < f64::EPSILON {
1313                    0.0 // All values are the same
1314                } else {
1315                    (v - mean) / stddev
1316                };
1317                Rc::new(Variable::Number(
1318                    serde_json::Number::from_f64(z).unwrap_or_else(|| serde_json::Number::from(0)),
1319                )) as Rcvar
1320            })
1321            .collect();
1322
1323        Ok(Rc::new(Variable::Array(result)))
1324    }
1325}
1326
1327// =============================================================================
1328// correlation(arr1, arr2) -> number
1329// Pearson correlation coefficient between two arrays
1330// =============================================================================
1331
1332define_function!(
1333    CorrelationFn,
1334    vec![ArgumentType::Array, ArgumentType::Array],
1335    None
1336);
1337
1338impl Function for CorrelationFn {
1339    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
1340        self.signature.validate(args, ctx)?;
1341
1342        let arr1 = args[0].as_array().ok_or_else(|| {
1343            JmespathError::new(
1344                ctx.expression,
1345                0,
1346                ErrorReason::Parse("Expected array argument".to_owned()),
1347            )
1348        })?;
1349
1350        let arr2 = args[1].as_array().ok_or_else(|| {
1351            JmespathError::new(
1352                ctx.expression,
1353                0,
1354                ErrorReason::Parse("Expected array argument".to_owned()),
1355            )
1356        })?;
1357
1358        // Extract numeric values
1359        let values1: Vec<f64> = arr1.iter().filter_map(|v| v.as_number()).collect();
1360        let values2: Vec<f64> = arr2.iter().filter_map(|v| v.as_number()).collect();
1361
1362        if values1.is_empty() || values2.is_empty() {
1363            return Ok(Rc::new(Variable::Null));
1364        }
1365
1366        // Use the shorter length
1367        let n = values1.len().min(values2.len());
1368        if n == 0 {
1369            return Ok(Rc::new(Variable::Null));
1370        }
1371
1372        let values1 = &values1[..n];
1373        let values2 = &values2[..n];
1374
1375        let mean1 = values1.iter().sum::<f64>() / n as f64;
1376        let mean2 = values2.iter().sum::<f64>() / n as f64;
1377
1378        let mut cov = 0.0;
1379        let mut var1 = 0.0;
1380        let mut var2 = 0.0;
1381
1382        for i in 0..n {
1383            let d1 = values1[i] - mean1;
1384            let d2 = values2[i] - mean2;
1385            cov += d1 * d2;
1386            var1 += d1 * d1;
1387            var2 += d2 * d2;
1388        }
1389
1390        let denom = (var1 * var2).sqrt();
1391        let correlation = if denom.abs() < f64::EPSILON {
1392            0.0 // No variance in one or both arrays
1393        } else {
1394            cov / denom
1395        };
1396
1397        Ok(Rc::new(Variable::Number(
1398            serde_json::Number::from_f64(correlation)
1399                .unwrap_or_else(|| serde_json::Number::from(0)),
1400        )))
1401    }
1402}
1403
1404// =============================================================================
1405// quantile(array, q) -> number
1406// Nth quantile (generalized percentile), q in [0, 1]
1407// =============================================================================
1408
1409define_function!(
1410    QuantileFn,
1411    vec![ArgumentType::Array, ArgumentType::Number],
1412    None
1413);
1414
1415impl Function for QuantileFn {
1416    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
1417        self.signature.validate(args, ctx)?;
1418
1419        let arr = args[0].as_array().ok_or_else(|| {
1420            JmespathError::new(
1421                ctx.expression,
1422                0,
1423                ErrorReason::Parse("Expected array argument".to_owned()),
1424            )
1425        })?;
1426
1427        let q = args[1].as_number().ok_or_else(|| {
1428            JmespathError::new(
1429                ctx.expression,
1430                0,
1431                ErrorReason::Parse("Expected number for quantile".to_owned()),
1432            )
1433        })?;
1434
1435        if !(0.0..=1.0).contains(&q) {
1436            return Ok(Rc::new(Variable::Null));
1437        }
1438
1439        let mut values: Vec<f64> = arr.iter().filter_map(|v| v.as_number()).collect();
1440
1441        if values.is_empty() {
1442            return Ok(Rc::new(Variable::Null));
1443        }
1444
1445        values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1446
1447        let n = values.len();
1448        let pos = q * (n - 1) as f64;
1449        let lower = pos.floor() as usize;
1450        let upper = pos.ceil() as usize;
1451        let frac = pos - lower as f64;
1452
1453        let result = if lower == upper {
1454            values[lower]
1455        } else {
1456            values[lower] * (1.0 - frac) + values[upper] * frac
1457        };
1458
1459        Ok(Rc::new(Variable::Number(
1460            serde_json::Number::from_f64(result).unwrap_or_else(|| serde_json::Number::from(0)),
1461        )))
1462    }
1463}
1464
1465// =============================================================================
1466// moving_avg(array, window) -> array
1467// Simple moving average
1468// =============================================================================
1469
1470define_function!(
1471    MovingAvgFn,
1472    vec![ArgumentType::Array, ArgumentType::Number],
1473    None
1474);
1475
1476impl Function for MovingAvgFn {
1477    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
1478        self.signature.validate(args, ctx)?;
1479
1480        let arr = args[0].as_array().ok_or_else(|| {
1481            JmespathError::new(
1482                ctx.expression,
1483                0,
1484                ErrorReason::Parse("Expected array argument".to_owned()),
1485            )
1486        })?;
1487
1488        let window = args[1].as_number().ok_or_else(|| {
1489            JmespathError::new(
1490                ctx.expression,
1491                0,
1492                ErrorReason::Parse("Expected number for window size".to_owned()),
1493            )
1494        })? as usize;
1495
1496        if window == 0 {
1497            return Ok(Rc::new(Variable::Null));
1498        }
1499
1500        let values: Vec<f64> = arr.iter().filter_map(|v| v.as_number()).collect();
1501
1502        if values.is_empty() || window > values.len() {
1503            return Ok(Rc::new(Variable::Array(vec![])));
1504        }
1505
1506        let mut result: Vec<Rcvar> = Vec::new();
1507
1508        // Compute moving averages for each position where we have enough data
1509        for i in 0..values.len() {
1510            if i + 1 < window {
1511                // Not enough data yet, use null
1512                result.push(Rc::new(Variable::Null));
1513            } else {
1514                let start = i + 1 - window;
1515                let sum: f64 = values[start..=i].iter().sum();
1516                let avg = sum / window as f64;
1517                result.push(Rc::new(Variable::Number(
1518                    serde_json::Number::from_f64(avg)
1519                        .unwrap_or_else(|| serde_json::Number::from(0)),
1520                )));
1521            }
1522        }
1523
1524        Ok(Rc::new(Variable::Array(result)))
1525    }
1526}
1527
1528// =============================================================================
1529// ewma(array, alpha) -> array
1530// Exponential weighted moving average
1531// =============================================================================
1532
1533define_function!(
1534    EwmaFn,
1535    vec![ArgumentType::Array, ArgumentType::Number],
1536    None
1537);
1538
1539impl Function for EwmaFn {
1540    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
1541        self.signature.validate(args, ctx)?;
1542
1543        let arr = args[0].as_array().ok_or_else(|| {
1544            JmespathError::new(
1545                ctx.expression,
1546                0,
1547                ErrorReason::Parse("Expected array argument".to_owned()),
1548            )
1549        })?;
1550
1551        let alpha = args[1].as_number().ok_or_else(|| {
1552            JmespathError::new(
1553                ctx.expression,
1554                0,
1555                ErrorReason::Parse("Expected number for alpha".to_owned()),
1556            )
1557        })?;
1558
1559        if !(0.0..=1.0).contains(&alpha) {
1560            return Ok(Rc::new(Variable::Null));
1561        }
1562
1563        let values: Vec<f64> = arr.iter().filter_map(|v| v.as_number()).collect();
1564
1565        if values.is_empty() {
1566            return Ok(Rc::new(Variable::Array(vec![])));
1567        }
1568
1569        let mut result: Vec<Rcvar> = Vec::new();
1570        let mut ewma = values[0];
1571
1572        for value in &values {
1573            ewma = alpha * value + (1.0 - alpha) * ewma;
1574            result.push(Rc::new(Variable::Number(
1575                serde_json::Number::from_f64(ewma).unwrap_or_else(|| serde_json::Number::from(0)),
1576            )));
1577        }
1578
1579        Ok(Rc::new(Variable::Array(result)))
1580    }
1581}
1582
1583// =============================================================================
1584// covariance(arr1, arr2) -> number
1585// Covariance between two arrays
1586// =============================================================================
1587
1588define_function!(
1589    CovarianceFn,
1590    vec![ArgumentType::Array, ArgumentType::Array],
1591    None
1592);
1593
1594impl Function for CovarianceFn {
1595    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
1596        self.signature.validate(args, ctx)?;
1597
1598        let arr1 = args[0].as_array().ok_or_else(|| {
1599            JmespathError::new(
1600                ctx.expression,
1601                0,
1602                ErrorReason::Parse("Expected array argument".to_owned()),
1603            )
1604        })?;
1605
1606        let arr2 = args[1].as_array().ok_or_else(|| {
1607            JmespathError::new(
1608                ctx.expression,
1609                0,
1610                ErrorReason::Parse("Expected array argument".to_owned()),
1611            )
1612        })?;
1613
1614        let values1: Vec<f64> = arr1.iter().filter_map(|v| v.as_number()).collect();
1615        let values2: Vec<f64> = arr2.iter().filter_map(|v| v.as_number()).collect();
1616
1617        if values1.is_empty() || values1.len() != values2.len() {
1618            return Ok(Rc::new(Variable::Null));
1619        }
1620
1621        let n = values1.len() as f64;
1622        let mean1: f64 = values1.iter().sum::<f64>() / n;
1623        let mean2: f64 = values2.iter().sum::<f64>() / n;
1624
1625        let cov: f64 = values1
1626            .iter()
1627            .zip(values2.iter())
1628            .map(|(x, y)| (x - mean1) * (y - mean2))
1629            .sum::<f64>()
1630            / n;
1631
1632        Ok(Rc::new(Variable::Number(
1633            serde_json::Number::from_f64(cov).unwrap_or_else(|| serde_json::Number::from(0)),
1634        )))
1635    }
1636}
1637
1638// =============================================================================
1639// standardize(array) -> array
1640// Standardize to mean=0, std=1 (z-score normalization)
1641// =============================================================================
1642
1643define_function!(StandardizeFn, vec![ArgumentType::Array], None);
1644
1645impl Function for StandardizeFn {
1646    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
1647        self.signature.validate(args, ctx)?;
1648
1649        let arr = args[0].as_array().ok_or_else(|| {
1650            JmespathError::new(
1651                ctx.expression,
1652                0,
1653                ErrorReason::Parse("Expected array argument".to_owned()),
1654            )
1655        })?;
1656
1657        let values: Vec<f64> = arr.iter().filter_map(|v| v.as_number()).collect();
1658
1659        if values.is_empty() {
1660            return Ok(Rc::new(Variable::Array(vec![])));
1661        }
1662
1663        let n = values.len() as f64;
1664        let mean: f64 = values.iter().sum::<f64>() / n;
1665        let variance: f64 = values.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / n;
1666        let std_dev = variance.sqrt();
1667
1668        let result: Vec<Rcvar> = values
1669            .iter()
1670            .map(|x| {
1671                let standardized = if std_dev.abs() < f64::EPSILON {
1672                    0.0
1673                } else {
1674                    (x - mean) / std_dev
1675                };
1676                Rc::new(Variable::Number(
1677                    serde_json::Number::from_f64(standardized)
1678                        .unwrap_or_else(|| serde_json::Number::from(0)),
1679                ))
1680            })
1681            .collect();
1682
1683        Ok(Rc::new(Variable::Array(result)))
1684    }
1685}
1686
1687#[cfg(test)]
1688mod tests {
1689    use super::*;
1690    use jmespath::Runtime;
1691
1692    fn setup_runtime() -> Runtime {
1693        let mut runtime = Runtime::new();
1694        runtime.register_builtin_functions();
1695        register(&mut runtime);
1696        runtime
1697    }
1698
1699    #[test]
1700    #[allow(clippy::approx_constant)]
1701    fn test_round() {
1702        let runtime = setup_runtime();
1703        let expr = runtime.compile("round(`3.14159`, `2`)").unwrap();
1704        let result = expr.search(&Variable::Null).unwrap();
1705        assert!((result.as_number().unwrap() - 3.14_f64).abs() < 0.001);
1706    }
1707
1708    #[test]
1709    fn test_sqrt() {
1710        let runtime = setup_runtime();
1711        let expr = runtime.compile("sqrt(`16`)").unwrap();
1712        let result = expr.search(&Variable::Null).unwrap();
1713        assert_eq!(result.as_number().unwrap() as i64, 4);
1714    }
1715
1716    #[test]
1717    fn test_clamp() {
1718        let runtime = setup_runtime();
1719        let expr = runtime.compile("clamp(`5`, `0`, `3`)").unwrap();
1720        let result = expr.search(&Variable::Null).unwrap();
1721        assert_eq!(result.as_number().unwrap() as i64, 3);
1722    }
1723
1724    #[test]
1725    fn test_add() {
1726        let runtime = setup_runtime();
1727        let expr = runtime.compile("add(`1`, `2`)").unwrap();
1728        let result = expr.search(&Variable::Null).unwrap();
1729        assert_eq!(result.as_number().unwrap() as i64, 3);
1730    }
1731
1732    #[test]
1733    fn test_subtract() {
1734        let runtime = setup_runtime();
1735        let expr = runtime.compile("subtract(`10`, `3`)").unwrap();
1736        let result = expr.search(&Variable::Null).unwrap();
1737        assert_eq!(result.as_number().unwrap() as i64, 7);
1738    }
1739
1740    #[test]
1741    fn test_multiply() {
1742        let runtime = setup_runtime();
1743        let expr = runtime.compile("multiply(`4`, `5`)").unwrap();
1744        let result = expr.search(&Variable::Null).unwrap();
1745        assert_eq!(result.as_number().unwrap() as i64, 20);
1746    }
1747
1748    #[test]
1749    fn test_divide() {
1750        let runtime = setup_runtime();
1751        let expr = runtime.compile("divide(`10`, `4`)").unwrap();
1752        let result = expr.search(&Variable::Null).unwrap();
1753        assert_eq!(result.as_number().unwrap(), 2.5);
1754    }
1755
1756    #[test]
1757    fn test_mode_numbers() {
1758        let runtime = setup_runtime();
1759        let expr = runtime.compile("mode(`[1, 2, 2, 3]`)").unwrap();
1760        let result = expr.search(&Variable::Null).unwrap();
1761        assert_eq!(result.as_number().unwrap() as i64, 2);
1762    }
1763
1764    #[test]
1765    fn test_mode_strings() {
1766        let runtime = setup_runtime();
1767        let expr = runtime
1768            .compile("mode(`[\"a\", \"b\", \"a\", \"c\"]`)")
1769            .unwrap();
1770        let result = expr.search(&Variable::Null).unwrap();
1771        assert_eq!(result.as_string().unwrap(), "a");
1772    }
1773
1774    #[test]
1775    fn test_mode_empty() {
1776        let runtime = setup_runtime();
1777        let expr = runtime.compile("mode(`[]`)").unwrap();
1778        let result = expr.search(&Variable::Null).unwrap();
1779        assert!(result.is_null());
1780    }
1781
1782    #[test]
1783    fn test_to_fixed() {
1784        let runtime = setup_runtime();
1785        let expr = runtime.compile("to_fixed(`3.14159`, `2`)").unwrap();
1786        let result = expr.search(&Variable::Null).unwrap();
1787        assert_eq!(result.as_string().unwrap(), "3.14");
1788    }
1789
1790    #[test]
1791    fn test_to_fixed_padding() {
1792        let runtime = setup_runtime();
1793        let expr = runtime.compile("to_fixed(`3.1`, `3`)").unwrap();
1794        let result = expr.search(&Variable::Null).unwrap();
1795        assert_eq!(result.as_string().unwrap(), "3.100");
1796    }
1797
1798    #[test]
1799    fn test_format_number_with_separators() {
1800        let runtime = setup_runtime();
1801        let expr = runtime.compile("format_number(`1234567.89`, `2`)").unwrap();
1802        let result = expr.search(&Variable::Null).unwrap();
1803        assert_eq!(result.as_string().unwrap(), "1,234,567.89");
1804    }
1805
1806    #[test]
1807    fn test_format_number_with_k_suffix() {
1808        let runtime = setup_runtime();
1809        let expr = runtime.compile("format_number(`1500`, `1`, 'k')").unwrap();
1810        let result = expr.search(&Variable::Null).unwrap();
1811        assert_eq!(result.as_string().unwrap(), "1.5k");
1812    }
1813
1814    #[test]
1815    fn test_format_number_with_m_suffix() {
1816        let runtime = setup_runtime();
1817        let expr = runtime
1818            .compile("format_number(`1500000`, `1`, 'M')")
1819            .unwrap();
1820        let result = expr.search(&Variable::Null).unwrap();
1821        assert_eq!(result.as_string().unwrap(), "1.5M");
1822    }
1823
1824    #[test]
1825    fn test_format_number_auto_suffix() {
1826        let runtime = setup_runtime();
1827        let expr = runtime
1828            .compile("format_number(`1500000000`, `2`, 'auto')")
1829            .unwrap();
1830        let result = expr.search(&Variable::Null).unwrap();
1831        assert_eq!(result.as_string().unwrap(), "1.50B");
1832    }
1833
1834    #[test]
1835    fn test_histogram() {
1836        let runtime = setup_runtime();
1837        let expr = runtime.compile("histogram(@, `3`)").unwrap();
1838        let data: Variable = serde_json::from_str("[1, 2, 3, 4, 5, 6, 7, 8, 9]").unwrap();
1839        let result = expr.search(&data).unwrap();
1840        let arr = result.as_array().unwrap();
1841        assert_eq!(arr.len(), 3);
1842        // Each bin should have 3 values
1843        for bin in arr {
1844            let obj = bin.as_object().unwrap();
1845            assert!(obj.contains_key("min"));
1846            assert!(obj.contains_key("max"));
1847            assert!(obj.contains_key("count"));
1848        }
1849    }
1850
1851    #[test]
1852    fn test_normalize() {
1853        let runtime = setup_runtime();
1854        let expr = runtime.compile("normalize(@)").unwrap();
1855        let data: Variable = serde_json::from_str("[0, 50, 100]").unwrap();
1856        let result = expr.search(&data).unwrap();
1857        let arr = result.as_array().unwrap();
1858        assert_eq!(arr.len(), 3);
1859        assert!((arr[0].as_number().unwrap() - 0.0).abs() < 0.001);
1860        assert!((arr[1].as_number().unwrap() - 0.5).abs() < 0.001);
1861        assert!((arr[2].as_number().unwrap() - 1.0).abs() < 0.001);
1862    }
1863
1864    #[test]
1865    fn test_z_score() {
1866        let runtime = setup_runtime();
1867        let expr = runtime.compile("z_score(@)").unwrap();
1868        let data: Variable = serde_json::from_str("[1, 2, 3, 4, 5]").unwrap();
1869        let result = expr.search(&data).unwrap();
1870        let arr = result.as_array().unwrap();
1871        assert_eq!(arr.len(), 5);
1872        // Middle value (3) should have z-score of 0
1873        assert!((arr[2].as_number().unwrap() - 0.0).abs() < 0.001);
1874    }
1875
1876    #[test]
1877    fn test_correlation_positive() {
1878        let runtime = setup_runtime();
1879        let expr = runtime
1880            .compile("correlation(`[1, 2, 3]`, `[1, 2, 3]`)")
1881            .unwrap();
1882        let result = expr.search(&Variable::Null).unwrap();
1883        assert!((result.as_number().unwrap() - 1.0).abs() < 0.001);
1884    }
1885
1886    #[test]
1887    fn test_correlation_negative() {
1888        let runtime = setup_runtime();
1889        let expr = runtime
1890            .compile("correlation(`[1, 2, 3]`, `[3, 2, 1]`)")
1891            .unwrap();
1892        let result = expr.search(&Variable::Null).unwrap();
1893        assert!((result.as_number().unwrap() - (-1.0)).abs() < 0.001);
1894    }
1895
1896    #[test]
1897    fn test_quantile_median() {
1898        let runtime = setup_runtime();
1899        let expr = runtime
1900            .compile("quantile(`[1, 2, 3, 4, 5]`, `0.5`)")
1901            .unwrap();
1902        let result = expr.search(&Variable::Null).unwrap();
1903        assert_eq!(result.as_number().unwrap(), 3.0);
1904    }
1905
1906    #[test]
1907    fn test_quantile_quartiles() {
1908        let runtime = setup_runtime();
1909        // First quartile
1910        let expr = runtime
1911            .compile("quantile(`[1, 2, 3, 4, 5]`, `0.25`)")
1912            .unwrap();
1913        let result = expr.search(&Variable::Null).unwrap();
1914        assert_eq!(result.as_number().unwrap(), 2.0);
1915
1916        // Third quartile
1917        let expr = runtime
1918            .compile("quantile(`[1, 2, 3, 4, 5]`, `0.75`)")
1919            .unwrap();
1920        let result = expr.search(&Variable::Null).unwrap();
1921        assert_eq!(result.as_number().unwrap(), 4.0);
1922    }
1923
1924    #[test]
1925    fn test_moving_avg() {
1926        let runtime = setup_runtime();
1927        let expr = runtime
1928            .compile("moving_avg(`[1, 2, 3, 4, 5, 6]`, `3`)")
1929            .unwrap();
1930        let result = expr.search(&Variable::Null).unwrap();
1931        let arr = result.as_array().unwrap();
1932        assert_eq!(arr.len(), 6);
1933        assert!(arr[0].is_null());
1934        assert!(arr[1].is_null());
1935        assert_eq!(arr[2].as_number().unwrap(), 2.0); // (1+2+3)/3
1936        assert_eq!(arr[3].as_number().unwrap(), 3.0); // (2+3+4)/3
1937        assert_eq!(arr[4].as_number().unwrap(), 4.0); // (3+4+5)/3
1938        assert_eq!(arr[5].as_number().unwrap(), 5.0); // (4+5+6)/3
1939    }
1940
1941    #[test]
1942    fn test_ewma() {
1943        let runtime = setup_runtime();
1944        let expr = runtime.compile("ewma(`[1, 2, 3, 4, 5]`, `0.5`)").unwrap();
1945        let result = expr.search(&Variable::Null).unwrap();
1946        let arr = result.as_array().unwrap();
1947        assert_eq!(arr.len(), 5);
1948        // First value is just the first value
1949        assert_eq!(arr[0].as_number().unwrap(), 1.0);
1950        // Subsequent values: alpha * current + (1-alpha) * prev_ewma
1951        assert_eq!(arr[1].as_number().unwrap(), 1.5); // 0.5*2 + 0.5*1
1952        assert_eq!(arr[2].as_number().unwrap(), 2.25); // 0.5*3 + 0.5*1.5
1953    }
1954
1955    #[test]
1956    fn test_covariance() {
1957        let runtime = setup_runtime();
1958        let expr = runtime
1959            .compile("covariance(`[1, 2, 3]`, `[1, 2, 3]`)")
1960            .unwrap();
1961        let result = expr.search(&Variable::Null).unwrap();
1962        // Variance of [1,2,3] is 2/3
1963        assert!((result.as_number().unwrap() - 0.666666).abs() < 0.01);
1964    }
1965
1966    #[test]
1967    fn test_covariance_negative() {
1968        let runtime = setup_runtime();
1969        let expr = runtime
1970            .compile("covariance(`[1, 2, 3]`, `[3, 2, 1]`)")
1971            .unwrap();
1972        let result = expr.search(&Variable::Null).unwrap();
1973        assert!((result.as_number().unwrap() - (-0.666666)).abs() < 0.01);
1974    }
1975
1976    #[test]
1977    fn test_standardize() {
1978        let runtime = setup_runtime();
1979        let expr = runtime
1980            .compile("standardize(`[10, 20, 30, 40, 50]`)")
1981            .unwrap();
1982        let result = expr.search(&Variable::Null).unwrap();
1983        let arr = result.as_array().unwrap();
1984        assert_eq!(arr.len(), 5);
1985        // Mean is 30, std is ~14.14
1986        // First value: (10-30)/14.14 ≈ -1.41
1987        assert!((arr[0].as_number().unwrap() - (-1.414)).abs() < 0.01);
1988        // Middle value should be 0
1989        assert!(arr[2].as_number().unwrap().abs() < 0.001);
1990        // Last value: (50-30)/14.14 ≈ 1.41
1991        assert!((arr[4].as_number().unwrap() - 1.414).abs() < 0.01);
1992    }
1993}