uni_core/primitives/
head.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 head extraction (car in Lisp terminology)
8// Stack-based head: ( list -- first-element )
9// Returns the first element of a list, or error if not a list
10pub fn head_builtin(interp: &mut Interpreter) -> Result<(), RuntimeError> {
11    let list = interp.pop()?;
12
13    match list {
14        Value::Pair(car, _cdr) => {
15            // RUST CONCEPT: Cloning Rc just increments reference count
16            interp.push((*car).clone());
17            Ok(())
18        }
19        Value::Nil => Err(RuntimeError::TypeError(
20            "Cannot take head of empty list".to_string(),
21        )),
22        _ => Err(RuntimeError::TypeError("head requires a list".to_string())),
23    }
24}
25
26#[cfg(test)]
27mod tests {
28    use super::*;
29    use crate::value::Value;
30
31    fn setup_interpreter() -> Interpreter {
32        Interpreter::new()
33    }
34
35    #[test]
36    fn test_head_builtin_basic() {
37        let mut interp = setup_interpreter();
38
39        // Create list [1, 2, 3] and take head
40        let list = interp.make_list(vec![
41            Value::Number(1.0),
42            Value::Number(2.0),
43            Value::Number(3.0),
44        ]);
45
46        interp.push(list);
47        head_builtin(&mut interp).unwrap();
48
49        let result = interp.pop().unwrap();
50        assert!(matches!(result, Value::Number(n) if n == 1.0));
51    }
52
53    #[test]
54    fn test_head_builtin_single_element() {
55        let mut interp = setup_interpreter();
56
57        // Create single-element list ["hello"] and take head
58        let list = interp.make_list(vec![Value::String("hello".into())]);
59
60        interp.push(list);
61        head_builtin(&mut interp).unwrap();
62
63        let result = interp.pop().unwrap();
64        assert!(matches!(result, Value::String(s) if s.as_ref() == "hello"));
65    }
66
67    #[test]
68    fn test_head_builtin_mixed_types() {
69        let mut interp = setup_interpreter();
70
71        // Create mixed-type list [true, 42, "world"] and take head
72        let list = interp.make_list(vec![
73            Value::Boolean(true),
74            Value::Number(42.0),
75            Value::String("world".into()),
76        ]);
77
78        interp.push(list);
79        head_builtin(&mut interp).unwrap();
80
81        let result = interp.pop().unwrap();
82        assert!(matches!(result, Value::Boolean(true)));
83    }
84
85    #[test]
86    fn test_head_builtin_nested_list() {
87        let mut interp = setup_interpreter();
88
89        // Create nested list [[1, 2], 3] and take head
90        let inner_list = interp.make_list(vec![Value::Number(1.0), Value::Number(2.0)]);
91        let outer_list = interp.make_list(vec![inner_list, Value::Number(3.0)]);
92
93        interp.push(outer_list);
94        head_builtin(&mut interp).unwrap();
95
96        let result = interp.pop().unwrap();
97        // Should get [1, 2] as the head
98        match result {
99            Value::Pair(car, cdr) => {
100                assert!(matches!(car.as_ref(), Value::Number(n) if *n == 1.0));
101                match cdr.as_ref() {
102                    Value::Pair(car2, cdr2) => {
103                        assert!(matches!(car2.as_ref(), Value::Number(n) if *n == 2.0));
104                        assert!(matches!(cdr2.as_ref(), Value::Nil));
105                    }
106                    _ => panic!("Expected second element in inner list"),
107                }
108            }
109            _ => panic!("Expected inner list as head"),
110        }
111    }
112
113    #[test]
114    fn test_head_builtin_empty_list() {
115        let mut interp = setup_interpreter();
116
117        // Test head of empty list
118        interp.push(Value::Nil);
119        let result = head_builtin(&mut interp);
120        assert!(matches!(result, Err(RuntimeError::TypeError(msg)) if msg.contains("empty list")));
121    }
122
123    #[test]
124    fn test_head_builtin_non_list() {
125        let mut interp = setup_interpreter();
126
127        // Test head of non-list values
128        let test_cases = vec![
129            Value::Number(42.0),
130            Value::String("not a list".into()),
131            Value::Boolean(true),
132            Value::Null,
133            Value::Atom(interp.intern_atom("atom")),
134        ];
135
136        for test_value in test_cases {
137            interp.push(test_value);
138            let result = head_builtin(&mut interp);
139            assert!(
140                matches!(result, Err(RuntimeError::TypeError(msg)) if msg.contains("requires a list"))
141            );
142
143            // Clean up stack for next test
144            let _ = interp.pop();
145        }
146    }
147
148    #[test]
149    fn test_head_builtin_stack_underflow() {
150        let mut interp = setup_interpreter();
151
152        // Test with empty stack
153        let result = head_builtin(&mut interp);
154        assert!(matches!(result, Err(RuntimeError::StackUnderflow)));
155    }
156}