use std::cmp::Ordering;
use std::rc::Rc;
use super::as_f64;
use crate::error::{DogeError, DogeResult};
use crate::value::Value;
pub fn values_equal(a: &Value, b: &Value) -> bool {
match (a, b) {
(Value::Int(x), Value::Int(y)) => x == y,
(Value::Float(x), Value::Float(y)) => x == y,
(Value::Int(x), Value::Float(y)) => (*x as f64) == *y,
(Value::Float(x), Value::Int(y)) => *x == (*y as f64),
(Value::Str(x), Value::Str(y)) => x == y,
(Value::Bool(x), Value::Bool(y)) => x == y,
(Value::None, Value::None) => true,
(Value::List(x), Value::List(y)) => {
let (xb, yb) = (x.borrow(), y.borrow());
xb.len() == yb.len() && xb.iter().zip(yb.iter()).all(|(p, q)| values_equal(p, q))
}
(Value::Dict(x), Value::Dict(y)) => {
let (xb, yb) = (x.borrow(), y.borrow());
xb.len() == yb.len()
&& xb
.iter()
.all(|(k, v)| yb.get(k).is_some_and(|w| values_equal(v, w)))
}
(Value::Object(x), Value::Object(y)) => Rc::ptr_eq(x, y),
(Value::Function(x), Value::Function(y)) => {
x.fn_id == y.fn_id
&& x.captures.len() == y.captures.len()
&& x.captures
.iter()
.zip(y.captures.iter())
.all(|(p, q)| Rc::ptr_eq(p, q))
}
(Value::Class(x), Value::Class(y)) => x.fn_id == y.fn_id,
(Value::BoundMethod(x), Value::BoundMethod(y)) => {
x.method == y.method && values_equal(&x.receiver, &y.receiver)
}
(Value::Error(x), Value::Error(y)) => {
x.kind == y.kind && x.message == y.message && x.file == y.file && x.line == y.line
}
(Value::Socket(x), Value::Socket(y)) => Rc::ptr_eq(x, y),
(Value::Pup(x), Value::Pup(y)) => Rc::ptr_eq(x, y),
(Value::Bowl(x), Value::Bowl(y)) => Rc::ptr_eq(x, y),
(Value::Int(_), _)
| (Value::Float(_), _)
| (Value::Str(_), _)
| (Value::Bool(_), _)
| (Value::None, _)
| (Value::List(_), _)
| (Value::Dict(_), _)
| (Value::Object(_), _)
| (Value::Function(_), _)
| (Value::Class(_), _)
| (Value::BoundMethod(_), _)
| (Value::Error(_), _)
| (Value::Socket(_), _)
| (Value::Pup(_), _)
| (Value::Bowl(_), _) => false,
}
}
pub fn eq(a: Value, b: Value) -> DogeResult {
Ok(Value::Bool(values_equal(&a, &b)))
}
pub fn ne(a: Value, b: Value) -> DogeResult {
Ok(Value::Bool(!values_equal(&a, &b)))
}
pub(crate) fn slice_contains(items: &[Value], target: &Value) -> bool {
items.iter().any(|element| values_equal(element, target))
}
pub fn in_(needle: Value, container: Value) -> DogeResult {
let found = match &container {
Value::List(items) => slice_contains(&items.borrow(), &needle),
Value::Dict(entries) => match &needle {
Value::Str(k) => entries.borrow().contains_key(k.as_ref()),
_ => false,
},
Value::Str(haystack) => match &needle {
Value::Str(sub) => haystack.contains(sub.as_ref()),
_ => {
return Err(DogeError::type_error(format!(
"can only check if a Str is in a Str, not {}",
needle.describe()
)));
}
},
Value::Int(_)
| Value::Float(_)
| Value::Bool(_)
| Value::None
| Value::Object(_)
| Value::Function(_)
| Value::Class(_)
| Value::BoundMethod(_)
| Value::Error(_)
| Value::Socket(_)
| Value::Pup(_)
| Value::Bowl(_) => {
return Err(DogeError::type_error(format!(
"in wants a List, Dict, or Str on the right, not {}",
container.describe()
)));
}
};
Ok(Value::Bool(found))
}
pub fn not_in(needle: Value, container: Value) -> DogeResult {
match in_(needle, container)? {
Value::Bool(found) => Ok(Value::Bool(!found)),
_ => unreachable!("in_ always yields a Bool"),
}
}
pub(crate) fn order(a: &Value, b: &Value) -> DogeResult<Ordering> {
if let (Some(x), Some(y)) = (as_f64(a), as_f64(b)) {
return x.partial_cmp(&y).ok_or_else(|| {
DogeError::type_error(format!(
"cannot compare {} and {}",
a.describe(),
b.describe()
))
});
}
if let (Value::Str(x), Value::Str(y)) = (a, b) {
return Ok(x.as_ref().cmp(y.as_ref()));
}
Err(DogeError::type_error(format!(
"cannot compare {} and {}",
a.describe(),
b.describe()
)))
}
pub fn lt(a: Value, b: Value) -> DogeResult {
Ok(Value::Bool(order(&a, &b)? == Ordering::Less))
}
pub fn le(a: Value, b: Value) -> DogeResult {
Ok(Value::Bool(matches!(
order(&a, &b)?,
Ordering::Less | Ordering::Equal
)))
}
pub fn gt(a: Value, b: Value) -> DogeResult {
Ok(Value::Bool(order(&a, &b)? == Ordering::Greater))
}
pub fn ge(a: Value, b: Value) -> DogeResult {
Ok(Value::Bool(matches!(
order(&a, &b)?,
Ordering::Greater | Ordering::Equal
)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bound_methods_equal_only_on_the_same_receiver_and_name() {
let a = Value::object(0, "Shibe");
let b = Value::object(0, "Shibe");
assert!(values_equal(
&Value::bound_method(a.clone(), "speak"),
&Value::bound_method(a.clone(), "speak"),
));
assert!(!values_equal(
&Value::bound_method(a.clone(), "speak"),
&Value::bound_method(a.clone(), "wag"),
));
assert!(!values_equal(
&Value::bound_method(a, "speak"),
&Value::bound_method(b, "speak"),
));
}
}