Skip to main content

doge_runtime/ops/
compare.rs

1use std::cmp::Ordering;
2use std::rc::Rc;
3
4use super::as_f64;
5use crate::error::{DogeError, DogeResult};
6use crate::value::Value;
7
8/// Structural, Python-style equality: `1 == 1.0`, deep list/dict comparison,
9/// everything else across types is unequal.
10pub fn values_equal(a: &Value, b: &Value) -> bool {
11    match (a, b) {
12        (Value::Int(x), Value::Int(y)) => x == y,
13        (Value::Float(x), Value::Float(y)) => x == y,
14        (Value::Int(x), Value::Float(y)) => (*x as f64) == *y,
15        (Value::Float(x), Value::Int(y)) => *x == (*y as f64),
16        (Value::Str(x), Value::Str(y)) => x == y,
17        (Value::Bool(x), Value::Bool(y)) => x == y,
18        (Value::None, Value::None) => true,
19        (Value::List(x), Value::List(y)) => {
20            let (xb, yb) = (x.borrow(), y.borrow());
21            xb.len() == yb.len() && xb.iter().zip(yb.iter()).all(|(p, q)| values_equal(p, q))
22        }
23        (Value::Dict(x), Value::Dict(y)) => {
24            let (xb, yb) = (x.borrow(), y.borrow());
25            xb.len() == yb.len()
26                && xb
27                    .iter()
28                    .all(|(k, v)| yb.get(k).is_some_and(|w| values_equal(v, w)))
29        }
30        // Objects are equal only when they are the very same instance.
31        (Value::Object(x), Value::Object(y)) => Rc::ptr_eq(x, y),
32        // Functions are equal when they share a definition and the very same
33        // captured cells: `greet == greet` holds, but two closures built from the
34        // same definition over different environments do not.
35        (Value::Function(x), Value::Function(y)) => {
36            x.fn_id == y.fn_id
37                && x.captures.len() == y.captures.len()
38                && x.captures
39                    .iter()
40                    .zip(y.captures.iter())
41                    .all(|(p, q)| Rc::ptr_eq(p, q))
42        }
43        // Classes are equal when they name the same constructor: a class captures
44        // nothing, so `Shibe == Shibe` holds and `Shibe == Corgi` does not.
45        (Value::Class(x), Value::Class(y)) => x.fn_id == y.fn_id,
46        // Bound methods are equal when they name the same method on the very same
47        // receiver instance: `a.speak == a.speak` holds, but not `b.speak`.
48        (Value::BoundMethod(x), Value::BoundMethod(y)) => {
49            x.method == y.method && values_equal(&x.receiver, &y.receiver)
50        }
51        // Errors are equal when their type, message, and raise site all match.
52        (Value::Error(x), Value::Error(y)) => {
53            x.kind == y.kind && x.message == y.message && x.file == y.file && x.line == y.line
54        }
55        // Sockets are equal only when they are the very same handle.
56        (Value::Socket(x), Value::Socket(y)) => Rc::ptr_eq(x, y),
57        // Pups and bowls, like sockets, are equal only to the very same handle.
58        (Value::Pup(x), Value::Pup(y)) => Rc::ptr_eq(x, y),
59        (Value::Bowl(x), Value::Bowl(y)) => Rc::ptr_eq(x, y),
60        // Cross-type comparisons are simply unequal, never an error. Written by
61        // left-hand variant rather than a wildcard, so a new Value variant forces
62        // its own same-type case to be added above.
63        (Value::Int(_), _)
64        | (Value::Float(_), _)
65        | (Value::Str(_), _)
66        | (Value::Bool(_), _)
67        | (Value::None, _)
68        | (Value::List(_), _)
69        | (Value::Dict(_), _)
70        | (Value::Object(_), _)
71        | (Value::Function(_), _)
72        | (Value::Class(_), _)
73        | (Value::BoundMethod(_), _)
74        | (Value::Error(_), _)
75        | (Value::Socket(_), _)
76        | (Value::Pup(_), _)
77        | (Value::Bowl(_), _) => false,
78    }
79}
80
81/// `==`.
82pub fn eq(a: Value, b: Value) -> DogeResult {
83    Ok(Value::Bool(values_equal(&a, &b)))
84}
85
86/// `!=`.
87pub fn ne(a: Value, b: Value) -> DogeResult {
88    Ok(Value::Bool(!values_equal(&a, &b)))
89}
90
91/// Whether `target` is structurally equal to some element of `items`. Shared by
92/// the `in` operator and `List.contains` so their membership rule is identical.
93pub(crate) fn slice_contains(items: &[Value], target: &Value) -> bool {
94    items.iter().any(|element| values_equal(element, target))
95}
96
97/// `needle in container`. Python-style: a List tests element membership, a Dict
98/// tests key membership, a Str tests substring. Every other container type is a
99/// catchable type error. Matched by container variant (not a wildcard) so a new
100/// `Value` variant forces its own decision here.
101pub fn in_(needle: Value, container: Value) -> DogeResult {
102    let found = match &container {
103        Value::List(items) => slice_contains(&items.borrow(), &needle),
104        // A dict is keyed by Str; a non-Str needle can never be a key, so it is
105        // simply absent rather than an error (mirrors cross-type `==`).
106        Value::Dict(entries) => match &needle {
107            Value::Str(k) => entries.borrow().contains_key(k.as_ref()),
108            _ => false,
109        },
110        Value::Str(haystack) => match &needle {
111            Value::Str(sub) => haystack.contains(sub.as_ref()),
112            _ => {
113                return Err(DogeError::type_error(format!(
114                    "can only check if a Str is in a Str, not {}",
115                    needle.describe()
116                )));
117            }
118        },
119        Value::Int(_)
120        | Value::Float(_)
121        | Value::Bool(_)
122        | Value::None
123        | Value::Object(_)
124        | Value::Function(_)
125        | Value::Class(_)
126        | Value::BoundMethod(_)
127        | Value::Error(_)
128        | Value::Socket(_)
129        | Value::Pup(_)
130        | Value::Bowl(_) => {
131            return Err(DogeError::type_error(format!(
132                "in wants a List, Dict, or Str on the right, not {}",
133                container.describe()
134            )));
135        }
136    };
137    Ok(Value::Bool(found))
138}
139
140/// `needle not in container` — the negation of [`in_`], sharing its type rules so
141/// `x not in xs` and `not (x in xs)` always agree.
142pub fn not_in(needle: Value, container: Value) -> DogeResult {
143    match in_(needle, container)? {
144        Value::Bool(found) => Ok(Value::Bool(!found)),
145        _ => unreachable!("in_ always yields a Bool"),
146    }
147}
148
149/// Ordering for `< <= > >=`: numbers compare across Int/Float, Str compares
150/// lexicographically, anything else is a type error. The list `sort` method
151/// reuses this so its ordering matches the comparison operators exactly.
152pub(crate) fn order(a: &Value, b: &Value) -> DogeResult<Ordering> {
153    if let (Some(x), Some(y)) = (as_f64(a), as_f64(b)) {
154        return x.partial_cmp(&y).ok_or_else(|| {
155            DogeError::type_error(format!(
156                "cannot compare {} and {}",
157                a.describe(),
158                b.describe()
159            ))
160        });
161    }
162    if let (Value::Str(x), Value::Str(y)) = (a, b) {
163        return Ok(x.as_ref().cmp(y.as_ref()));
164    }
165    Err(DogeError::type_error(format!(
166        "cannot compare {} and {}",
167        a.describe(),
168        b.describe()
169    )))
170}
171
172/// `<`.
173pub fn lt(a: Value, b: Value) -> DogeResult {
174    Ok(Value::Bool(order(&a, &b)? == Ordering::Less))
175}
176
177/// `<=`.
178pub fn le(a: Value, b: Value) -> DogeResult {
179    Ok(Value::Bool(matches!(
180        order(&a, &b)?,
181        Ordering::Less | Ordering::Equal
182    )))
183}
184
185/// `>`.
186pub fn gt(a: Value, b: Value) -> DogeResult {
187    Ok(Value::Bool(order(&a, &b)? == Ordering::Greater))
188}
189
190/// `>=`.
191pub fn ge(a: Value, b: Value) -> DogeResult {
192    Ok(Value::Bool(matches!(
193        order(&a, &b)?,
194        Ordering::Greater | Ordering::Equal
195    )))
196}
197
198#[cfg(test)]
199mod tests {
200    use super::*;
201
202    #[test]
203    fn bound_methods_equal_only_on_the_same_receiver_and_name() {
204        let a = Value::object(0, "Shibe");
205        let b = Value::object(0, "Shibe");
206        // Same instance, same method → equal.
207        assert!(values_equal(
208            &Value::bound_method(a.clone(), "speak"),
209            &Value::bound_method(a.clone(), "speak"),
210        ));
211        // Same instance, different method → not equal.
212        assert!(!values_equal(
213            &Value::bound_method(a.clone(), "speak"),
214            &Value::bound_method(a.clone(), "wag"),
215        ));
216        // Different instances of the same class → not equal.
217        assert!(!values_equal(
218            &Value::bound_method(a, "speak"),
219            &Value::bound_method(b, "speak"),
220        ));
221    }
222}