Skip to main content

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