formualizer_eval/builtins/
random.rs

1//! Volatile functions like RAND, RANDBETWEEN.
2use crate::args::ArgSchema;
3use crate::function::Function;
4use crate::traits::{ArgumentHandle, FunctionContext};
5use formualizer_common::{ExcelError, LiteralValue};
6use formualizer_macros::func_caps;
7use rand::Rng;
8
9#[derive(Debug)]
10pub struct RandFn;
11
12impl Function for RandFn {
13    func_caps!(VOLATILE);
14
15    fn name(&self) -> &'static str {
16        "RAND"
17    }
18    fn min_args(&self) -> usize {
19        0
20    }
21
22    fn eval_scalar<'a, 'b>(
23        &self,
24        _args: &'a [ArgumentHandle<'a, 'b>],
25        ctx: &dyn FunctionContext,
26    ) -> Result<LiteralValue, ExcelError> {
27        let mut rng = ctx.rng_for_current(self.function_salt());
28        Ok(LiteralValue::Number(rng.gen_range(0.0..1.0)))
29    }
30}
31
32pub fn register_builtins() {
33    crate::function_registry::register_function(std::sync::Arc::new(RandFn));
34    crate::function_registry::register_function(std::sync::Arc::new(RandBetweenFn));
35}
36
37#[cfg(test)]
38mod tests {
39    use super::*;
40    use crate::traits::DefaultFunctionContext;
41    use crate::{interpreter::Interpreter, test_workbook::TestWorkbook};
42    use formualizer_parse::LiteralValue;
43
44    fn interp(wb: &TestWorkbook) -> Interpreter<'_> {
45        wb.interpreter()
46    }
47
48    #[test]
49    fn test_rand_caps() {
50        let rand_fn = RandFn;
51        let caps = rand_fn.caps();
52
53        // Check that VOLATILE is set
54        assert!(caps.contains(crate::function::FnCaps::VOLATILE));
55
56        // Check that PURE is not set (volatile functions are not pure)
57        assert!(!caps.contains(crate::function::FnCaps::PURE));
58    }
59
60    #[test]
61    fn test_rand() {
62        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(RandFn));
63        let ctx = interp(&wb);
64
65        let f = ctx.context.get_function("", "RAND").unwrap();
66        let fctx = DefaultFunctionContext::new(ctx.context, None);
67        let result = f.eval_scalar(&[], &fctx).unwrap();
68        match result {
69            LiteralValue::Number(n) => assert!((0.0..1.0).contains(&n)),
70            _ => panic!("Expected a number"),
71        }
72    }
73
74    #[test]
75    fn test_randbetween_basic() {
76        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(RandBetweenFn));
77        let ctx = interp(&wb);
78        let f = ctx.context.get_function("", "RANDBETWEEN").unwrap();
79        let fctx = DefaultFunctionContext::new(ctx.context, None);
80        // Build two scalar args 1 and 3
81        let lo = formualizer_parse::ASTNode::new(
82            formualizer_parse::ASTNodeType::Literal(LiteralValue::Int(1)),
83            None,
84        );
85        let hi = formualizer_parse::ASTNode::new(
86            formualizer_parse::ASTNodeType::Literal(LiteralValue::Int(3)),
87            None,
88        );
89        let args = vec![
90            ArgumentHandle::new(&lo, &ctx),
91            ArgumentHandle::new(&hi, &ctx),
92        ];
93        let v = f.eval_scalar(&args, &fctx).unwrap();
94        match v {
95            LiteralValue::Int(n) => assert!((1..=3).contains(&n)),
96            _ => panic!("Expected Int"),
97        }
98    }
99}
100
101#[derive(Debug)]
102pub struct RandBetweenFn;
103
104impl Function for RandBetweenFn {
105    func_caps!(VOLATILE);
106
107    fn name(&self) -> &'static str {
108        "RANDBETWEEN"
109    }
110    fn min_args(&self) -> usize {
111        2
112    }
113    fn arg_schema(&self) -> &'static [ArgSchema] {
114        &crate::builtins::utils::ARG_NUM_LENIENT_TWO[..]
115    }
116
117    fn eval_scalar<'a, 'b>(
118        &self,
119        args: &'a [ArgumentHandle<'a, 'b>],
120        ctx: &dyn FunctionContext,
121    ) -> Result<LiteralValue, ExcelError> {
122        // Evaluate bounds as integers
123        let lo_v = args[0].value()?.into_owned();
124        let hi_v = args[1].value()?.into_owned();
125        let lo = match lo_v {
126            LiteralValue::Int(n) => n,
127            LiteralValue::Number(n) => n as i64,
128            _ => 0,
129        };
130        let hi = match hi_v {
131            LiteralValue::Int(n) => n,
132            LiteralValue::Number(n) => n as i64,
133            _ => 0,
134        };
135        if hi < lo {
136            return Err(ExcelError::new(formualizer_common::ExcelErrorKind::Num)
137                .with_message("RANDBETWEEN: hi < lo".to_string()));
138        }
139        let mut rng = ctx.rng_for_current(self.function_salt());
140        let n = if (hi - lo) as u64 == u64::MAX {
141            lo
142        } else {
143            rng.gen_range(lo..=hi)
144        };
145        Ok(LiteralValue::Int(n))
146    }
147}