1use std::cmp::Ordering;
23
24use crate::descriptor::Strategy;
25use crate::error::{PanicKind, SoilError};
26use crate::json;
27use crate::value::Value;
28use crate::Runtime;
29
30pub fn eq(runtime: &Runtime, a: &Value, b: &Value) -> Result<bool, SoilError> {
34 Ok(compare(runtime, a, b)? == Ordering::Equal)
35}
36
37pub fn compare(runtime: &Runtime, a: &Value, b: &Value) -> Result<Ordering, SoilError> {
48 match (a, b) {
49 (Value::I64(x), Value::I64(y)) => Ok(x.cmp(y)),
50 (Value::U64(x), Value::U64(y)) => Ok(x.cmp(y)),
51 (Value::I32(x), Value::I32(y)) => Ok(x.cmp(y)),
52 (Value::U32(x), Value::U32(y)) => Ok(x.cmp(y)),
53 (Value::I16(x), Value::I16(y)) => Ok(x.cmp(y)),
54 (Value::U16(x), Value::U16(y)) => Ok(x.cmp(y)),
55 (Value::I8(x), Value::I8(y)) => Ok(x.cmp(y)),
56 (Value::U8(x), Value::U8(y)) => Ok(x.cmp(y)),
57 (Value::F64(x), Value::F64(y)) => Ok(compare_f64(*x, *y)),
58 (Value::BigInt(x), Value::BigInt(y)) => Ok(x.cmp(y)),
59 (Value::Utf8(x), Value::Utf8(y)) => Ok(x.as_bytes().cmp(y.as_bytes())),
60 (Value::Bytes(x), Value::Bytes(y)) => Ok((**x).cmp(&**y)),
61 (Value::Unit, Value::Unit) => Ok(Ordering::Equal),
62 (Value::Record(x), Value::Record(y)) => {
63 if x.type_id != y.type_id {
64 return Err(type_mismatch());
65 }
66 let desc = runtime.registry.get(x.type_id)?;
67 if desc.strategy == Strategy::Opaque {
68 return Ok(x.addr().cmp(&y.addr()));
69 }
70 require_derivable(runtime, x.type_id)?;
71 let crate::descriptor::TypeBody::Record(fields) = &desc.body else {
72 return Err(type_mismatch());
73 };
74 for (i, field) in fields.iter().enumerate() {
75 if field.ignored.is_some() {
76 continue;
77 }
78 match compare(runtime, &x.fields[i], &y.fields[i])? {
79 Ordering::Equal => {}
80 other => return Ok(other),
81 }
82 }
83 Ok(Ordering::Equal)
84 }
85 (Value::Sum(x), Value::Sum(y)) => {
86 if x.type_id != y.type_id {
87 return Err(type_mismatch());
88 }
89 let desc = runtime.registry.get(x.type_id)?;
90 if desc.strategy == Strategy::Opaque {
91 return Ok(x.addr().cmp(&y.addr()));
92 }
93 require_derivable(runtime, x.type_id)?;
94 match x.variant.cmp(&y.variant) {
95 Ordering::Equal => match (&x.payload, &y.payload) {
96 (Some(p), Some(q)) => compare(runtime, p, q),
97 (None, None) => Ok(Ordering::Equal),
98 _ => Err(type_mismatch()),
99 },
100 other => Ok(other),
101 }
102 }
103 (Value::List(x), Value::List(y)) => compare_seq(runtime, x.iter(), y.iter()),
104 (Value::Map(x), Value::Map(y)) => {
105 let xs = x.entries().iter().flat_map(|(k, v)| [k, v]);
108 let ys = y.entries().iter().flat_map(|(k, v)| [k, v]);
109 compare_seq(runtime, xs, ys)
110 }
111 (Value::Opaque(x), Value::Opaque(y)) => {
112 if x.type_id != y.type_id {
113 return Err(type_mismatch());
114 }
115 Ok(x.addr().cmp(&y.addr()))
116 }
117 (Value::Closure(_), _) | (_, Value::Closure(_)) => Err(SoilError::new(
118 PanicKind::DerivationError,
119 "compare reached a function value",
120 )),
121 _ => Err(type_mismatch()),
122 }
123}
124
125pub fn hash(runtime: &Runtime, value: &Value) -> Result<u64, SoilError> {
129 match value {
130 Value::Opaque(v) => Ok(fnv1a64(&v.addr().to_le_bytes())),
131 Value::Record(v) => {
132 if runtime.registry.get(v.type_id)?.strategy == Strategy::Opaque {
133 return Ok(fnv1a64(&v.addr().to_le_bytes()));
134 }
135 Ok(fnv1a64(json::encode(runtime, value)?.as_bytes()))
136 }
137 Value::Sum(v) => {
138 if runtime.registry.get(v.type_id)?.strategy == Strategy::Opaque {
139 return Ok(fnv1a64(&v.addr().to_le_bytes()));
140 }
141 Ok(fnv1a64(json::encode(runtime, value)?.as_bytes()))
142 }
143 Value::Closure(_) => Err(SoilError::new(
144 PanicKind::DerivationError,
145 "hash reached a function value",
146 )),
147 _ => Ok(fnv1a64(json::encode(runtime, value)?.as_bytes())),
148 }
149}
150
151fn fnv1a64(bytes: &[u8]) -> u64 {
153 const OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
154 const PRIME: u64 = 0x0000_0100_0000_01b3;
155 let mut state = OFFSET_BASIS;
156 for &byte in bytes {
157 state ^= u64::from(byte);
158 state = state.wrapping_mul(PRIME);
159 }
160 state
161}
162
163fn compare_f64(x: f64, y: f64) -> Ordering {
166 match (x.is_nan(), y.is_nan()) {
167 (true, true) => Ordering::Equal,
168 (true, false) => Ordering::Greater,
169 (false, true) => Ordering::Less,
170 (false, false) => x.total_cmp(&y),
171 }
172}
173
174fn compare_seq<'a>(
175 runtime: &Runtime,
176 mut xs: impl Iterator<Item = &'a Value>,
177 mut ys: impl Iterator<Item = &'a Value>,
178) -> Result<Ordering, SoilError> {
179 loop {
180 match (xs.next(), ys.next()) {
181 (Some(x), Some(y)) => match compare(runtime, x, y)? {
182 Ordering::Equal => {}
183 other => return Ok(other),
184 },
185 (None, None) => return Ok(Ordering::Equal),
186 (Some(_), None) => return Ok(Ordering::Greater),
187 (None, Some(_)) => return Ok(Ordering::Less),
188 }
189 }
190}
191
192fn require_derivable(runtime: &Runtime, id: crate::descriptor::TypeId) -> Result<(), SoilError> {
193 if runtime.registry.derivable(id)? {
194 Ok(())
195 } else {
196 let name = runtime.registry.get(id)?.name.clone();
197 Err(SoilError::new(
198 PanicKind::DerivationError,
199 format!("type {name} contains a function type and derives nothing"),
200 ))
201 }
202}
203
204fn type_mismatch() -> SoilError {
205 SoilError::new(PanicKind::TypeError, "compared values have different types")
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211 use crate::descriptor::{FieldDesc, TypeBody, TypeDesc, TypeShape};
212 use crate::value::{Closure, OpaqueVal, Record, Ref};
213
214 fn rt() -> Runtime {
215 Runtime::new()
216 }
217
218 #[test]
219 fn nan_is_one_value_sorting_last() {
220 let rt = rt();
221 let nan1 = Value::F64(f64::NAN);
222 let nan2 = Value::F64(-f64::NAN);
223 assert!(eq(&rt, &nan1, &nan2).unwrap());
224 assert_eq!(
225 compare(&rt, &nan1, &Value::F64(f64::INFINITY)).unwrap(),
226 Ordering::Greater
227 );
228 assert_eq!(
229 compare(&rt, &Value::F64(-0.0), &Value::F64(0.0)).unwrap(),
230 Ordering::Less
231 );
232 assert!(!eq(&rt, &Value::F64(-0.0), &Value::F64(0.0)).unwrap());
233 }
234
235 #[test]
236 fn lists_compare_lexicographically_then_by_length() {
237 let rt = rt();
238 let short = Value::List(Ref::new(vec![Value::I64(1)]));
239 let long = Value::List(Ref::new(vec![Value::I64(1), Value::I64(2)]));
240 let bigger = Value::List(Ref::new(vec![Value::I64(9)]));
241 assert_eq!(compare(&rt, &short, &long).unwrap(), Ordering::Less);
242 assert_eq!(compare(&rt, &bigger, &long).unwrap(), Ordering::Greater);
243 assert!(eq(&rt, &short, &short.clone()).unwrap());
244 }
245
246 #[test]
247 fn records_skip_ignored_fields() {
248 let mut rt = rt();
249 let id = rt
250 .registry
251 .register(TypeDesc {
252 name: "Doc".to_string(),
253 strategy: Strategy::Structural,
254 body: TypeBody::Record(vec![
255 FieldDesc {
256 name: "text".to_string(),
257 shape: TypeShape::Utf8,
258 ignored: None,
259 },
260 FieldDesc {
261 name: "cache".to_string(),
262 shape: TypeShape::U64,
263 ignored: Some(crate::descriptor::IgnoredDefault::Native(Closure::native(
264 |_| Ok(Value::U64(0)),
265 ))),
266 },
267 ]),
268 })
269 .unwrap();
270 let make = |cache: u64| {
271 Value::Record(Ref::new(Record {
272 type_id: id,
273 fields: Box::new([Value::Utf8(Ref::from("same")), Value::U64(cache)]),
274 }))
275 };
276 let a = make(1);
277 let b = make(2);
278 assert!(eq(&rt, &a, &b).unwrap());
279 assert_eq!(hash(&rt, &a).unwrap(), hash(&rt, &b).unwrap());
280 }
281
282 #[test]
283 fn equal_values_hash_equal_and_deterministically() {
284 let rt = rt();
285 let a = Value::List(Ref::new(vec![Value::I64(1), Value::Utf8(Ref::from("x"))]));
286 let b = Value::List(Ref::new(vec![Value::I64(1), Value::Utf8(Ref::from("x"))]));
287 assert!(eq(&rt, &a, &b).unwrap());
288 assert_eq!(hash(&rt, &a).unwrap(), hash(&rt, &b).unwrap());
289 assert_eq!(hash(&rt, &a).unwrap(), fnv1a64(b"[1,\"x\"]"));
292 }
293
294 #[test]
295 fn opaque_hash_and_eq_by_identity() {
296 let mut rt = rt();
297 let id = rt
298 .registry
299 .register(TypeDesc {
300 name: "Handle".to_string(),
301 strategy: Strategy::Opaque,
302 body: TypeBody::Opaque,
303 })
304 .unwrap();
305 let a = Value::Opaque(Ref::new(OpaqueVal {
306 type_id: id,
307 payload: Box::new(1u8),
308 }));
309 let b = a.clone();
310 let c = Value::Opaque(Ref::new(OpaqueVal {
311 type_id: id,
312 payload: Box::new(1u8),
313 }));
314 assert!(eq(&rt, &a, &b).unwrap());
315 assert!(!eq(&rt, &a, &c).unwrap());
316 assert_eq!(hash(&rt, &a).unwrap(), hash(&rt, &b).unwrap());
317 assert_ne!(hash(&rt, &a).unwrap(), hash(&rt, &c).unwrap());
318 }
319
320 #[test]
321 fn closures_do_not_derive() {
322 let rt = rt();
323 let f = Value::Closure(Ref::new(Closure::native(|_| Ok(Value::Unit))));
324 assert_eq!(
325 compare(&rt, &f, &f.clone()).unwrap_err().kind,
326 PanicKind::DerivationError
327 );
328 assert_eq!(hash(&rt, &f).unwrap_err().kind, PanicKind::DerivationError);
329 }
330
331 #[test]
332 fn mismatched_types_error() {
333 let rt = rt();
334 assert_eq!(
335 compare(&rt, &Value::I64(1), &Value::U64(1))
336 .unwrap_err()
337 .kind,
338 PanicKind::TypeError
339 );
340 }
341}