1use logicaffeine_base::numeric;
4use logicaffeine_base::{BigInt, Rational};
5
6use crate::ast::stmt::BinaryOpKind;
7use crate::interpreter::RuntimeValue;
8
9pub fn values_equal(left: &RuntimeValue, right: &RuntimeValue) -> bool {
24 values_equal_depth(left, right, 0)
25}
26
27const EQ_MAX_DEPTH: usize = 256;
30
31fn values_equal_depth(left: &RuntimeValue, right: &RuntimeValue, depth: usize) -> bool {
32 if depth > EQ_MAX_DEPTH {
33 return false;
34 }
35 match (left, right) {
36 (RuntimeValue::Int(a), RuntimeValue::Int(b)) => a == b,
37 (RuntimeValue::BigInt(a), RuntimeValue::BigInt(b)) => a == b,
40 (RuntimeValue::Rational(a), RuntimeValue::Rational(b)) => a == b,
43 (RuntimeValue::Decimal(a), RuntimeValue::Decimal(b)) => a == b,
46 (RuntimeValue::Complex(a), RuntimeValue::Complex(b)) => a == b,
49 (RuntimeValue::Modular(a), RuntimeValue::Modular(b)) => a == b,
52 (RuntimeValue::Float(a), RuntimeValue::Float(b)) => a == b,
54 (RuntimeValue::Int(a), RuntimeValue::Float(b))
56 | (RuntimeValue::Float(b), RuntimeValue::Int(a)) => {
57 numeric::cmp_i64_f64_exact(*a, *b) == Some(std::cmp::Ordering::Equal)
58 }
59 (RuntimeValue::BigInt(a), RuntimeValue::Float(b))
60 | (RuntimeValue::Float(b), RuntimeValue::BigInt(a)) => {
61 numeric::cmp_bigint_f64_exact(a, *b) == Some(std::cmp::Ordering::Equal)
62 }
63 (RuntimeValue::Rational(a), RuntimeValue::Float(b))
64 | (RuntimeValue::Float(b), RuntimeValue::Rational(a)) => {
65 numeric::cmp_rational_f64_exact(a, *b) == Some(std::cmp::Ordering::Equal)
66 }
67 (RuntimeValue::Bool(a), RuntimeValue::Bool(b)) => a == b,
68 (RuntimeValue::Text(a), RuntimeValue::Text(b)) => **a == **b,
69 (RuntimeValue::Char(a), RuntimeValue::Char(b)) => a == b,
70 (RuntimeValue::Nothing, RuntimeValue::Nothing) => true,
71 (RuntimeValue::Duration(a), RuntimeValue::Duration(b)) => a == b,
72 (RuntimeValue::Date(a), RuntimeValue::Date(b)) => a == b,
73 (RuntimeValue::Moment(a), RuntimeValue::Moment(b)) => a == b,
74 (
75 RuntimeValue::Span { months: m1, days: d1 },
76 RuntimeValue::Span { months: m2, days: d2 },
77 ) => m1 == m2 && d1 == d2,
78 (RuntimeValue::Time(a), RuntimeValue::Time(b)) => a == b,
79 (RuntimeValue::Word(a), RuntimeValue::Word(b)) => a == b,
81 (RuntimeValue::Money(a), RuntimeValue::Money(b)) => a == b,
84 (RuntimeValue::Quantity(a), RuntimeValue::Quantity(b)) => a.q == b.q,
85 (RuntimeValue::Uuid(a), RuntimeValue::Uuid(b)) => a == b,
86 (RuntimeValue::Inductive(a), RuntimeValue::Inductive(b)) => {
87 a.inductive_type == b.inductive_type
88 && a.constructor == b.constructor
89 && a.args.len() == b.args.len()
90 && a.args.iter().zip(b.args.iter()).all(|(x, y)| values_equal_depth(x, y, depth + 1))
91 }
92 (RuntimeValue::List(a), RuntimeValue::List(b)) => {
94 if std::rc::Rc::ptr_eq(a, b) {
95 return true;
96 }
97 let (a, b) = (a.borrow(), b.borrow());
98 a.len() == b.len()
99 && (0..a.len()).all(|i| match (a.get(i), b.get(i)) {
100 (Some(x), Some(y)) => values_equal_depth(&x, &y, depth + 1),
101 _ => false,
102 })
103 }
104 (RuntimeValue::Tuple(a), RuntimeValue::Tuple(b)) => {
105 a.len() == b.len()
106 && a.iter().zip(b.iter()).all(|(x, y)| values_equal_depth(x, y, depth + 1))
107 }
108 (RuntimeValue::Set(a), RuntimeValue::Set(b)) => {
111 if std::rc::Rc::ptr_eq(a, b) {
112 return true;
113 }
114 let (a, b) = (a.borrow(), b.borrow());
115 a.len() == b.len()
116 && a.iter().all(|x| b.iter().any(|y| values_equal_depth(x, y, depth + 1)))
117 }
118 (RuntimeValue::Map(a), RuntimeValue::Map(b)) => {
120 if std::rc::Rc::ptr_eq(a, b) {
121 return true;
122 }
123 let (a, b) = (a.borrow(), b.borrow());
124 a.len() == b.len()
125 && a.iter().all(|(k, va)| {
126 b.get(k).is_some_and(|vb| values_equal_depth(va, vb, depth + 1))
127 })
128 }
129 (RuntimeValue::Struct(a), RuntimeValue::Struct(b)) => {
130 a.type_name == b.type_name
131 && a.fields.len() == b.fields.len()
132 && a.fields.iter().all(|(name, va)| {
133 b.fields.get(name).is_some_and(|vb| values_equal_depth(va, vb, depth + 1))
134 })
135 }
136 (RuntimeValue::Chan(a), RuntimeValue::Chan(b)) => a == b,
139 (RuntimeValue::TaskHandle(a), RuntimeValue::TaskHandle(b)) => a == b,
140 (RuntimeValue::Peer(a), RuntimeValue::Peer(b)) => **a == **b,
141 (RuntimeValue::Function(a), RuntimeValue::Function(b)) => a.body_index == b.body_index,
142 (RuntimeValue::Crdt(a), RuntimeValue::Crdt(b)) => {
145 crate::semantics::crdt::crdt_values_equal(&a.borrow(), &b.borrow())
146 }
147 (RuntimeValue::Lanes(a), RuntimeValue::Lanes(b)) => a == b,
149 _ => false,
150 }
151}
152
153pub fn compare(
161 op: BinaryOpKind,
162 left: &RuntimeValue,
163 right: &RuntimeValue,
164) -> Result<RuntimeValue, String> {
165 use std::cmp::Ordering;
166
167 let rel = |ord: Option<Ordering>| -> bool {
169 match ord {
170 None => false,
171 Some(o) => match op {
172 BinaryOpKind::Lt => o == Ordering::Less,
173 BinaryOpKind::Gt => o == Ordering::Greater,
174 BinaryOpKind::LtEq => o != Ordering::Greater,
175 BinaryOpKind::GtEq => o != Ordering::Less,
176 _ => false,
177 },
178 }
179 };
180 let int_rel = |a: i64, b: i64| rel(Some(a.cmp(&b)));
181
182 match (left, right) {
183 (RuntimeValue::Int(a), RuntimeValue::Int(b)) => Ok(RuntimeValue::Bool(int_rel(*a, *b))),
184 (RuntimeValue::BigInt(a), RuntimeValue::BigInt(b)) => {
186 Ok(RuntimeValue::Bool(rel(Some((**a).cmp(b)))))
187 }
188 (RuntimeValue::BigInt(a), RuntimeValue::Int(b)) => {
189 Ok(RuntimeValue::Bool(rel(Some((**a).cmp(&BigInt::from_i64(*b))))))
190 }
191 (RuntimeValue::Int(a), RuntimeValue::BigInt(b)) => {
192 Ok(RuntimeValue::Bool(rel(Some(BigInt::from_i64(*a).cmp(b)))))
193 }
194 (RuntimeValue::BigInt(a), RuntimeValue::Float(b)) => {
197 Ok(RuntimeValue::Bool(rel(numeric::cmp_bigint_f64_exact(a, *b))))
198 }
199 (RuntimeValue::Float(a), RuntimeValue::BigInt(b)) => {
200 Ok(RuntimeValue::Bool(rel(numeric::cmp_bigint_f64_exact(b, *a).map(std::cmp::Ordering::reverse))))
201 }
202 (RuntimeValue::Rational(a), RuntimeValue::Rational(b)) => {
205 Ok(RuntimeValue::Bool(rel(Some((**a).cmp(b)))))
206 }
207 (RuntimeValue::Rational(a), RuntimeValue::Int(b)) => {
208 Ok(RuntimeValue::Bool(rel(Some((**a).cmp(&Rational::from_i64(*b))))))
209 }
210 (RuntimeValue::Int(a), RuntimeValue::Rational(b)) => {
211 Ok(RuntimeValue::Bool(rel(Some(Rational::from_i64(*a).cmp(b)))))
212 }
213 (RuntimeValue::Rational(a), RuntimeValue::BigInt(b)) => {
214 Ok(RuntimeValue::Bool(rel(Some((**a).cmp(&Rational::from_bigint((**b).clone()))))))
215 }
216 (RuntimeValue::BigInt(a), RuntimeValue::Rational(b)) => {
217 Ok(RuntimeValue::Bool(rel(Some(Rational::from_bigint((**a).clone()).cmp(b)))))
218 }
219 (RuntimeValue::Rational(a), RuntimeValue::Float(b)) => {
220 Ok(RuntimeValue::Bool(rel(numeric::cmp_rational_f64_exact(a, *b))))
221 }
222 (RuntimeValue::Float(a), RuntimeValue::Rational(b)) => {
223 Ok(RuntimeValue::Bool(rel(numeric::cmp_rational_f64_exact(b, *a).map(std::cmp::Ordering::reverse))))
224 }
225 (RuntimeValue::Float(a), RuntimeValue::Float(b)) => {
226 Ok(RuntimeValue::Bool(rel(a.partial_cmp(b))))
227 }
228 (RuntimeValue::Int(a), RuntimeValue::Float(b)) => {
229 Ok(RuntimeValue::Bool(rel(numeric::cmp_i64_f64_exact(*a, *b))))
230 }
231 (RuntimeValue::Float(a), RuntimeValue::Int(b)) => {
232 Ok(RuntimeValue::Bool(rel(numeric::cmp_i64_f64_exact(*b, *a).map(std::cmp::Ordering::reverse))))
233 }
234 (RuntimeValue::Duration(a), RuntimeValue::Duration(b)) => {
235 Ok(RuntimeValue::Bool(int_rel(*a, *b)))
236 }
237 (RuntimeValue::Date(a), RuntimeValue::Date(b)) => {
238 Ok(RuntimeValue::Bool(int_rel(*a as i64, *b as i64)))
239 }
240 (RuntimeValue::Moment(a), RuntimeValue::Moment(b)) => {
241 Ok(RuntimeValue::Bool(int_rel(*a, *b)))
242 }
243 (RuntimeValue::Time(a), RuntimeValue::Time(b)) => Ok(RuntimeValue::Bool(int_rel(*a, *b))),
244 (RuntimeValue::Moment(m), RuntimeValue::Time(t)) => {
248 let nanos_per_day = 86_400_000_000_000i64;
249 Ok(RuntimeValue::Bool(int_rel(m.rem_euclid(nanos_per_day), *t)))
250 }
251 (RuntimeValue::Time(t), RuntimeValue::Moment(m)) => {
252 let nanos_per_day = 86_400_000_000_000i64;
253 Ok(RuntimeValue::Bool(int_rel(*t, m.rem_euclid(nanos_per_day))))
254 }
255 (RuntimeValue::Quantity(a), RuntimeValue::Quantity(b)) => {
259 if a.q.dimension() != b.q.dimension() {
260 return Err(format!(
261 "Cannot compare quantities of different dimensions ({} vs {})",
262 a.q.dimension(),
263 b.q.dimension()
264 ));
265 }
266 Ok(RuntimeValue::Bool(rel(Some(a.q.magnitude_si().cmp(b.q.magnitude_si())))))
267 }
268 (RuntimeValue::Money(a), RuntimeValue::Money(b)) => {
271 if a.currency != b.currency {
272 return Err(format!(
273 "cannot compare money of different currencies ({} vs {})",
274 a.currency.code, b.currency.code
275 ));
276 }
277 Ok(RuntimeValue::Bool(rel(Some(
278 a.amount.to_rational().cmp(&b.amount.to_rational()),
279 ))))
280 }
281 (RuntimeValue::Uuid(a), RuntimeValue::Uuid(b)) => Ok(RuntimeValue::Bool(rel(Some(a.cmp(b))))),
283 (l, r) if matches!(l, RuntimeValue::Decimal(_)) || matches!(r, RuntimeValue::Decimal(_)) => {
286 let rat_view = |v: &RuntimeValue| -> Option<Rational> {
287 match v {
288 RuntimeValue::Int(n) => Some(Rational::from_i64(*n)),
289 RuntimeValue::BigInt(b) => Some(Rational::from_bigint((**b).clone())),
290 RuntimeValue::Rational(r) => Some((**r).clone()),
291 RuntimeValue::Decimal(d) => Some(d.to_rational()),
292 _ => None,
293 }
294 };
295 if let (Some(a), Some(b)) = (rat_view(l), rat_view(r)) {
296 Ok(RuntimeValue::Bool(rel(Some(a.cmp(&b)))))
297 } else {
298 let f64_view = |v: &RuntimeValue| -> Option<f64> {
299 match v {
300 RuntimeValue::Float(f) => Some(*f),
301 other => rat_view(other).map(|x| x.to_f64()),
302 }
303 };
304 match (f64_view(l), f64_view(r)) {
305 (Some(a), Some(b)) => Ok(RuntimeValue::Bool(rel(a.partial_cmp(&b)))),
306 _ => Err(format!("Cannot compare {} and {}", l.type_name(), r.type_name())),
307 }
308 }
309 }
310 _ => Err(format!(
311 "Cannot compare {} and {}",
312 left.type_name(),
313 right.type_name()
314 )),
315 }
316}
317
318#[cfg(test)]
319mod tests {
320 use super::*;
321
322 #[test]
323 fn float_equality_is_ieee_and_nan_unequal() {
324 let sum = RuntimeValue::Float(0.1 + 0.2);
328 assert!(!values_equal(&sum, &RuntimeValue::Float(0.3)));
329 assert!(values_equal(&sum, &RuntimeValue::Float(0.30000000000000004)));
330 assert!(!values_equal(&RuntimeValue::Float(f64::NAN), &RuntimeValue::Float(f64::NAN)));
331 assert!(values_equal(&RuntimeValue::Int(1), &RuntimeValue::Float(1.0)));
333 assert!(!values_equal(
335 &RuntimeValue::Int(9_007_199_254_740_993),
336 &RuntimeValue::Float(9_007_199_254_740_992.0)
337 ));
338 }
339
340 #[test]
341 fn collections_compare_structurally() {
342 use std::cell::RefCell;
343 use std::rc::Rc;
344 let list = |vals: Vec<i64>| {
345 RuntimeValue::List(Rc::new(RefCell::new(
346 crate::interpreter::ListRepr::from_values(
347 vals.into_iter().map(RuntimeValue::Int).collect(),
348 ),
349 )))
350 };
351 assert!(values_equal(&list(vec![1, 2, 3]), &list(vec![1, 2, 3])));
353 assert!(!values_equal(&list(vec![1, 2, 3]), &list(vec![1, 2, 4])));
355 assert!(!values_equal(&list(vec![1, 2]), &list(vec![1, 2, 3])));
356 }
357
358 #[test]
359 fn nan_relational_comparisons_are_false_not_errors() {
360 let nan = RuntimeValue::Float(f64::NAN);
361 for op in [BinaryOpKind::Lt, BinaryOpKind::Gt, BinaryOpKind::LtEq, BinaryOpKind::GtEq] {
362 let r = compare(op, &nan, &RuntimeValue::Float(1.0)).unwrap();
363 assert!(matches!(r, RuntimeValue::Bool(false)));
364 }
365 }
366
367 #[test]
368 fn moment_compares_to_time_by_time_of_day() {
369 let nanos_per_day = 86_400_000_000_000i64;
370 let m = RuntimeValue::Moment(3 * nanos_per_day + 10 * 3_600_000_000_000);
372 let t = RuntimeValue::Time(11 * 3_600_000_000_000);
373 assert!(matches!(compare(BinaryOpKind::Lt, &m, &t).unwrap(), RuntimeValue::Bool(true)));
374 assert!(matches!(compare(BinaryOpKind::Gt, &t, &m).unwrap(), RuntimeValue::Bool(true)));
375 }
376
377 #[test]
378 fn comparison_type_error_message() {
379 let e = compare(BinaryOpKind::Lt, &RuntimeValue::Bool(true), &RuntimeValue::Int(1))
380 .unwrap_err();
381 assert_eq!(e, "Cannot compare Bool and Int");
382 }
383}