uni_core/primitives/
null.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, Value};
5
6// RUST CONCEPT: Type checking predicates
7// null? ( value -- boolean ) - Check if value is null
8// Returns true only for Value::Null, false for all other types including Nil
9pub fn null_predicate_builtin(interp: &mut Interpreter) -> Result<(), RuntimeError> {
10    let value = interp.pop()?;
11    let is_null = interp.is_null(&value);
12    interp.push(Value::Boolean(is_null));
13    Ok(())
14}
15
16#[cfg(test)]
17mod tests {
18    use super::*;
19    use crate::value::Value;
20    use crate::compat::Rc;
21
22    fn setup_interpreter() -> Interpreter {
23        Interpreter::new()
24    }
25
26    #[test]
27    fn test_null_predicate_builtin() {
28        let mut interp = setup_interpreter();
29
30        // Test null? with null value -> true
31        interp.push(Value::Null);
32        null_predicate_builtin(&mut interp).unwrap();
33        let result = interp.pop().unwrap();
34        assert!(matches!(result, Value::Boolean(true)));
35
36        // Test null? with non-null values -> false
37        let test_cases = vec![
38            Value::Boolean(false),
39            Value::Boolean(true),
40            Value::Number(0.0),
41            Value::Number(42.0),
42            Value::String("".into()),
43            Value::String("hello".into()),
44            Value::Nil, // Nil is NOT null
45            Value::Atom(interp.intern_atom("test")),
46            Value::QuotedAtom(interp.intern_atom("quoted")),
47            Value::Pair(Rc::new(Value::Number(1.0)), Rc::new(Value::Nil)),
48        ];
49
50        for (i, test_value) in test_cases.into_iter().enumerate() {
51            interp.push(test_value.clone());
52            null_predicate_builtin(&mut interp).unwrap();
53            let result = interp.pop().unwrap();
54            assert!(
55                matches!(result, Value::Boolean(false)),
56                "Expected false for non-null value #{}: {:?}",
57                i,
58                test_value
59            );
60        }
61    }
62
63    #[test]
64    fn test_null_predicate_null_vs_nil_distinction() {
65        let mut interp = setup_interpreter();
66
67        // null is null
68        interp.push(Value::Null);
69        null_predicate_builtin(&mut interp).unwrap();
70        let result = interp.pop().unwrap();
71        assert!(matches!(result, Value::Boolean(true)));
72
73        // nil is NOT null
74        interp.push(Value::Nil);
75        null_predicate_builtin(&mut interp).unwrap();
76        let result = interp.pop().unwrap();
77        assert!(matches!(result, Value::Boolean(false)));
78
79        // boolean false is NOT null
80        interp.push(Value::Boolean(false));
81        null_predicate_builtin(&mut interp).unwrap();
82        let result = interp.pop().unwrap();
83        assert!(matches!(result, Value::Boolean(false)));
84
85        // number 0 is NOT null
86        interp.push(Value::Number(0.0));
87        null_predicate_builtin(&mut interp).unwrap();
88        let result = interp.pop().unwrap();
89        assert!(matches!(result, Value::Boolean(false)));
90
91        // empty string is NOT null
92        interp.push(Value::String("".into()));
93        null_predicate_builtin(&mut interp).unwrap();
94        let result = interp.pop().unwrap();
95        assert!(matches!(result, Value::Boolean(false)));
96    }
97
98    #[test]
99    fn test_null_predicate_edge_cases() {
100        let mut interp = setup_interpreter();
101
102        // Test with various edge case values
103        let edge_cases = vec![
104            Value::Number(-0.0),           // negative zero
105            Value::Number(f64::NAN),       // NaN
106            Value::Number(f64::INFINITY),  // infinity
107            Value::String("null".into()),  // string "null"
108            Value::String("false".into()), // string "false"
109        ];
110
111        for (i, test_value) in edge_cases.into_iter().enumerate() {
112            interp.push(test_value.clone());
113            null_predicate_builtin(&mut interp).unwrap();
114            let result = interp.pop().unwrap();
115            assert!(
116                matches!(result, Value::Boolean(false)),
117                "Expected false for edge case #{}: {:?}",
118                i,
119                test_value
120            );
121        }
122    }
123
124    #[test]
125    fn test_null_predicate_list_with_null() {
126        let mut interp = setup_interpreter();
127
128        // Test list containing null -> false (list itself is not null)
129        let list_with_null = interp.make_list(vec![Value::Null, Value::Number(1.0)]);
130        interp.push(list_with_null);
131        null_predicate_builtin(&mut interp).unwrap();
132        let result = interp.pop().unwrap();
133        assert!(matches!(result, Value::Boolean(false)));
134
135        // Test empty list -> false (nil is not null)
136        interp.push(Value::Nil);
137        null_predicate_builtin(&mut interp).unwrap();
138        let result = interp.pop().unwrap();
139        assert!(matches!(result, Value::Boolean(false)));
140    }
141
142    #[test]
143    fn test_null_predicate_atom_types() {
144        let mut interp = setup_interpreter();
145
146        // Test regular atom
147        let atom = interp.intern_atom("null");
148        interp.push(Value::Atom(atom));
149        null_predicate_builtin(&mut interp).unwrap();
150        let result = interp.pop().unwrap();
151        assert!(matches!(result, Value::Boolean(false)));
152
153        // Test quoted atom
154        let quoted_atom = interp.intern_atom("null");
155        interp.push(Value::QuotedAtom(quoted_atom));
156        null_predicate_builtin(&mut interp).unwrap();
157        let result = interp.pop().unwrap();
158        assert!(matches!(result, Value::Boolean(false)));
159    }
160
161    #[test]
162    fn test_null_predicate_stack_underflow() {
163        let mut interp = setup_interpreter();
164
165        // Test with empty stack
166        let result = null_predicate_builtin(&mut interp);
167        assert!(matches!(result, Err(RuntimeError::StackUnderflow)));
168    }
169
170    #[test]
171    fn test_null_predicate_multiple_calls() {
172        let mut interp = setup_interpreter();
173
174        // Test multiple null checks in sequence
175        interp.push(Value::Null);
176        interp.push(Value::Number(42.0));
177        interp.push(Value::Null);
178
179        // Check third value (null)
180        null_predicate_builtin(&mut interp).unwrap();
181        let result = interp.pop().unwrap();
182        assert!(matches!(result, Value::Boolean(true)));
183
184        // Check second value (number)
185        null_predicate_builtin(&mut interp).unwrap();
186        let result = interp.pop().unwrap();
187        assert!(matches!(result, Value::Boolean(false)));
188
189        // Check first value (null)
190        null_predicate_builtin(&mut interp).unwrap();
191        let result = interp.pop().unwrap();
192        assert!(matches!(result, Value::Boolean(true)));
193    }
194}