uni_core/primitives/
head.rs1use crate::compat::ToString;
4use crate::interpreter::Interpreter;
5use crate::value::{RuntimeError, Value};
6
7pub fn head_builtin(interp: &mut Interpreter) -> Result<(), RuntimeError> {
11 let list = interp.pop()?;
12
13 match list {
14 Value::Pair(car, _cdr) => {
15 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 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 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 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 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 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 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 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 let _ = interp.pop();
145 }
146 }
147
148 #[test]
149 fn test_head_builtin_stack_underflow() {
150 let mut interp = setup_interpreter();
151
152 let result = head_builtin(&mut interp);
154 assert!(matches!(result, Err(RuntimeError::StackUnderflow)));
155 }
156}