184 lines
6.8 KiB
Rust
184 lines
6.8 KiB
Rust
use serde::{Deserialize, Serialize};
|
|
use std::cmp::Ordering;
|
|
use std::fmt::Write;
|
|
use std::sync::Arc;
|
|
|
|
/// Opaque handle that references an object stored in the VM heap.
|
|
///
|
|
/// This is an index-based handle. It does not imply ownership and carries
|
|
/// no lifetime information. GC/allocator integration will come in later PRs.
|
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
|
pub struct HeapRef(pub u32);
|
|
|
|
/// 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(Arc<str>),
|
|
/// A handle to an object on the heap (opaque reference).
|
|
HeapRef(HeapRef),
|
|
/// 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::HeapRef(a), Value::HeapRef(b)) => a == b,
|
|
(Value::Null, Value::Null) => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl PartialOrd for Value {
|
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
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::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<f64> {
|
|
match self {
|
|
Value::Int32(i) => Some(*i as f64),
|
|
Value::Int64(i) => Some(*i as f64),
|
|
Value::Float(f) => Some(*f),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn as_integer(&self) -> Option<i64> {
|
|
match self {
|
|
Value::Int32(i) => Some(*i as i64),
|
|
Value::Int64(i) => Some(*i),
|
|
Value::Float(f) => Some(*f as i64),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn append_to_string(&self, out: &mut String) {
|
|
match self {
|
|
Value::Int32(i) => {
|
|
let _ = write!(out, "{}", i);
|
|
}
|
|
Value::Int64(i) => {
|
|
let _ = write!(out, "{}", i);
|
|
}
|
|
Value::Float(f) => {
|
|
let _ = write!(out, "{}", f);
|
|
}
|
|
Value::Boolean(b) => {
|
|
let _ = write!(out, "{}", b);
|
|
}
|
|
Value::String(s) => out.push_str(s),
|
|
Value::HeapRef(r) => {
|
|
let _ = write!(out, "[HeapRef {}]", r.0);
|
|
}
|
|
Value::Null => out.push_str("null"),
|
|
}
|
|
}
|
|
|
|
pub fn string_len_hint(&self) -> usize {
|
|
match self {
|
|
Value::String(s) => s.len(),
|
|
Value::Null => 4,
|
|
Value::Boolean(true) => 4,
|
|
Value::Boolean(false) => 5,
|
|
Value::Int32(i) => i.to_string().len(),
|
|
Value::Int64(i) => i.to_string().len(),
|
|
Value::Float(f) => f.to_string().len(),
|
|
Value::HeapRef(r) => 11 + r.0.to_string().len(),
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::inherent_to_string)]
|
|
pub fn to_string(&self) -> String {
|
|
let mut out = String::with_capacity(self.string_len_hint());
|
|
self.append_to_string(&mut out);
|
|
out
|
|
}
|
|
}
|
|
|
|
#[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);
|
|
}
|
|
}
|