bQUARKz f9120e740b
dev/pbs (#8)
Co-authored-by: Nilton Constantino <nilton.constantino@visma.com>
Reviewed-on: #8
2026-03-24 13:40:22 +00:00

151 lines
5.8 KiB
Rust

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<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::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<f64> {
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<i64> {
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);
}
}