uni_core/primitives/
modulo.rs1use crate::compat::format;
4use crate::interpreter::Interpreter;
5use crate::primitives::numeric_promotion::promote_pair;
6use crate::value::{RuntimeError, Value};
7use num_traits::Zero;
8
9pub fn mod_builtin(interp: &mut Interpreter) -> Result<(), RuntimeError> {
13 let b = interp.pop()?;
14 let a = interp.pop()?;
15
16 let is_zero = match &b {
18 Value::Int32(i) => *i == 0,
19 Value::Integer(i) => i.is_zero(),
20 Value::Rational(r) => r.is_zero(),
21 Value::Number(n) => *n == 0.0,
22 _ => false,
23 };
24 if is_zero {
25 return Err(RuntimeError::ModuloByZero);
26 }
27
28 let (pa, pb) = promote_pair(&a, &b);
30
31 let result = match (&pa, &pb) {
32 (Value::Int32(i1), Value::Int32(i2)) => Value::Int32(i1 % i2),
33 (Value::Integer(i1), Value::Integer(i2)) => Value::Integer(i1 % i2),
34 (Value::Rational(r1), Value::Rational(r2)) => {
35 let result = Value::Rational(r1 % r2);
36 result.demote()
37 }
38 (Value::Number(n1), Value::Number(n2)) => Value::Number(n1 % n2),
39 _ => {
40 return Err(RuntimeError::TypeError(format!(
41 "Cannot compute modulo of {:?} and {:?}",
42 a, b
43 )))
44 }
45 };
46
47 interp.push(result);
48 Ok(())
49}
50
51#[cfg(test)]
52mod tests {
53 use super::*;
54 use crate::value::Value;
55 use num_bigint::BigInt;
56 use num_rational::BigRational;
57
58 fn setup_interpreter() -> Interpreter {
59 Interpreter::new()
60 }
61
62 #[test]
63 fn test_mod_builtin_floats() {
64 let mut interp = setup_interpreter();
65
66 interp.push(Value::Number(13.0));
68 interp.push(Value::Number(5.0));
69 mod_builtin(&mut interp).unwrap();
70
71 let result = interp.pop().unwrap();
72 assert!(matches!(result, Value::Number(n) if n == 3.0));
73
74 interp.push(Value::Number(12.0));
76 interp.push(Value::Number(4.0));
77 mod_builtin(&mut interp).unwrap();
78
79 let result = interp.pop().unwrap();
80 assert!(matches!(result, Value::Number(n) if n == 0.0));
81
82 interp.push(Value::Number(-7.0));
84 interp.push(Value::Number(3.0));
85 mod_builtin(&mut interp).unwrap();
86
87 let result = interp.pop().unwrap();
88 if let Value::Number(n) = result {
89 assert_eq!(n, -1.0);
91 } else {
92 panic!("Expected number");
93 }
94 }
95
96 #[test]
97 fn test_mod_builtin_integers() {
98 let mut interp = setup_interpreter();
99
100 interp.push(Value::Integer(BigInt::from(13)));
102 interp.push(Value::Integer(BigInt::from(5)));
103 mod_builtin(&mut interp).unwrap();
104
105 let result = interp.pop().unwrap();
106 assert!(matches!(result, Value::Integer(ref i) if *i == BigInt::from(3)));
107
108 interp.push(Value::Integer(BigInt::from(12)));
110 interp.push(Value::Integer(BigInt::from(4)));
111 mod_builtin(&mut interp).unwrap();
112
113 let result = interp.pop().unwrap();
114 assert!(matches!(result, Value::Integer(ref i) if *i == BigInt::from(0)));
115
116 interp.push(Value::Integer(BigInt::from(-17)));
118 interp.push(Value::Integer(BigInt::from(5)));
119 mod_builtin(&mut interp).unwrap();
120
121 let result = interp.pop().unwrap();
122 assert!(matches!(result, Value::Integer(ref i) if *i == BigInt::from(-2)));
123 }
124
125 #[test]
126 fn test_mod_builtin_rationals() {
127 let mut interp = setup_interpreter();
128
129 interp.push(Value::Rational(BigRational::new(
131 BigInt::from(7),
132 BigInt::from(2),
133 )));
134 interp.push(Value::Rational(BigRational::new(
135 BigInt::from(3),
136 BigInt::from(2),
137 )));
138 mod_builtin(&mut interp).unwrap();
139
140 let result = interp.pop().unwrap();
141 if let Value::Rational(r) = result {
142 assert_eq!(*r.numer(), BigInt::from(1));
143 assert_eq!(*r.denom(), BigInt::from(2));
144 } else {
145 panic!("Expected Rational, got {:?}", result);
146 }
147 }
148
149 #[test]
150 fn test_mod_builtin_mixed_types() {
151 let mut interp = setup_interpreter();
152
153 interp.push(Value::Integer(BigInt::from(13)));
155 interp.push(Value::Number(5.0));
156 mod_builtin(&mut interp).unwrap();
157
158 let result = interp.pop().unwrap();
159 assert!(matches!(result, Value::Number(n) if n == 3.0));
160
161 interp.push(Value::Rational(BigRational::new(
163 BigInt::from(7),
164 BigInt::from(2),
165 )));
166 interp.push(Value::Integer(BigInt::from(2)));
167 mod_builtin(&mut interp).unwrap();
168
169 let result = interp.pop().unwrap();
170 if let Value::Rational(r) = result {
171 assert_eq!(*r.numer(), BigInt::from(3));
172 assert_eq!(*r.denom(), BigInt::from(2));
173 } else {
174 panic!("Expected Rational, got {:?}", result);
175 }
176 }
177
178 #[test]
179 fn test_mod_builtin_modulo_by_zero() {
180 let mut interp = setup_interpreter();
181
182 interp.push(Value::Number(5.0));
184 interp.push(Value::Number(0.0));
185 let result = mod_builtin(&mut interp);
186 assert!(matches!(result, Err(RuntimeError::ModuloByZero)));
187
188 interp.push(Value::Integer(BigInt::from(5)));
190 interp.push(Value::Integer(BigInt::from(0)));
191 let result = mod_builtin(&mut interp);
192 assert!(matches!(result, Err(RuntimeError::ModuloByZero)));
193
194 interp.push(Value::Rational(BigRational::new(
196 BigInt::from(5),
197 BigInt::from(1),
198 )));
199 interp.push(Value::Rational(BigRational::new(
200 BigInt::from(0),
201 BigInt::from(1),
202 )));
203 let result = mod_builtin(&mut interp);
204 assert!(matches!(result, Err(RuntimeError::ModuloByZero)));
205 }
206
207 #[test]
208 fn test_mod_builtin_stack_underflow() {
209 let mut interp = setup_interpreter();
210
211 let result = mod_builtin(&mut interp);
213 assert!(matches!(result, Err(RuntimeError::StackUnderflow)));
214
215 interp.push(Value::Number(5.0));
217 let result = mod_builtin(&mut interp);
218 assert!(matches!(result, Err(RuntimeError::StackUnderflow)));
219 }
220
221 #[test]
222 fn test_mod_builtin_type_error() {
223 let mut interp = setup_interpreter();
224
225 interp.push(Value::Number(5.0));
227 interp.push(Value::String("not a number".into()));
228 let result = mod_builtin(&mut interp);
229 assert!(matches!(result, Err(RuntimeError::TypeError(_))));
230 }
231}