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