Skip to main content

harn_vm/value/
structural.rs

1use std::rc::Rc;
2use std::sync::atomic::Ordering;
3
4use super::VmValue;
5
6/// Reference / identity equality. For heap-allocated refcounted values
7/// (List/Dict/Set/Closure) returns true only when both operands share the
8/// same underlying `Rc` allocation. For primitive scalars, falls back to
9/// structural equality (since primitives have no distinct identity).
10pub fn values_identical(a: &VmValue, b: &VmValue) -> bool {
11    match (a, b) {
12        (VmValue::List(x), VmValue::List(y)) => Rc::ptr_eq(x, y),
13        (VmValue::Dict(x), VmValue::Dict(y)) => Rc::ptr_eq(x, y),
14        (VmValue::Set(x), VmValue::Set(y)) => Rc::ptr_eq(x, y),
15        (VmValue::Closure(x), VmValue::Closure(y)) => Rc::ptr_eq(x, y),
16        (VmValue::String(x), VmValue::String(y)) => Rc::ptr_eq(x, y) || x == y,
17        (VmValue::Bytes(x), VmValue::Bytes(y)) => Rc::ptr_eq(x, y) || x == y,
18        (VmValue::BuiltinRef(x), VmValue::BuiltinRef(y)) => x == y,
19        (VmValue::Pair(x), VmValue::Pair(y)) => Rc::ptr_eq(x, y),
20        // Primitives: identity collapses to structural equality.
21        _ => values_equal(a, b),
22    }
23}
24
25/// Stable identity key for a value. Different allocations produce different
26/// keys; two values with the same heap identity produce the same key. For
27/// primitives the key is derived from the displayed value plus type name so
28/// logically-equal primitives always compare equal.
29pub fn value_identity_key(v: &VmValue) -> String {
30    match v {
31        VmValue::List(x) => format!("list@{:p}", Rc::as_ptr(x)),
32        VmValue::Dict(x) => format!("dict@{:p}", Rc::as_ptr(x)),
33        VmValue::Set(x) => format!("set@{:p}", Rc::as_ptr(x)),
34        VmValue::Closure(x) => format!("closure@{:p}", Rc::as_ptr(x)),
35        VmValue::String(x) => format!("string@{:p}", x.as_ptr()),
36        VmValue::Bytes(x) => format!("bytes@{:p}", Rc::as_ptr(x)),
37        VmValue::BuiltinRef(name) => format!("builtin@{name}"),
38        other => format!("{}@{}", other.type_name(), other.display()),
39    }
40}
41
42/// Canonical string form used as the keying material for `hash_value`.
43/// Different types never collide (the type name is prepended) and collection
44/// order is preserved so structurally-equal values always produce the same
45/// key. Not intended for cross-process stability; depends on the in-process
46/// iteration order for collections (Dict uses BTreeMap so keys are sorted).
47pub fn value_structural_hash_key(v: &VmValue) -> String {
48    let mut out = String::new();
49    write_structural_hash_key(v, &mut out);
50    out
51}
52
53/// Writes the structural hash key for a value directly into `out`,
54/// avoiding intermediate allocations. Uses length-prefixed encoding
55/// for strings and dict keys to prevent separator collisions.
56fn write_structural_hash_key(v: &VmValue, out: &mut String) {
57    match v {
58        VmValue::Nil => out.push('N'),
59        VmValue::Bool(b) => {
60            out.push(if *b { 'T' } else { 'F' });
61        }
62        VmValue::Int(n) => {
63            out.push('i');
64            out.push_str(&n.to_string());
65            out.push(';');
66        }
67        VmValue::Float(n) => {
68            out.push('f');
69            out.push_str(&n.to_bits().to_string());
70            out.push(';');
71        }
72        VmValue::String(s) => {
73            // Length-prefixed: s<len>:<content> — no ambiguity from content
74            out.push('s');
75            out.push_str(&s.len().to_string());
76            out.push(':');
77            out.push_str(s);
78        }
79        VmValue::Bytes(bytes) => {
80            out.push('b');
81            for byte in bytes.iter() {
82                out.push_str(&format!("{byte:02x}"));
83            }
84            out.push(';');
85        }
86        VmValue::Duration(ms) => {
87            out.push('d');
88            out.push_str(&ms.to_string());
89            out.push(';');
90        }
91        VmValue::List(items) => {
92            out.push('L');
93            for item in items.iter() {
94                write_structural_hash_key(item, out);
95                out.push(',');
96            }
97            out.push(']');
98        }
99        VmValue::Dict(map) => {
100            out.push('D');
101            for (k, v) in map.iter() {
102                // Length-prefixed key
103                out.push_str(&k.len().to_string());
104                out.push(':');
105                out.push_str(k);
106                out.push('=');
107                write_structural_hash_key(v, out);
108                out.push(',');
109            }
110            out.push('}');
111        }
112        VmValue::Set(items) => {
113            // Sets need sorted keys for order-independence
114            let mut keys: Vec<String> = items.iter().map(value_structural_hash_key).collect();
115            keys.sort();
116            out.push('S');
117            for k in &keys {
118                out.push_str(k);
119                out.push(',');
120            }
121            out.push('}');
122        }
123        other => {
124            let tn = other.type_name();
125            out.push('o');
126            out.push_str(&tn.len().to_string());
127            out.push(':');
128            out.push_str(tn);
129            let d = other.display();
130            out.push_str(&d.len().to_string());
131            out.push(':');
132            out.push_str(&d);
133        }
134    }
135}
136
137pub fn values_equal(a: &VmValue, b: &VmValue) -> bool {
138    match (a, b) {
139        (VmValue::Int(x), VmValue::Int(y)) => x == y,
140        (VmValue::Float(x), VmValue::Float(y)) => x == y,
141        (VmValue::String(x), VmValue::String(y)) => x == y,
142        (VmValue::Bytes(x), VmValue::Bytes(y)) => x == y,
143        (VmValue::Bool(x), VmValue::Bool(y)) => x == y,
144        (VmValue::Nil, VmValue::Nil) => true,
145        (VmValue::Int(x), VmValue::Float(y)) => (*x as f64) == *y,
146        (VmValue::Float(x), VmValue::Int(y)) => *x == (*y as f64),
147        (VmValue::TaskHandle(a), VmValue::TaskHandle(b)) => a == b,
148        (VmValue::Channel(_), VmValue::Channel(_)) => false, // channels are never equal
149        (VmValue::Atomic(a), VmValue::Atomic(b)) => {
150            a.value.load(Ordering::SeqCst) == b.value.load(Ordering::SeqCst)
151        }
152        (VmValue::List(a), VmValue::List(b)) => {
153            a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| values_equal(x, y))
154        }
155        (VmValue::Dict(a), VmValue::Dict(b)) => {
156            a.len() == b.len()
157                && a.iter()
158                    .zip(b.iter())
159                    .all(|((k1, v1), (k2, v2))| k1 == k2 && values_equal(v1, v2))
160        }
161        (
162            VmValue::EnumVariant {
163                enum_name: a_e,
164                variant: a_v,
165                fields: a_f,
166            },
167            VmValue::EnumVariant {
168                enum_name: b_e,
169                variant: b_v,
170                fields: b_f,
171            },
172        ) => {
173            a_e == b_e
174                && a_v == b_v
175                && a_f.len() == b_f.len()
176                && a_f.iter().zip(b_f.iter()).all(|(x, y)| values_equal(x, y))
177        }
178        (
179            VmValue::StructInstance {
180                struct_name: a_s,
181                fields: a_f,
182            },
183            VmValue::StructInstance {
184                struct_name: b_s,
185                fields: b_f,
186            },
187        ) => {
188            a_s == b_s
189                && a_f.len() == b_f.len()
190                && a_f
191                    .iter()
192                    .zip(b_f.iter())
193                    .all(|((k1, v1), (k2, v2))| k1 == k2 && values_equal(v1, v2))
194        }
195        (VmValue::Set(a), VmValue::Set(b)) => {
196            a.len() == b.len() && a.iter().all(|x| b.iter().any(|y| values_equal(x, y)))
197        }
198        (VmValue::Generator(_), VmValue::Generator(_)) => false, // generators are never equal
199        (VmValue::Range(a), VmValue::Range(b)) => {
200            a.start == b.start && a.end == b.end && a.inclusive == b.inclusive
201        }
202        (VmValue::Iter(a), VmValue::Iter(b)) => Rc::ptr_eq(a, b),
203        (VmValue::Pair(a), VmValue::Pair(b)) => {
204            values_equal(&a.0, &b.0) && values_equal(&a.1, &b.1)
205        }
206        _ => false,
207    }
208}
209
210pub fn compare_values(a: &VmValue, b: &VmValue) -> i32 {
211    match (a, b) {
212        (VmValue::Int(x), VmValue::Int(y)) => x.cmp(y) as i32,
213        (VmValue::Float(x), VmValue::Float(y)) => {
214            if x < y {
215                -1
216            } else if x > y {
217                1
218            } else {
219                0
220            }
221        }
222        (VmValue::Int(x), VmValue::Float(y)) => {
223            let x = *x as f64;
224            if x < *y {
225                -1
226            } else if x > *y {
227                1
228            } else {
229                0
230            }
231        }
232        (VmValue::Float(x), VmValue::Int(y)) => {
233            let y = *y as f64;
234            if *x < y {
235                -1
236            } else if *x > y {
237                1
238            } else {
239                0
240            }
241        }
242        (VmValue::String(x), VmValue::String(y)) => x.cmp(y) as i32,
243        (VmValue::Pair(x), VmValue::Pair(y)) => {
244            let c = compare_values(&x.0, &y.0);
245            if c != 0 {
246                c
247            } else {
248                compare_values(&x.1, &y.1)
249            }
250        }
251        _ => 0,
252    }
253}