uni_core/primitives/
equals.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};
5use crate::compat::Rc;
6
7// RUST CONCEPT: Equality comparison across all value types
8// Stack-based equality: ( value1 value2 -- boolean )
9pub fn eq_builtin(interp: &mut Interpreter) -> Result<(), RuntimeError> {
10    let b = interp.pop()?;
11    let a = interp.pop()?;
12
13    let result = match (&a, &b) {
14        // Numeric types
15        (Value::Int32(i1), Value::Int32(i2)) => i1 == i2,
16        (Value::Number(n1), Value::Number(n2)) => n1 == n2,
17        (Value::Integer(i1), Value::Integer(i2)) => i1 == i2,
18        (Value::Rational(r1), Value::Rational(r2)) => r1 == r2,
19        #[cfg(feature = "complex_numbers")]
20        (Value::GaussianInt(re1, im1), Value::GaussianInt(re2, im2)) => re1 == re2 && im1 == im2,
21        #[cfg(feature = "complex_numbers")]
22        (Value::Complex(c1), Value::Complex(c2)) => c1 == c2,
23
24        // Other types
25        (Value::String(s1), Value::String(s2)) => s1 == s2,
26        (Value::Atom(a1), Value::Atom(a2)) => a1 == a2,
27        (Value::QuotedAtom(a1), Value::QuotedAtom(a2)) => a1 == a2,
28        (Value::Boolean(b1), Value::Boolean(b2)) => b1 == b2,
29        (Value::Null, Value::Null) => true,
30        (Value::Nil, Value::Nil) => true,
31        // Lists (Pairs) require deep comparison
32        (Value::Pair(car1, cdr1), Value::Pair(car2, cdr2)) => {
33            // Recursive equality check for cons cells
34            eq_values(car1, car2) && eq_values(cdr1, cdr2)
35        }
36        // I16Buffers compare by reference equality (same object)
37        (Value::I16Buffer(buf1), Value::I16Buffer(buf2)) => Rc::ptr_eq(buf1, buf2),
38        // Different types are never equal
39        _ => false,
40    };
41
42    interp.push(Value::Boolean(result));
43    Ok(())
44}
45
46// RUST CONCEPT: Recursive helper function for deep equality
47// This avoids stack overflow from the interpreter's stack operations
48fn eq_values(a: &Rc<Value>, b: &Rc<Value>) -> bool {
49    match (a.as_ref(), b.as_ref()) {
50        // Numeric types
51        (Value::Int32(i1), Value::Int32(i2)) => i1 == i2,
52        (Value::Number(n1), Value::Number(n2)) => n1 == n2,
53        (Value::Integer(i1), Value::Integer(i2)) => i1 == i2,
54        (Value::Rational(r1), Value::Rational(r2)) => r1 == r2,
55        #[cfg(feature = "complex_numbers")]
56        (Value::GaussianInt(re1, im1), Value::GaussianInt(re2, im2)) => re1 == re2 && im1 == im2,
57        #[cfg(feature = "complex_numbers")]
58        (Value::Complex(c1), Value::Complex(c2)) => c1 == c2,
59
60        // Other types
61        (Value::String(s1), Value::String(s2)) => s1 == s2,
62        (Value::Atom(a1), Value::Atom(a2)) => a1 == a2,
63        (Value::QuotedAtom(a1), Value::QuotedAtom(a2)) => a1 == a2,
64        (Value::Boolean(b1), Value::Boolean(b2)) => b1 == b2,
65        (Value::Null, Value::Null) => true,
66        (Value::Nil, Value::Nil) => true,
67        (Value::Pair(car1, cdr1), Value::Pair(car2, cdr2)) => {
68            eq_values(car1, car2) && eq_values(cdr1, cdr2)
69        }
70        // I16Buffers compare by reference equality (same object)
71        (Value::I16Buffer(buf1), Value::I16Buffer(buf2)) => Rc::ptr_eq(buf1, buf2),
72        _ => false,
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79    use crate::value::Value;
80
81    fn setup_interpreter() -> Interpreter {
82        Interpreter::new()
83    }
84
85    #[test]
86    fn test_eq_builtin_numbers() {
87        let mut interp = setup_interpreter();
88
89        // Test equal numbers
90        interp.push(Value::Number(42.0));
91        interp.push(Value::Number(42.0));
92        eq_builtin(&mut interp).unwrap();
93        let result = interp.pop().unwrap();
94        assert!(matches!(result, Value::Boolean(true)));
95
96        // Test unequal numbers
97        interp.push(Value::Number(3.0));
98        interp.push(Value::Number(5.0));
99        eq_builtin(&mut interp).unwrap();
100        let result = interp.pop().unwrap();
101        assert!(matches!(result, Value::Boolean(false)));
102    }
103
104    #[test]
105    fn test_eq_builtin_strings() {
106        let mut interp = setup_interpreter();
107
108        // Test equal strings
109        interp.push(Value::String("hello".into()));
110        interp.push(Value::String("hello".into()));
111        eq_builtin(&mut interp).unwrap();
112        let result = interp.pop().unwrap();
113        assert!(matches!(result, Value::Boolean(true)));
114
115        // Test unequal strings
116        interp.push(Value::String("hello".into()));
117        interp.push(Value::String("world".into()));
118        eq_builtin(&mut interp).unwrap();
119        let result = interp.pop().unwrap();
120        assert!(matches!(result, Value::Boolean(false)));
121    }
122
123    #[test]
124    fn test_eq_builtin_booleans() {
125        let mut interp = setup_interpreter();
126
127        // Test equal booleans
128        interp.push(Value::Boolean(true));
129        interp.push(Value::Boolean(true));
130        eq_builtin(&mut interp).unwrap();
131        let result = interp.pop().unwrap();
132        assert!(matches!(result, Value::Boolean(true)));
133
134        // Test unequal booleans
135        interp.push(Value::Boolean(true));
136        interp.push(Value::Boolean(false));
137        eq_builtin(&mut interp).unwrap();
138        let result = interp.pop().unwrap();
139        assert!(matches!(result, Value::Boolean(false)));
140    }
141
142    #[test]
143    fn test_eq_builtin_null() {
144        let mut interp = setup_interpreter();
145
146        // Test null vs null
147        interp.push(Value::Null);
148        interp.push(Value::Null);
149        eq_builtin(&mut interp).unwrap();
150        let result = interp.pop().unwrap();
151        assert!(matches!(result, Value::Boolean(true)));
152
153        // Test null vs other types
154        interp.push(Value::Null);
155        interp.push(Value::Boolean(false));
156        eq_builtin(&mut interp).unwrap();
157        let result = interp.pop().unwrap();
158        assert!(matches!(result, Value::Boolean(false)));
159    }
160
161    #[test]
162    fn test_eq_builtin_different_types() {
163        let mut interp = setup_interpreter();
164
165        // Numbers and strings should never be equal
166        interp.push(Value::Number(42.0));
167        interp.push(Value::String("42".into()));
168        eq_builtin(&mut interp).unwrap();
169        let result = interp.pop().unwrap();
170        assert!(matches!(result, Value::Boolean(false)));
171
172        // Boolean true and number 1 should never be equal
173        interp.push(Value::Boolean(true));
174        interp.push(Value::Number(1.0));
175        eq_builtin(&mut interp).unwrap();
176        let result = interp.pop().unwrap();
177        assert!(matches!(result, Value::Boolean(false)));
178    }
179
180    #[test]
181    fn test_eq_builtin_lists() {
182        let mut interp = setup_interpreter();
183
184        // Create identical lists [1, 2]
185        let list1 = interp.make_list(vec![Value::Number(1.0), Value::Number(2.0)]);
186        let list2 = interp.make_list(vec![Value::Number(1.0), Value::Number(2.0)]);
187
188        interp.push(list1);
189        interp.push(list2);
190        eq_builtin(&mut interp).unwrap();
191        let result = interp.pop().unwrap();
192        assert!(matches!(result, Value::Boolean(true)));
193
194        // Create different lists [1, 2] vs [1, 3]
195        let list3 = interp.make_list(vec![Value::Number(1.0), Value::Number(2.0)]);
196        let list4 = interp.make_list(vec![Value::Number(1.0), Value::Number(3.0)]);
197
198        interp.push(list3);
199        interp.push(list4);
200        eq_builtin(&mut interp).unwrap();
201        let result = interp.pop().unwrap();
202        assert!(matches!(result, Value::Boolean(false)));
203    }
204
205    #[test]
206    fn test_eq_builtin_stack_underflow() {
207        let mut interp = setup_interpreter();
208
209        // Test with empty stack
210        let result = eq_builtin(&mut interp);
211        assert!(matches!(result, Err(RuntimeError::StackUnderflow)));
212
213        // Test with only one element
214        interp.push(Value::Number(5.0));
215        let result = eq_builtin(&mut interp);
216        assert!(matches!(result, Err(RuntimeError::StackUnderflow)));
217    }
218
219    #[test]
220    fn test_eq_builtin_edge_cases_boolean_null() {
221        let mut interp = setup_interpreter();
222
223        // Edge case: null vs boolean false (should be false - different types)
224        interp.push(Value::Null);
225        interp.push(Value::Boolean(false));
226        eq_builtin(&mut interp).unwrap();
227        let result = interp.pop().unwrap();
228        assert!(matches!(result, Value::Boolean(false)));
229
230        // Edge case: null vs number 0 (should be false - different types)
231        interp.push(Value::Null);
232        interp.push(Value::Number(0.0));
233        eq_builtin(&mut interp).unwrap();
234        let result = interp.pop().unwrap();
235        assert!(matches!(result, Value::Boolean(false)));
236
237        // Edge case: boolean true vs number 1 (should be false - different types)
238        interp.push(Value::Boolean(true));
239        interp.push(Value::Number(1.0));
240        eq_builtin(&mut interp).unwrap();
241        let result = interp.pop().unwrap();
242        assert!(matches!(result, Value::Boolean(false)));
243    }
244}