use serde::{Deserialize, Serialize}; use std::cmp::Ordering; /// Represents any piece of data that can be stored on the VM stack or in globals. /// /// The PVM is "dynamically typed" at the bytecode level, meaning a single /// `Value` enum can hold different primitive types. The VM performs /// automatic type promotion (e.g., adding an Int32 to a Float64 results /// in a Float64) to ensure mathematical correctness. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum Value { /// 32-bit signed integer. Used for most loop counters and indices. Int32(i32), /// 64-bit signed integer. Used for large numbers and timestamps. Int64(i64), /// 64-bit double precision float. Used for physics and complex math. Float(f64), /// Boolean value (true/false). Boolean(bool), /// UTF-8 string. Strings are immutable and usually come from the Constant Pool. String(String), /// Bounded 16-bit-ish integer. Bounded(u32), /// A pointer to an object on the heap. Gate(usize), /// Represents the absence of a value (equivalent to `null` or `undefined`). Null, } impl PartialEq for Value { fn eq(&self, other: &Self) -> bool { match (self, other) { (Value::Int32(a), Value::Int32(b)) => a == b, (Value::Int64(a), Value::Int64(b)) => a == b, (Value::Int32(a), Value::Int64(b)) => *a as i64 == *b, (Value::Int64(a), Value::Int32(b)) => *a == *b as i64, (Value::Float(a), Value::Float(b)) => a == b, (Value::Int32(a), Value::Float(b)) => *a as f64 == *b, (Value::Float(a), Value::Int32(b)) => *a == *b as f64, (Value::Int64(a), Value::Float(b)) => *a as f64 == *b, (Value::Float(a), Value::Int64(b)) => *a == *b as f64, (Value::Boolean(a), Value::Boolean(b)) => a == b, (Value::String(a), Value::String(b)) => a == b, (Value::Bounded(a), Value::Bounded(b)) => a == b, (Value::Gate(a), Value::Gate(b)) => a == b, (Value::Null, Value::Null) => true, _ => false, } } } impl PartialOrd for Value { fn partial_cmp(&self, other: &Self) -> Option { match (self, other) { (Value::Int32(a), Value::Int32(b)) => a.partial_cmp(b), (Value::Int64(a), Value::Int64(b)) => a.partial_cmp(b), (Value::Int32(a), Value::Int64(b)) => (*a as i64).partial_cmp(b), (Value::Int64(a), Value::Int32(b)) => a.partial_cmp(&(*b as i64)), (Value::Float(a), Value::Float(b)) => a.partial_cmp(b), (Value::Bounded(a), Value::Bounded(b)) => a.partial_cmp(b), (Value::Int32(a), Value::Float(b)) => (*a as f64).partial_cmp(b), (Value::Float(a), Value::Int32(b)) => a.partial_cmp(&(*b as f64)), (Value::Int64(a), Value::Float(b)) => (*a as f64).partial_cmp(b), (Value::Float(a), Value::Int64(b)) => a.partial_cmp(&(*b as f64)), (Value::Boolean(a), Value::Boolean(b)) => a.partial_cmp(b), (Value::String(a), Value::String(b)) => a.partial_cmp(b), _ => None, } } } impl Value { pub fn as_float(&self) -> Option { match self { Value::Int32(i) => Some(*i as f64), Value::Int64(i) => Some(*i as f64), Value::Float(f) => Some(*f), Value::Bounded(b) => Some(*b as f64), _ => None, } } pub fn as_integer(&self) -> Option { match self { Value::Int32(i) => Some(*i as i64), Value::Int64(i) => Some(*i), Value::Float(f) => Some(*f as i64), Value::Bounded(b) => Some(*b as i64), _ => None, } } pub fn to_string(&self) -> String { match self { Value::Int32(i) => i.to_string(), Value::Int64(i) => i.to_string(), Value::Float(f) => f.to_string(), Value::Bounded(b) => format!("{}b", b), Value::Boolean(b) => b.to_string(), Value::String(s) => s.clone(), Value::Gate(r) => format!("[Gate {}]", r), Value::Null => "null".to_string(), } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_value_equality() { assert_eq!(Value::Int32(10), Value::Int32(10)); assert_eq!(Value::Int64(10), Value::Int64(10)); assert_eq!(Value::Int32(10), Value::Int64(10)); assert_eq!(Value::Int64(10), Value::Int32(10)); assert_eq!(Value::Float(10.5), Value::Float(10.5)); assert_eq!(Value::Int32(10), Value::Float(10.0)); assert_eq!(Value::Float(10.0), Value::Int32(10)); assert_eq!(Value::Int64(10), Value::Float(10.0)); assert_eq!(Value::Float(10.0), Value::Int64(10)); assert_ne!(Value::Int32(10), Value::Int32(11)); assert_ne!(Value::Int64(10), Value::Int64(11)); assert_ne!(Value::Int32(10), Value::Int64(11)); assert_ne!(Value::Int32(10), Value::Float(10.1)); assert_eq!(Value::Boolean(true), Value::Boolean(true)); assert_ne!(Value::Boolean(true), Value::Boolean(false)); assert_eq!(Value::String("oi".into()), Value::String("oi".into())); assert_eq!(Value::Null, Value::Null); } #[test] fn test_value_conversions() { let v_int32 = Value::Int32(42); assert_eq!(v_int32.as_float(), Some(42.0)); assert_eq!(v_int32.as_integer(), Some(42)); let v_int64 = Value::Int64(42); assert_eq!(v_int64.as_float(), Some(42.0)); assert_eq!(v_int64.as_integer(), Some(42)); let v_float = Value::Float(42.7); assert_eq!(v_float.as_float(), Some(42.7)); assert_eq!(v_float.as_integer(), Some(42)); let v_bool = Value::Boolean(true); assert_eq!(v_bool.as_float(), None); assert_eq!(v_bool.as_integer(), None); } }