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