uni_core/primitives/
pick.rs

1// RUST CONCEPT: Modular primitive organization
2// Each primitive gets its own file with implementation and tests
3use crate::interpreter::Interpreter;
4use crate::value::RuntimeError;
5
6// RUST CONCEPT: ANS Forth pick primitive
7// pick ( n -- value ) - Copy the nth item from the stack to the top
8// n=0: dup, n=1: over, n=2: pick third item, etc.
9// Example: 1 2 3 4  2 pick  ->  1 2 3 4 2 (copied item at depth 2 to top)
10pub fn pick_builtin(interp: &mut Interpreter) -> Result<(), RuntimeError> {
11    let n = interp.pop_integer()?;
12
13    // RUST CONCEPT: Bounds checking
14    // We need at least n+1 items on the remaining stack
15    if interp.stack.len() < n + 1 {
16        return Err(RuntimeError::StackUnderflow);
17    }
18
19    // RUST CONCEPT: Vec indexing from the end
20    // Get the item at position n from the top (0-indexed)
21    let stack_len = interp.stack.len();
22    let item = interp.stack[stack_len - n - 1].clone();
23
24    // Push a copy to the top
25    interp.stack.push(item);
26
27    Ok(())
28}
29
30#[cfg(test)]
31mod tests {
32    use super::*;
33    use crate::value::Value;
34
35    fn setup_interpreter() -> Interpreter {
36        Interpreter::new()
37    }
38
39    #[test]
40    fn test_pick_builtin_dup() {
41        let mut interp = setup_interpreter();
42
43        // Test: 1 2 3  0 pick  ->  1 2 3 3 (dup)
44        interp.push(Value::Number(1.0));
45        interp.push(Value::Number(2.0));
46        interp.push(Value::Number(3.0));
47        interp.push(Value::Number(0.0));
48        pick_builtin(&mut interp).unwrap();
49
50        // Stack should be: 1 2 3 3
51        let top = interp.pop().unwrap();
52        assert!(matches!(top, Value::Number(n) if n == 3.0));
53
54        let second = interp.pop().unwrap();
55        assert!(matches!(second, Value::Number(n) if n == 3.0));
56
57        let third = interp.pop().unwrap();
58        assert!(matches!(third, Value::Number(n) if n == 2.0));
59
60        let fourth = interp.pop().unwrap();
61        assert!(matches!(fourth, Value::Number(n) if n == 1.0));
62    }
63
64    #[test]
65    fn test_pick_builtin_over() {
66        let mut interp = setup_interpreter();
67
68        // Test: 1 2 3  1 pick  ->  1 2 3 2 (over)
69        interp.push(Value::Number(1.0));
70        interp.push(Value::Number(2.0));
71        interp.push(Value::Number(3.0));
72        interp.push(Value::Number(1.0));
73        pick_builtin(&mut interp).unwrap();
74
75        // Stack should be: 1 2 3 2
76        let top = interp.pop().unwrap();
77        assert!(matches!(top, Value::Number(n) if n == 2.0));
78
79        let second = interp.pop().unwrap();
80        assert!(matches!(second, Value::Number(n) if n == 3.0));
81
82        let third = interp.pop().unwrap();
83        assert!(matches!(third, Value::Number(n) if n == 2.0));
84
85        let fourth = interp.pop().unwrap();
86        assert!(matches!(fourth, Value::Number(n) if n == 1.0));
87    }
88
89    #[test]
90    fn test_pick_builtin_deep() {
91        let mut interp = setup_interpreter();
92
93        // Test: 1 2 3 4 5  3 pick  ->  1 2 3 4 5 2
94        interp.push(Value::Number(1.0));
95        interp.push(Value::Number(2.0));
96        interp.push(Value::Number(3.0));
97        interp.push(Value::Number(4.0));
98        interp.push(Value::Number(5.0));
99        interp.push(Value::Number(3.0));
100        pick_builtin(&mut interp).unwrap();
101
102        let top = interp.pop().unwrap();
103        assert!(matches!(top, Value::Number(n) if n == 2.0));
104
105        let second = interp.pop().unwrap();
106        assert!(matches!(second, Value::Number(n) if n == 5.0));
107
108        // Verify original stack is preserved
109        let third = interp.pop().unwrap();
110        assert!(matches!(third, Value::Number(n) if n == 4.0));
111
112        let fourth = interp.pop().unwrap();
113        assert!(matches!(fourth, Value::Number(n) if n == 3.0));
114
115        let fifth = interp.pop().unwrap();
116        assert!(matches!(fifth, Value::Number(n) if n == 2.0));
117
118        let sixth = interp.pop().unwrap();
119        assert!(matches!(sixth, Value::Number(n) if n == 1.0));
120    }
121
122    #[test]
123    fn test_pick_builtin_mixed_types() {
124        let mut interp = setup_interpreter();
125
126        // Test with mixed value types
127        interp.push(Value::String("hello".into()));
128        interp.push(Value::Boolean(true));
129        interp.push(Value::Number(42.0));
130        interp.push(Value::Number(2.0)); // pick depth
131        pick_builtin(&mut interp).unwrap();
132
133        // Should copy "hello" to top
134        let top = interp.pop().unwrap();
135        assert!(matches!(top, Value::String(s) if s.as_ref() == "hello"));
136
137        let second = interp.pop().unwrap();
138        assert!(matches!(second, Value::Number(n) if n == 42.0));
139
140        let third = interp.pop().unwrap();
141        assert!(matches!(third, Value::Boolean(true)));
142
143        // Original should still be there
144        let fourth = interp.pop().unwrap();
145        assert!(matches!(fourth, Value::String(s) if s.as_ref() == "hello"));
146    }
147
148    #[test]
149    fn test_pick_builtin_single_element() {
150        let mut interp = setup_interpreter();
151
152        // Test: 42  0 pick  ->  42 42
153        interp.push(Value::Number(42.0));
154        interp.push(Value::Number(0.0));
155        pick_builtin(&mut interp).unwrap();
156
157        let top = interp.pop().unwrap();
158        assert!(matches!(top, Value::Number(n) if n == 42.0));
159
160        let second = interp.pop().unwrap();
161        assert!(matches!(second, Value::Number(n) if n == 42.0));
162    }
163
164    #[test]
165    fn test_pick_builtin_stack_underflow() {
166        let mut interp = setup_interpreter();
167
168        // Test with empty stack
169        let result = pick_builtin(&mut interp);
170        assert!(matches!(result, Err(RuntimeError::StackUnderflow)));
171
172        // Test when n >= available items
173        interp.push(Value::Number(1.0));
174        interp.push(Value::Number(1.0)); // Want to pick item at depth 1 but only have 1 item total
175        let result = pick_builtin(&mut interp);
176        assert!(matches!(result, Err(RuntimeError::StackUnderflow)));
177    }
178
179    #[test]
180    fn test_pick_builtin_type_error() {
181        let mut interp = setup_interpreter();
182
183        // Test with non-number pick count
184        interp.push(Value::Number(1.0));
185        interp.push(Value::Number(2.0));
186        interp.push(Value::String("not a number".into()));
187        let result = pick_builtin(&mut interp);
188        assert!(matches!(result, Err(RuntimeError::TypeError(_))));
189    }
190
191    #[test]
192    fn test_pick_builtin_preserves_original_stack() {
193        let mut interp = setup_interpreter();
194
195        // Verify that pick doesn't modify the original stack items
196        interp.push(Value::Number(10.0));
197        interp.push(Value::Number(20.0));
198        interp.push(Value::Number(30.0));
199        interp.push(Value::Number(1.0)); // pick second item (20)
200        pick_builtin(&mut interp).unwrap();
201
202        // Should have: 10 20 30 20
203        let copied = interp.pop().unwrap();
204        assert!(matches!(copied, Value::Number(n) if n == 20.0));
205
206        // Original stack should be intact
207        let original_top = interp.pop().unwrap();
208        assert!(matches!(original_top, Value::Number(n) if n == 30.0));
209
210        let original_second = interp.pop().unwrap();
211        assert!(matches!(original_second, Value::Number(n) if n == 20.0));
212
213        let original_third = interp.pop().unwrap();
214        assert!(matches!(original_third, Value::Number(n) if n == 10.0));
215    }
216}