use std::sync::atomic::Ordering;
use std::sync::Arc;
use super::VmValue;
pub fn values_identical(a: &VmValue, b: &VmValue) -> bool {
match (a, b) {
(VmValue::List(x), VmValue::List(y)) => Arc::ptr_eq(x, y),
(VmValue::Dict(x), VmValue::Dict(y)) => Arc::ptr_eq(x, y),
(VmValue::Set(x), VmValue::Set(y)) => Arc::ptr_eq(x, y),
(VmValue::Closure(x), VmValue::Closure(y)) => Arc::ptr_eq(x, y),
(VmValue::String(x), VmValue::String(y)) => Arc::ptr_eq(x, y) || x == y,
(VmValue::Bytes(x), VmValue::Bytes(y)) => Arc::ptr_eq(x, y) || x == y,
(VmValue::BuiltinRef(x), VmValue::BuiltinRef(y)) => x == y,
(VmValue::BuiltinRefId { name: x, .. }, VmValue::BuiltinRefId { name: y, .. }) => x == y,
(VmValue::BuiltinRef(x), VmValue::BuiltinRefId { name: y, .. })
| (VmValue::BuiltinRefId { name: y, .. }, VmValue::BuiltinRef(x)) => x == y,
(VmValue::Pair(x), VmValue::Pair(y)) => Arc::ptr_eq(x, y),
_ => values_equal(a, b),
}
}
pub fn value_identity_key(v: &VmValue) -> String {
match v {
VmValue::List(x) => format!("list@{:p}", Arc::as_ptr(x)),
VmValue::Dict(x) => format!("dict@{:p}", Arc::as_ptr(x)),
VmValue::Set(x) => format!("set@{:p}", Arc::as_ptr(x)),
VmValue::Closure(x) => format!("closure@{:p}", Arc::as_ptr(x)),
VmValue::String(x) => format!("string@{:p}", x.as_ptr()),
VmValue::Bytes(x) => format!("bytes@{:p}", Arc::as_ptr(x)),
VmValue::BuiltinRef(name) => format!("builtin@{name}"),
VmValue::BuiltinRefId { name, .. } => format!("builtin@{name}"),
other => format!("{}@{}", other.type_name(), other.display()),
}
}
pub fn value_structural_hash_key(v: &VmValue) -> String {
let mut out = String::new();
write_structural_hash_key(v, &mut out);
out
}
fn write_structural_hash_key(v: &VmValue, out: &mut String) {
match v {
VmValue::Nil => out.push('N'),
VmValue::Bool(b) => {
out.push(if *b { 'T' } else { 'F' });
}
VmValue::Int(n) => write_int_hash_key(*n, out),
VmValue::Float(n) => {
match float_as_equivalent_int(*n) {
Some(i) => write_int_hash_key(i, out),
None => {
out.push('f');
out.push_str(&n.to_bits().to_string());
out.push(';');
}
}
}
VmValue::String(s) => {
out.push('s');
write_len_prefixed(s, out);
}
VmValue::Bytes(bytes) => {
out.push('b');
for byte in bytes.iter() {
out.push_str(&format!("{byte:02x}"));
}
out.push(';');
}
VmValue::Duration(ms) => {
out.push('d');
out.push_str(&ms.to_string());
out.push(';');
}
VmValue::List(items) => {
out.push('L');
for item in items.iter() {
write_structural_hash_key(item, out);
out.push(',');
}
out.push(']');
}
VmValue::Dict(map) => {
out.push('D');
for (k, v) in map.iter() {
write_len_prefixed(k, out);
out.push('=');
write_structural_hash_key(v, out);
out.push(',');
}
out.push('}');
}
VmValue::Set(items) => {
let mut keys: Vec<String> = items.iter().map(value_structural_hash_key).collect();
keys.sort();
out.push('S');
for k in &keys {
out.push_str(k);
out.push(',');
}
out.push('}');
}
VmValue::Pair(pair) => {
out.push('P');
write_structural_hash_key(&pair.0, out);
out.push(',');
write_structural_hash_key(&pair.1, out);
out.push(';');
}
VmValue::EnumVariant(ev) => {
out.push('E');
write_len_prefixed(&ev.enum_name, out);
write_len_prefixed(&ev.variant, out);
for field in ev.fields.iter() {
write_structural_hash_key(field, out);
out.push(',');
}
out.push(';');
}
VmValue::StructInstance { layout, fields } => {
out.push('I');
write_len_prefixed(layout.struct_name(), out);
for (k, v) in super::struct_fields_to_map(layout, fields) {
write_len_prefixed(&k, out);
out.push('=');
write_structural_hash_key(&v, out);
out.push(',');
}
out.push('}');
}
other => {
out.push('o');
write_len_prefixed(other.type_name(), out);
write_len_prefixed(&other.display(), out);
}
}
}
fn write_len_prefixed(s: &str, out: &mut String) {
out.push_str(&s.len().to_string());
out.push(':');
out.push_str(s);
}
fn write_int_hash_key(n: i64, out: &mut String) {
out.push('i');
out.push_str(&n.to_string());
out.push(';');
}
fn float_as_equivalent_int(n: f64) -> Option<i64> {
let candidate = n as i64; ((candidate as f64) == n).then_some(candidate)
}
pub fn values_equal(a: &VmValue, b: &VmValue) -> bool {
match (a, b) {
(VmValue::Int(x), VmValue::Int(y)) => x == y,
(VmValue::Float(x), VmValue::Float(y)) => x == y,
(VmValue::String(x), VmValue::String(y)) => x == y,
(VmValue::Bytes(x), VmValue::Bytes(y)) => x == y,
(VmValue::BuiltinRef(x), VmValue::BuiltinRef(y)) => x == y,
(VmValue::BuiltinRefId { name: x, .. }, VmValue::BuiltinRefId { name: y, .. }) => x == y,
(VmValue::BuiltinRef(x), VmValue::BuiltinRefId { name: y, .. })
| (VmValue::BuiltinRefId { name: y, .. }, VmValue::BuiltinRef(x)) => x == y,
(VmValue::Bool(x), VmValue::Bool(y)) => x == y,
(VmValue::Nil, VmValue::Nil) => true,
(VmValue::Int(x), VmValue::Float(y)) => (*x as f64) == *y,
(VmValue::Float(x), VmValue::Int(y)) => *x == (*y as f64),
(VmValue::TaskHandle(a), VmValue::TaskHandle(b)) => a == b,
(VmValue::Channel(_), VmValue::Channel(_)) => false, (VmValue::Rng(_), VmValue::Rng(_)) => false,
(VmValue::SyncPermit(_), VmValue::SyncPermit(_)) => false,
(VmValue::Atomic(a), VmValue::Atomic(b)) => {
a.value.load(Ordering::SeqCst) == b.value.load(Ordering::SeqCst)
}
(VmValue::List(a), VmValue::List(b)) => {
a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| values_equal(x, y))
}
(VmValue::Dict(a), VmValue::Dict(b)) => {
a.len() == b.len()
&& a.iter()
.zip(b.iter())
.all(|((k1, v1), (k2, v2))| k1 == k2 && values_equal(v1, v2))
}
(VmValue::EnumVariant(a), VmValue::EnumVariant(b)) => {
a.enum_name == b.enum_name
&& a.variant == b.variant
&& a.fields.len() == b.fields.len()
&& a.fields
.iter()
.zip(b.fields.iter())
.all(|(x, y)| values_equal(x, y))
}
(
VmValue::StructInstance {
layout: a_layout,
fields: a_fields,
},
VmValue::StructInstance {
layout: b_layout,
fields: b_fields,
},
) => {
if a_layout.struct_name() != b_layout.struct_name() {
return false;
}
let a_map = super::struct_fields_to_map(a_layout, a_fields);
let b_map = super::struct_fields_to_map(b_layout, b_fields);
a_map.len() == b_map.len()
&& a_map
.iter()
.zip(b_map.iter())
.all(|((k1, v1), (k2, v2))| k1 == k2 && values_equal(v1, v2))
}
(VmValue::Set(a), VmValue::Set(b)) => {
a.len() == b.len() && a.iter().all(|x| b.iter().any(|y| values_equal(x, y)))
}
(VmValue::Generator(_), VmValue::Generator(_)) => false, (VmValue::Stream(_), VmValue::Stream(_)) => false, (VmValue::Range(a), VmValue::Range(b)) => {
a.start == b.start && a.end == b.end && a.inclusive == b.inclusive
}
(VmValue::Iter(a), VmValue::Iter(b)) => Arc::ptr_eq(a, b),
(VmValue::Pair(a), VmValue::Pair(b)) => {
values_equal(&a.0, &b.0) && values_equal(&a.1, &b.1)
}
(VmValue::Harness(_), VmValue::Harness(_)) => false,
_ => false,
}
}
pub fn dedup_values<'a, I>(items: I) -> Vec<VmValue>
where
I: IntoIterator<Item = &'a VmValue>,
{
use std::collections::HashMap;
let mut buckets: HashMap<String, Vec<VmValue>> = HashMap::new();
let mut result = Vec::new();
for item in items {
let bucket = buckets.entry(value_structural_hash_key(item)).or_default();
if !bucket.iter().any(|kept| values_equal(kept, item)) {
bucket.push(item.clone());
result.push(item.clone());
}
}
result
}
pub fn compare_values(a: &VmValue, b: &VmValue) -> i32 {
try_compare_values(a, b).unwrap_or(0)
}
pub fn try_compare_values(a: &VmValue, b: &VmValue) -> Option<i32> {
match (a, b) {
(VmValue::Int(x), VmValue::Int(y)) => Some(x.cmp(y) as i32),
(VmValue::Float(x), VmValue::Float(y)) => float_ordering(*x, *y),
(VmValue::Int(x), VmValue::Float(y)) => float_ordering(*x as f64, *y),
(VmValue::Float(x), VmValue::Int(y)) => float_ordering(*x, *y as f64),
(VmValue::String(x), VmValue::String(y)) => Some(x.cmp(y) as i32),
(VmValue::Pair(x), VmValue::Pair(y)) => {
let c = try_compare_values(&x.0, &y.0)?;
if c != 0 {
Some(c)
} else {
try_compare_values(&x.1, &y.1)
}
}
_ => Some(0),
}
}
fn float_ordering(x: f64, y: f64) -> Option<i32> {
x.partial_cmp(&y).map(|ord| ord as i32)
}