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