uni_core/primitives/
tail.rs

1// RUST CONCEPT: Modular primitive organization
2// Each primitive gets its own file with implementation and tests
3use crate::compat::ToString;
4use crate::interpreter::Interpreter;
5use crate::value::{RuntimeError, Value};
6
7// RUST CONCEPT: List operations - tail builtin
8// tail ( list -- list ) - Get rest of list after first element
9// Example: [1 2 3] tail -> [2 3]
10// Example: [42] tail -> []
11// Example: [] tail -> []
12pub fn tail_builtin(interp: &mut Interpreter) -> Result<(), RuntimeError> {
13    let list = interp.pop()?;
14
15    match list {
16        Value::Pair(_, cdr) => {
17            // Return the rest of the list (cdr)
18            interp.push((*cdr).clone());
19            Ok(())
20        }
21        Value::Nil => {
22            // Tail of empty list is empty list
23            interp.push(Value::Nil);
24            Ok(())
25        }
26        _ => {
27            // Not a list
28            Err(RuntimeError::TypeError("tail expects a list".to_string()))
29        }
30    }
31}
32
33#[cfg(test)]
34mod tests {
35    use super::*;
36    use crate::value::Value;
37
38    fn setup_interpreter() -> Interpreter {
39        Interpreter::new()
40    }
41
42    #[test]
43    fn test_tail_builtin_basic() {
44        let mut interp = setup_interpreter();
45
46        // Test tail of [1 2 3] -> [2 3]
47        let list = interp.make_list(vec![
48            Value::Number(1.0),
49            Value::Number(2.0),
50            Value::Number(3.0),
51        ]);
52        interp.push(list);
53        tail_builtin(&mut interp).unwrap();
54
55        let result = interp.pop().unwrap();
56
57        // Verify structure: [2 3]
58        match result {
59            Value::Pair(car, cdr) => {
60                assert!(matches!(car.as_ref(), Value::Number(n) if *n == 2.0));
61                match cdr.as_ref() {
62                    Value::Pair(car2, cdr2) => {
63                        assert!(matches!(car2.as_ref(), Value::Number(n) if *n == 3.0));
64                        assert!(matches!(cdr2.as_ref(), Value::Nil));
65                    }
66                    _ => panic!("Expected second element in tail"),
67                }
68            }
69            _ => panic!("Expected list structure for tail result"),
70        }
71    }
72
73    #[test]
74    fn test_tail_builtin_single_element() {
75        let mut interp = setup_interpreter();
76
77        // Test tail of [42] -> []
78        let single = interp.make_list(vec![Value::Number(42.0)]);
79        interp.push(single);
80        tail_builtin(&mut interp).unwrap();
81
82        let result = interp.pop().unwrap();
83        assert!(matches!(result, Value::Nil));
84    }
85
86    #[test]
87    fn test_tail_builtin_empty_list() {
88        let mut interp = setup_interpreter();
89
90        // Test tail of [] -> []
91        interp.push(Value::Nil);
92        tail_builtin(&mut interp).unwrap();
93
94        let result = interp.pop().unwrap();
95        assert!(matches!(result, Value::Nil));
96    }
97
98    #[test]
99    fn test_tail_builtin_mixed_types() {
100        let mut interp = setup_interpreter();
101
102        // Test tail of ["hello" 42 true] -> [42 true]
103        let mixed_list = interp.make_list(vec![
104            Value::String("hello".into()),
105            Value::Number(42.0),
106            Value::Boolean(true),
107        ]);
108        interp.push(mixed_list);
109        tail_builtin(&mut interp).unwrap();
110
111        let result = interp.pop().unwrap();
112
113        // Verify structure: [42 true]
114        match result {
115            Value::Pair(car, cdr) => {
116                assert!(matches!(car.as_ref(), Value::Number(n) if *n == 42.0));
117                match cdr.as_ref() {
118                    Value::Pair(car2, cdr2) => {
119                        assert!(matches!(car2.as_ref(), Value::Boolean(true)));
120                        assert!(matches!(cdr2.as_ref(), Value::Nil));
121                    }
122                    _ => panic!("Expected second element in tail"),
123                }
124            }
125            _ => panic!("Expected list structure for tail result"),
126        }
127    }
128
129    #[test]
130    fn test_tail_builtin_nested_lists() {
131        let mut interp = setup_interpreter();
132
133        // Test tail with nested structure: [1 [2 3] 4] -> [[2 3] 4]
134        let inner_list = interp.make_list(vec![Value::Number(2.0), Value::Number(3.0)]);
135        let outer_list = interp.make_list(vec![Value::Number(1.0), inner_list, Value::Number(4.0)]);
136
137        interp.push(outer_list);
138        tail_builtin(&mut interp).unwrap();
139
140        let result = interp.pop().unwrap();
141
142        // Verify structure: [[2 3] 4]
143        match result {
144            Value::Pair(car, cdr) => {
145                // First element should be [2 3]
146                assert!(matches!(car.as_ref(), Value::Pair(_, _)));
147                match cdr.as_ref() {
148                    Value::Pair(car2, cdr2) => {
149                        assert!(matches!(car2.as_ref(), Value::Number(n) if *n == 4.0));
150                        assert!(matches!(cdr2.as_ref(), Value::Nil));
151                    }
152                    _ => panic!("Expected second element in tail"),
153                }
154            }
155            _ => panic!("Expected list structure for tail result"),
156        }
157    }
158
159    #[test]
160    fn test_tail_builtin_improper_list() {
161        let mut interp = setup_interpreter();
162
163        // Test tail of improper list (1 . 2) -> 2
164        use crate::compat::Rc;
165        let improper = Value::Pair(Rc::new(Value::Number(1.0)), Rc::new(Value::Number(2.0)));
166
167        interp.push(improper);
168        tail_builtin(&mut interp).unwrap();
169
170        let result = interp.pop().unwrap();
171        assert!(matches!(result, Value::Number(n) if n == 2.0));
172    }
173
174    #[test]
175    fn test_tail_builtin_non_list_error() {
176        let mut interp = setup_interpreter();
177
178        // Test tail of number should error
179        interp.push(Value::Number(42.0));
180        let result = tail_builtin(&mut interp);
181        assert!(
182            matches!(result, Err(RuntimeError::TypeError(msg)) if msg.contains("tail expects a list"))
183        );
184
185        // Test tail of atom should error
186        let atom = interp.intern_atom("test");
187        interp.push(Value::Atom(atom));
188        let result = tail_builtin(&mut interp);
189        assert!(
190            matches!(result, Err(RuntimeError::TypeError(msg)) if msg.contains("tail expects a list"))
191        );
192
193        // Test tail of string should error
194        interp.push(Value::String("hello".into()));
195        let result = tail_builtin(&mut interp);
196        assert!(
197            matches!(result, Err(RuntimeError::TypeError(msg)) if msg.contains("tail expects a list"))
198        );
199    }
200
201    #[test]
202    fn test_tail_builtin_stack_underflow() {
203        let mut interp = setup_interpreter();
204
205        // Test with empty stack
206        let result = tail_builtin(&mut interp);
207        assert!(matches!(result, Err(RuntimeError::StackUnderflow)));
208    }
209
210    #[test]
211    fn test_tail_builtin_preserves_structure() {
212        let mut interp = setup_interpreter();
213
214        // Verify that tail preserves the original list structure (shares cdr)
215        let original_list = interp.make_list(vec![
216            Value::Number(1.0),
217            Value::Number(2.0),
218            Value::Number(3.0),
219        ]);
220
221        // Take tail
222        interp.push(original_list);
223        tail_builtin(&mut interp).unwrap();
224        let tail_result = interp.pop().unwrap();
225
226        // Verify tail is exactly [2 3] with correct structure
227        match tail_result {
228            Value::Pair(car, cdr) => {
229                assert!(matches!(car.as_ref(), Value::Number(n) if *n == 2.0));
230                match cdr.as_ref() {
231                    Value::Pair(car2, cdr2) => {
232                        assert!(matches!(car2.as_ref(), Value::Number(n) if *n == 3.0));
233                        assert!(matches!(cdr2.as_ref(), Value::Nil));
234                    }
235                    _ => panic!("Tail structure incorrect"),
236                }
237            }
238            _ => panic!("Expected proper tail structure"),
239        }
240    }
241}