uni_core/primitives/
shl.rs

1use crate::compat::ToString;
2use crate::interpreter::Interpreter;
3use crate::value::{RuntimeError, Value};
4
5pub fn shl_builtin(interp: &mut Interpreter) -> Result<(), RuntimeError> {
6    let shift_amount = interp.pop_number()?;
7    let value = interp.pop_number()?;
8
9    // Convert to integers for shift operations
10    let value_int = value as i64;
11    let shift_int = shift_amount as u32;
12
13    // Check for reasonable shift amounts to prevent overflow
14    if shift_int > 63 {
15        return Err(RuntimeError::DomainError(
16            "shift amount too large".to_string(),
17        ));
18    }
19
20    let result = value_int << shift_int;
21    interp.push(Value::Number(result as f64));
22    Ok(())
23}
24
25#[cfg(test)]
26mod tests {
27    use super::*;
28    use crate::value::Value;
29
30    fn setup_interpreter() -> Interpreter {
31        Interpreter::new()
32    }
33
34    #[test]
35    fn test_shl_basic() {
36        let mut interp = setup_interpreter();
37        interp.push(Value::Number(1.0));
38        interp.push(Value::Number(3.0)); // Shift left by 3
39
40        shl_builtin(&mut interp).unwrap();
41
42        let result = interp.pop().unwrap();
43        assert!(matches!(result, Value::Number(n) if n == 8.0)); // 1 << 3 = 8
44    }
45
46    #[test]
47    fn test_shl_zero_shift() {
48        let mut interp = setup_interpreter();
49        interp.push(Value::Number(42.0));
50        interp.push(Value::Number(0.0)); // Shift left by 0
51
52        shl_builtin(&mut interp).unwrap();
53
54        let result = interp.pop().unwrap();
55        assert!(matches!(result, Value::Number(n) if n == 42.0)); // No change
56    }
57
58    #[test]
59    fn test_shl_powers_of_two() {
60        let mut interp = setup_interpreter();
61
62        let test_cases = [
63            (1.0, 1.0, 2.0),   // 1 << 1 = 2
64            (1.0, 2.0, 4.0),   // 1 << 2 = 4
65            (1.0, 4.0, 16.0),  // 1 << 4 = 16
66            (1.0, 8.0, 256.0), // 1 << 8 = 256
67        ];
68
69        for (value, shift, expected) in test_cases {
70            interp.push(Value::Number(value));
71            interp.push(Value::Number(shift));
72            shl_builtin(&mut interp).unwrap();
73            let result = interp.pop().unwrap();
74            assert!(
75                matches!(result, Value::Number(n) if n == expected),
76                "{} << {} should be {}, got {:?}",
77                value,
78                shift,
79                expected,
80                result
81            );
82        }
83    }
84
85    #[test]
86    fn test_shl_multiple_bits() {
87        let mut interp = setup_interpreter();
88
89        let test_cases = [
90            (3.0, 2.0, 12.0), // 11 << 2 = 1100 = 12
91            (5.0, 1.0, 10.0), // 101 << 1 = 1010 = 10
92            (7.0, 3.0, 56.0), // 111 << 3 = 111000 = 56
93        ];
94
95        for (value, shift, expected) in test_cases {
96            interp.push(Value::Number(value));
97            interp.push(Value::Number(shift));
98            shl_builtin(&mut interp).unwrap();
99            let result = interp.pop().unwrap();
100            assert!(
101                matches!(result, Value::Number(n) if n == expected),
102                "{} << {} should be {}, got {:?}",
103                value,
104                shift,
105                expected,
106                result
107            );
108        }
109    }
110
111    #[test]
112    fn test_shl_zero_value() {
113        let mut interp = setup_interpreter();
114        interp.push(Value::Number(0.0));
115        interp.push(Value::Number(5.0)); // Shift any amount
116
117        shl_builtin(&mut interp).unwrap();
118
119        let result = interp.pop().unwrap();
120        assert!(matches!(result, Value::Number(n) if n == 0.0)); // 0 shifted is still 0
121    }
122
123    #[test]
124    fn test_shl_negative_numbers() {
125        let mut interp = setup_interpreter();
126
127        let test_cases = [
128            (-1.0, 1.0, -2.0), // -1 << 1 = -2
129            (-2.0, 2.0, -8.0), // -2 << 2 = -8
130            (-4.0, 1.0, -8.0), // -4 << 1 = -8
131        ];
132
133        for (value, shift, expected) in test_cases {
134            interp.push(Value::Number(value));
135            interp.push(Value::Number(shift));
136            shl_builtin(&mut interp).unwrap();
137            let result = interp.pop().unwrap();
138            assert!(
139                matches!(result, Value::Number(n) if n == expected),
140                "{} << {} should be {}, got {:?}",
141                value,
142                shift,
143                expected,
144                result
145            );
146        }
147    }
148
149    #[test]
150    fn test_shl_large_shifts() {
151        let mut interp = setup_interpreter();
152
153        let test_cases = [
154            (1.0, 10.0, 1024.0),    // 1 << 10 = 1024
155            (1.0, 20.0, 1048576.0), // 1 << 20 = 2^20
156        ];
157
158        for (value, shift, expected) in test_cases {
159            interp.push(Value::Number(value));
160            interp.push(Value::Number(shift));
161            shl_builtin(&mut interp).unwrap();
162            let result = interp.pop().unwrap();
163            assert!(
164                matches!(result, Value::Number(n) if n == expected),
165                "{} << {} should be {}, got {:?}",
166                value,
167                shift,
168                expected,
169                result
170            );
171        }
172    }
173
174    #[test]
175    fn test_shl_fractional_truncation() {
176        let mut interp = setup_interpreter();
177
178        // Both value and shift should be truncated
179        interp.push(Value::Number(3.7));
180        interp.push(Value::Number(2.9)); // Should truncate to 2
181
182        shl_builtin(&mut interp).unwrap();
183
184        let result = interp.pop().unwrap();
185        assert!(matches!(result, Value::Number(n) if n == 12.0)); // 3 << 2 = 12
186    }
187
188    #[test]
189    fn test_shl_multiplication_equivalence() {
190        let mut interp = setup_interpreter();
191
192        // Left shift by n is equivalent to multiplication by 2^n
193        let test_cases = [
194            (5.0, 1.0), // 5 << 1 = 5 * 2 = 10
195            (7.0, 2.0), // 7 << 2 = 7 * 4 = 28
196            (3.0, 4.0), // 3 << 4 = 3 * 16 = 48
197        ];
198
199        for (value, shift) in test_cases {
200            interp.push(Value::Number(value));
201            interp.push(Value::Number(shift));
202            shl_builtin(&mut interp).unwrap();
203            let shift_result = interp.pop().unwrap();
204
205            let expected = value * (2.0_f64.powf(shift));
206
207            assert!(
208                matches!(shift_result, Value::Number(n) if n == expected),
209                "{} << {} should equal {} * 2^{} = {}, got {:?}",
210                value,
211                shift,
212                value,
213                shift,
214                expected,
215                shift_result
216            );
217        }
218    }
219
220    #[test]
221    fn test_shl_excessive_shift_error() {
222        let mut interp = setup_interpreter();
223        interp.push(Value::Number(1.0));
224        interp.push(Value::Number(100.0)); // Shift amount too large
225
226        let result = shl_builtin(&mut interp);
227        assert!(matches!(result, Err(RuntimeError::DomainError(_))));
228    }
229
230    #[test]
231    fn test_shl_boundary_shift() {
232        let mut interp = setup_interpreter();
233        interp.push(Value::Number(1.0));
234        interp.push(Value::Number(63.0)); // Maximum allowed shift
235
236        let result = shl_builtin(&mut interp);
237        // Should succeed (might overflow but shouldn't error)
238        assert!(result.is_ok());
239    }
240
241    #[test]
242    fn test_shl_bit_pattern_preservation() {
243        let mut interp = setup_interpreter();
244
245        // Test that bit patterns are preserved correctly
246        interp.push(Value::Number(170.0)); // 10101010 in binary
247        interp.push(Value::Number(1.0)); // Shift left by 1
248
249        shl_builtin(&mut interp).unwrap();
250
251        let result = interp.pop().unwrap();
252        assert!(matches!(result, Value::Number(n) if n == 340.0)); // 101010100 in binary
253    }
254
255    #[test]
256    fn test_shl_stack_underflow() {
257        let mut interp = setup_interpreter();
258
259        let result = shl_builtin(&mut interp);
260        assert!(matches!(result, Err(RuntimeError::StackUnderflow)));
261
262        interp.push(Value::Number(5.0));
263        let result = shl_builtin(&mut interp);
264        assert!(matches!(result, Err(RuntimeError::StackUnderflow)));
265    }
266
267    #[test]
268    fn test_shl_type_error() {
269        let mut interp = setup_interpreter();
270        interp.push(Value::String("hello".into()));
271        interp.push(Value::Number(2.0));
272
273        let result = shl_builtin(&mut interp);
274        assert!(matches!(result, Err(RuntimeError::TypeError(_))));
275    }
276}