1use std::cmp::Ordering;
2use std::rc::Rc;
3
4use bigdecimal::{BigDecimal, ToPrimitive};
5
6use crate::error::{DogeError, DogeResult};
7use crate::value::Value;
8
9fn is_numeric(v: &Value) -> bool {
12 matches!(v, Value::Int(_) | Value::Float(_) | Value::Decimal(_))
13}
14
15fn numeric_f64(v: &Value) -> Option<f64> {
18 match v {
19 Value::Int(n) => n.to_f64(),
20 Value::Float(f) => Some(*f),
21 Value::Decimal(d) => d.to_f64(),
22 _ => None,
23 }
24}
25
26fn numeric_cmp(a: &Value, b: &Value) -> Option<Ordering> {
30 match (a, b) {
31 (Value::Int(x), Value::Int(y)) => Some(x.cmp(y)),
32 (Value::Decimal(x), Value::Decimal(y)) => Some(x.cmp(y)),
33 (Value::Int(x), Value::Decimal(y)) => Some(BigDecimal::from(x.clone()).cmp(y)),
34 (Value::Decimal(x), Value::Int(y)) => Some(x.cmp(&BigDecimal::from(y.clone()))),
35 _ => numeric_f64(a)?.partial_cmp(&numeric_f64(b)?),
36 }
37}
38
39fn numeric_equal(a: &Value, b: &Value) -> Option<bool> {
43 if is_numeric(a) && is_numeric(b) {
44 Some(numeric_cmp(a, b) == Some(Ordering::Equal))
45 } else {
46 None
47 }
48}
49
50pub fn values_equal(a: &Value, b: &Value) -> bool {
53 if let Some(numeric) = numeric_equal(a, b) {
54 return numeric;
55 }
56 match (a, b) {
57 (Value::Str(x), Value::Str(y)) => x == y,
58 (Value::Bytes(x), Value::Bytes(y)) => x == y,
59 (Value::Bool(x), Value::Bool(y)) => x == y,
60 (Value::None, Value::None) => true,
61 (Value::List(x), Value::List(y)) => {
62 let (xb, yb) = (x.borrow(), y.borrow());
63 xb.len() == yb.len() && xb.iter().zip(yb.iter()).all(|(p, q)| values_equal(p, q))
64 }
65 (Value::Dict(x), Value::Dict(y)) => {
66 let (xb, yb) = (x.borrow(), y.borrow());
67 xb.len() == yb.len()
68 && xb
69 .iter()
70 .all(|(k, v)| yb.get(k).is_some_and(|w| values_equal(v, w)))
71 }
72 (Value::Object(x), Value::Object(y)) => Rc::ptr_eq(x, y),
74 (Value::Function(x), Value::Function(y)) => {
78 x.fn_id == y.fn_id
79 && x.captures.len() == y.captures.len()
80 && x.captures
81 .iter()
82 .zip(y.captures.iter())
83 .all(|(p, q)| Rc::ptr_eq(p, q))
84 }
85 (Value::Class(x), Value::Class(y)) => x.fn_id == y.fn_id,
88 (Value::BoundMethod(x), Value::BoundMethod(y)) => {
91 x.method == y.method && values_equal(&x.receiver, &y.receiver)
92 }
93 (Value::Error(x), Value::Error(y)) => {
95 x.kind == y.kind && x.message == y.message && x.file == y.file && x.line == y.line
96 }
97 (Value::Socket(x), Value::Socket(y)) => Rc::ptr_eq(x, y),
99 (Value::Pup(x), Value::Pup(y)) => Rc::ptr_eq(x, y),
101 (Value::Bowl(x), Value::Bowl(y)) => Rc::ptr_eq(x, y),
102 (Value::Int(_), _)
106 | (Value::Float(_), _)
107 | (Value::Decimal(_), _)
108 | (Value::Str(_), _)
109 | (Value::Bytes(_), _)
110 | (Value::Bool(_), _)
111 | (Value::None, _)
112 | (Value::List(_), _)
113 | (Value::Dict(_), _)
114 | (Value::Object(_), _)
115 | (Value::Function(_), _)
116 | (Value::Class(_), _)
117 | (Value::BoundMethod(_), _)
118 | (Value::Error(_), _)
119 | (Value::Socket(_), _)
120 | (Value::Pup(_), _)
121 | (Value::Bowl(_), _) => false,
122 }
123}
124
125pub fn eq(a: Value, b: Value) -> DogeResult {
127 Ok(Value::Bool(values_equal(&a, &b)))
128}
129
130pub fn ne(a: Value, b: Value) -> DogeResult {
132 Ok(Value::Bool(!values_equal(&a, &b)))
133}
134
135pub(crate) fn slice_contains(items: &[Value], target: &Value) -> bool {
138 items.iter().any(|element| values_equal(element, target))
139}
140
141pub fn in_(needle: Value, container: Value) -> DogeResult {
146 let found = match &container {
147 Value::List(items) => slice_contains(&items.borrow(), &needle),
148 Value::Dict(entries) => match &needle {
151 Value::Str(k) => entries.borrow().contains_key(k.as_ref()),
152 _ => false,
153 },
154 Value::Str(haystack) => match &needle {
155 Value::Str(sub) => haystack.contains(sub.as_ref()),
156 _ => {
157 return Err(DogeError::type_error(format!(
158 "can only check if a Str is in a Str, not {}",
159 needle.describe()
160 )));
161 }
162 },
163 Value::Bytes(haystack) => match &needle {
166 Value::Int(n) => n.to_u8().is_some_and(|b| haystack.contains(&b)),
167 Value::Bytes(sub) => {
168 sub.is_empty() || haystack.windows(sub.len()).any(|w| w == &sub[..])
169 }
170 _ => {
171 return Err(DogeError::type_error(format!(
172 "can only check if an Int or Bytes is in a Bytes, not {}",
173 needle.describe()
174 )));
175 }
176 },
177 Value::Int(_)
178 | Value::Float(_)
179 | Value::Decimal(_)
180 | Value::Bool(_)
181 | Value::None
182 | Value::Object(_)
183 | Value::Function(_)
184 | Value::Class(_)
185 | Value::BoundMethod(_)
186 | Value::Error(_)
187 | Value::Socket(_)
188 | Value::Pup(_)
189 | Value::Bowl(_) => {
190 return Err(DogeError::type_error(format!(
191 "in wants a List, Dict, Str, or Bytes on the right, not {}",
192 container.describe()
193 )));
194 }
195 };
196 Ok(Value::Bool(found))
197}
198
199pub fn not_in(needle: Value, container: Value) -> DogeResult {
202 match in_(needle, container)? {
203 Value::Bool(found) => Ok(Value::Bool(!found)),
204 _ => unreachable!("in_ always yields a Bool"),
205 }
206}
207
208pub(crate) fn order(a: &Value, b: &Value) -> DogeResult<Ordering> {
212 if is_numeric(a) && is_numeric(b) {
213 return numeric_cmp(a, b).ok_or_else(|| {
214 DogeError::type_error(format!(
215 "cannot compare {} and {}",
216 a.describe(),
217 b.describe()
218 ))
219 });
220 }
221 if let (Value::Str(x), Value::Str(y)) = (a, b) {
222 return Ok(x.as_ref().cmp(y.as_ref()));
223 }
224 if let (Value::Bytes(x), Value::Bytes(y)) = (a, b) {
225 return Ok(x.as_ref().cmp(y.as_ref()));
226 }
227 Err(DogeError::type_error(format!(
228 "cannot compare {} and {}",
229 a.describe(),
230 b.describe()
231 )))
232}
233
234pub fn lt(a: Value, b: Value) -> DogeResult {
236 Ok(Value::Bool(order(&a, &b)? == Ordering::Less))
237}
238
239pub fn le(a: Value, b: Value) -> DogeResult {
241 Ok(Value::Bool(matches!(
242 order(&a, &b)?,
243 Ordering::Less | Ordering::Equal
244 )))
245}
246
247pub fn gt(a: Value, b: Value) -> DogeResult {
249 Ok(Value::Bool(order(&a, &b)? == Ordering::Greater))
250}
251
252pub fn ge(a: Value, b: Value) -> DogeResult {
254 Ok(Value::Bool(matches!(
255 order(&a, &b)?,
256 Ordering::Greater | Ordering::Equal
257 )))
258}
259
260#[cfg(test)]
261mod tests {
262 use super::*;
263
264 #[test]
265 fn bytes_membership_subslice_and_ordering() {
266 let hay = Value::bytes([104, 105, 33]);
267 assert!(matches!(
269 in_(Value::int(105), hay.clone()).unwrap(),
270 Value::Bool(true)
271 ));
272 assert!(matches!(
273 in_(Value::int(200), hay.clone()).unwrap(),
274 Value::Bool(false)
275 ));
276 assert!(matches!(
277 in_(Value::int(999), hay.clone()).unwrap(),
278 Value::Bool(false)
279 ));
280 assert!(matches!(
282 in_(Value::bytes([105, 33]), hay.clone()).unwrap(),
283 Value::Bool(true)
284 ));
285 assert!(matches!(
286 in_(Value::bytes([104, 33]), hay.clone()).unwrap(),
287 Value::Bool(false)
288 ));
289 assert_eq!(
291 order(&Value::bytes([1, 2]), &Value::bytes([1, 3])).unwrap(),
292 Ordering::Less
293 );
294 }
295
296 #[test]
297 fn bound_methods_equal_only_on_the_same_receiver_and_name() {
298 let a = Value::object(0, "Shibe");
299 let b = Value::object(0, "Shibe");
300 assert!(values_equal(
302 &Value::bound_method(a.clone(), "speak"),
303 &Value::bound_method(a.clone(), "speak"),
304 ));
305 assert!(!values_equal(
307 &Value::bound_method(a.clone(), "speak"),
308 &Value::bound_method(a.clone(), "wag"),
309 ));
310 assert!(!values_equal(
312 &Value::bound_method(a, "speak"),
313 &Value::bound_method(b, "speak"),
314 ));
315 }
316}