vrl/stdlib/
mod_func.rs

1use crate::compiler::prelude::*;
2
3fn r#mod(value: Value, modulus: Value) -> Resolved {
4    let result = value.try_rem(modulus)?;
5    Ok(result)
6}
7
8#[derive(Clone, Copy, Debug)]
9pub struct Mod;
10
11impl Function for Mod {
12    fn identifier(&self) -> &'static str {
13        "mod"
14    }
15
16    fn usage(&self) -> &'static str {
17        "Calculates the remainder of `value` divided by `modulus`."
18    }
19
20    fn category(&self) -> &'static str {
21        Category::Number.as_ref()
22    }
23
24    fn internal_failure_reasons(&self) -> &'static [&'static str] {
25        &[
26            "`value` is not an integer or float.",
27            "`modulus` is not an integer or float.",
28            "`modulus` is equal to 0.",
29        ]
30    }
31
32    fn return_kind(&self) -> u16 {
33        kind::INTEGER | kind::FLOAT
34    }
35
36    fn parameters(&self) -> &'static [Parameter] {
37        const PARAMETERS: &[Parameter] = &[
38            Parameter::required(
39                "value",
40                kind::INTEGER | kind::FLOAT,
41                "The value the `modulus` is applied to.",
42            ),
43            Parameter::required(
44                "modulus",
45                kind::INTEGER | kind::FLOAT,
46                "The `modulus` value.",
47            ),
48        ];
49        PARAMETERS
50    }
51
52    fn examples(&self) -> &'static [Example] {
53        &[example! {
54            title: "Calculate the remainder of two integers",
55            source: "mod(5, 2)",
56            result: Ok("1"),
57        }]
58    }
59
60    fn compile(
61        &self,
62        _state: &state::TypeState,
63        _ctx: &mut FunctionCompileContext,
64        arguments: ArgumentList,
65    ) -> Compiled {
66        let value = arguments.required("value");
67        let modulus = arguments.required("modulus");
68        // TODO: return a compile-time error if modulus is 0
69
70        Ok(ModFn { value, modulus }.as_expr())
71    }
72}
73
74#[derive(Debug, Clone)]
75struct ModFn {
76    value: Box<dyn Expression>,
77    modulus: Box<dyn Expression>,
78}
79
80impl FunctionExpression for ModFn {
81    fn resolve(&self, ctx: &mut Context) -> Resolved {
82        let value = self.value.resolve(ctx)?;
83        let modulus = self.modulus.resolve(ctx)?;
84        r#mod(value, modulus)
85    }
86
87    fn type_def(&self, state: &state::TypeState) -> TypeDef {
88        // Division is infallible if the rhs is a literal normal float or a literal non-zero integer.
89        match self.modulus.resolve_constant(state) {
90            Some(value) if value.is_float() || value.is_integer() => match value {
91                Value::Float(v) if v.is_normal() => TypeDef::float().infallible(),
92                Value::Float(_) => TypeDef::float().fallible(),
93                Value::Integer(v) if v != 0 => TypeDef::integer().infallible(),
94                Value::Integer(_) => TypeDef::integer().fallible(),
95                _ => TypeDef::float().or_integer().fallible(),
96            },
97            _ => TypeDef::float().or_integer().fallible(),
98        }
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105    use crate::value;
106
107    test_function![
108        r#mod => Mod;
109
110        int_mod {
111            args: func_args![value: 5, modulus: 2],
112            want: Ok(value!(1)),
113            tdef: TypeDef::integer().infallible(),
114        }
115
116        float_mod {
117            args: func_args![value: 5.0, modulus: 2.0],
118            want: Ok(value!(1.0)),
119            tdef: TypeDef::float().infallible(),
120        }
121
122        fallible_mod {
123            args: func_args![value: 5.0, modulus: {}],
124            want: Err("can't calculate remainder of type float and null"),
125            tdef: TypeDef::float().or_integer().fallible(),
126        }
127    ];
128}