#[derive(Debug, Clone, PartialEq)]
pub enum ConstValue {
I64(i64),
F64(f64),
Bool(bool),
Byte(u8),
Str(String),
Void,
Function(u16),
}
impl std::fmt::Display for ConstValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ConstValue::I64(n) => write!(f, "{n}"),
ConstValue::F64(x) => {
if x.is_nan() {
f.write_str("NaN")
} else if *x == f64::INFINITY {
f.write_str("inf")
} else if *x == f64::NEG_INFINITY {
f.write_str("-inf")
} else {
write!(f, "{x}")
}
}
ConstValue::Bool(b) => f.write_str(if *b { "true" } else { "false" }),
ConstValue::Byte(b) => write!(f, "b'\\x{b:02x}'"),
ConstValue::Str(s) => write!(f, "'{s}'"),
ConstValue::Void => f.write_str("()"),
ConstValue::Function(id) => write!(f, "fn#{id}"),
}
}
}
#[allow(dead_code)]
const QNAN: u64 = 0x7FF8_0000_0000_0000;
const TAG_FN: u64 = 0x7FFB_0000_0000_0000;
const TAG_BOOL: u64 = 0x7FFC_0000_0000_0000;
const TAG_BYTE: u64 = 0x7FFD_0000_0000_0000;
const TAG_VOID: u64 = 0x7FFE_0000_0000_0000;
const TAG_PTR: u64 = 0x7FFF_0000_0000_0000;
const KIND_MASK: u64 = 0xFFFF_0000_0000_0000;
const DATA_MASK: u64 = 0x0000_FFFF_FFFF_FFFF;
#[derive(Clone, Copy, PartialEq)]
pub struct Value(u64);
impl Value {
pub fn from_f64(x: f64) -> Value {
Value(x.to_bits())
}
pub fn bool(b: bool) -> Value {
Value(TAG_BOOL | b as u64)
}
pub fn byte(b: u8) -> Value {
Value(TAG_BYTE | b as u64)
}
pub fn void() -> Value {
Value(TAG_VOID)
}
pub fn pointer(slot: u32) -> Value {
Value(TAG_PTR | slot as u64)
}
pub fn function(id: u16) -> Value {
Value(TAG_FN | id as u64)
}
pub fn bits(self) -> u64 {
self.0
}
pub fn is_tagged(self) -> bool {
let top = self.0 & KIND_MASK;
(TAG_FN..=TAG_PTR).contains(&top)
}
pub fn as_f64(self) -> Option<f64> {
if self.is_tagged() {
None
} else {
Some(f64::from_bits(self.0))
}
}
pub fn as_bool(self) -> Option<bool> {
if self.0 & KIND_MASK == TAG_BOOL {
Some(self.0 & DATA_MASK != 0)
} else {
None
}
}
pub fn as_byte(self) -> Option<u8> {
if self.0 & KIND_MASK == TAG_BYTE {
Some((self.0 & 0xFF) as u8)
} else {
None
}
}
pub fn as_void(self) -> bool {
self.0 == TAG_VOID
}
pub fn as_pointer(self) -> Option<u32> {
if self.0 & KIND_MASK == TAG_PTR {
Some((self.0 & 0xFFFF_FFFF) as u32)
} else {
None
}
}
pub fn as_function(self) -> Option<u16> {
if self.0 & KIND_MASK == TAG_FN {
Some((self.0 & 0xFFFF) as u16)
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn cases() -> Vec<(ConstValue, &'static str)> {
vec![
(ConstValue::I64(42), "42"),
(ConstValue::I64(0), "0"),
(ConstValue::I64(-1), "-1"),
(ConstValue::I64(i64::MIN), "-9223372036854775808"),
(ConstValue::I64(i64::MAX), "9223372036854775807"),
(ConstValue::Bool(true), "true"),
(ConstValue::Bool(false), "false"),
(ConstValue::Byte(0), "b'\\x00'"),
(ConstValue::Byte(0xff), "b'\\xff'"),
(ConstValue::Byte(0xa3), "b'\\xa3'"),
(ConstValue::Str("hello".to_string()), "'hello'"),
(ConstValue::Str(String::new()), "''"),
(ConstValue::Void, "()"),
(ConstValue::Function(0), "fn#0"),
(ConstValue::Function(65535), "fn#65535"),
]
}
#[test]
fn display_i64_uses_rust_default_decimal_form() {
assert_eq!(ConstValue::I64(42).to_string(), "42");
assert_eq!(
ConstValue::I64(i64::MIN).to_string(),
"-9223372036854775808"
);
}
#[test]
fn display_f64_round_trips_finite_values_byte_identically() {
let v = ConstValue::F64(3.5);
assert_eq!(v.to_string(), "3.5");
let v2 = ConstValue::F64(0.1 + 0.2);
assert_eq!(v2.to_string(), v2.to_string());
}
#[test]
fn display_f64_spells_non_finite_explicitly() {
assert_eq!(ConstValue::F64(f64::INFINITY).to_string(), "inf");
assert_eq!(ConstValue::F64(f64::NEG_INFINITY).to_string(), "-inf");
assert_eq!(ConstValue::F64(f64::NAN).to_string(), "NaN");
}
#[test]
fn display_bool_uses_lowercase_keywords() {
assert_eq!(ConstValue::Bool(true).to_string(), "true");
assert_eq!(ConstValue::Bool(false).to_string(), "false");
}
#[test]
fn display_byte_uses_lowercase_two_digit_hex() {
assert_eq!(ConstValue::Byte(0).to_string(), "b'\\x00'");
assert_eq!(ConstValue::Byte(0xff).to_string(), "b'\\xff'");
assert_eq!(ConstValue::Byte(0xa3).to_string(), "b'\\xa3'");
}
#[test]
fn display_str_wraps_inner_bytes_in_single_quotes_without_escaping() {
assert_eq!(ConstValue::Str("hello".to_string()).to_string(), "'hello'");
assert_eq!(ConstValue::Str(String::new()).to_string(), "''");
}
#[test]
fn display_void_renders_as_empty_tuple_form() {
assert_eq!(ConstValue::Void.to_string(), "()");
}
#[test]
fn display_function_uses_hash_sigil_and_decimal_id() {
assert_eq!(ConstValue::Function(0).to_string(), "fn#0");
assert_eq!(ConstValue::Function(65535).to_string(), "fn#65535");
}
#[test]
fn every_variant_renders_to_its_locked_spelling() {
for (value, expected) in cases() {
assert_eq!(value.to_string(), expected, "Display drift for {value:?}");
}
}
#[test]
fn display_is_deterministic_across_repeated_calls() {
let values: Vec<ConstValue> = vec![
ConstValue::I64(1),
ConstValue::F64(2.5),
ConstValue::F64(f64::INFINITY),
ConstValue::F64(f64::NEG_INFINITY),
ConstValue::F64(f64::NAN),
ConstValue::Bool(true),
ConstValue::Bool(false),
ConstValue::Byte(0x10),
ConstValue::Str("hi".to_string()),
ConstValue::Void,
ConstValue::Function(7),
];
let first: String = values
.iter()
.map(|v| v.to_string())
.collect::<Vec<_>>()
.join(",");
let second: String = values
.iter()
.map(|v| v.to_string())
.collect::<Vec<_>>()
.join(",");
assert_eq!(first, second, "Display is non-deterministic");
}
#[test]
fn partial_eq_holds_for_finite_primitives_and_breaks_for_nan() {
assert_eq!(ConstValue::I64(1), ConstValue::I64(1));
assert_eq!(ConstValue::F64(0.0), ConstValue::F64(0.0));
assert_eq!(ConstValue::Bool(true), ConstValue::Bool(true));
assert_eq!(ConstValue::Byte(0xab), ConstValue::Byte(0xab));
assert_eq!(
ConstValue::Str("x".to_string()),
ConstValue::Str("x".to_string())
);
assert_eq!(ConstValue::Void, ConstValue::Void);
assert_eq!(ConstValue::Function(3), ConstValue::Function(3));
assert_ne!(ConstValue::F64(f64::NAN), ConstValue::F64(f64::NAN));
assert_ne!(ConstValue::I64(0), ConstValue::Bool(false));
}
#[test]
fn value_is_exactly_eight_bytes() {
assert_eq!(std::mem::size_of::<Value>(), 8);
}
#[test]
fn from_f64_round_trips_a_finite_value() {
let v = Value::from_f64(3.5);
assert!(!v.is_tagged(), "a finite f64 must not be tagged");
assert_eq!(v.as_f64(), Some(3.5));
}
#[test]
fn from_f64_round_trips_positive_and_negative_infinity() {
let pos = Value::from_f64(f64::INFINITY);
assert!(!pos.is_tagged());
assert_eq!(pos.as_f64(), Some(f64::INFINITY));
let neg = Value::from_f64(f64::NEG_INFINITY);
assert!(!neg.is_tagged());
assert_eq!(neg.as_f64(), Some(f64::NEG_INFINITY));
}
#[test]
fn from_f64_round_trips_a_genuine_nan_as_a_real_float() {
let v = Value::from_f64(f64::NAN);
assert!(!v.is_tagged(), "a real NaN must not read as tagged");
let decoded = v.as_f64().expect("a NaN must decode as an f64");
assert!(decoded.is_nan(), "the decoded value must still be a NaN");
assert_eq!(v.as_bool(), None);
assert_eq!(v.as_byte(), None);
assert!(!v.as_void());
assert_eq!(v.as_pointer(), None);
assert_eq!(v.as_function(), None);
}
#[test]
fn from_f64_preserves_the_sign_bit_of_negative_zero() {
let v = Value::from_f64(-0.0);
assert!(!v.is_tagged());
let decoded = v.as_f64().expect("-0.0 decodes as an f64");
assert!(
decoded == 0.0 && decoded.is_sign_negative(),
"sign bit lost"
);
}
#[test]
fn bool_round_trips_true_and_false() {
let t = Value::bool(true);
assert!(t.is_tagged());
assert_eq!(t.as_bool(), Some(true));
assert_eq!(t.as_f64(), None);
assert_eq!(t.as_byte(), None);
let f = Value::bool(false);
assert!(f.is_tagged());
assert_eq!(f.as_bool(), Some(false));
}
#[test]
fn byte_round_trips_low_high_and_mid_values() {
for b in [0x00u8, 0xFF, 0xA3] {
let v = Value::byte(b);
assert!(v.is_tagged(), "a byte must be tagged");
assert_eq!(v.as_byte(), Some(b), "byte round-trip failed for {b:#x}");
assert_eq!(v.as_bool(), None);
assert_eq!(v.as_f64(), None);
}
}
#[test]
fn void_is_a_tagged_singleton() {
let v = Value::void();
assert!(v.is_tagged());
assert!(v.as_void(), "void must read as void");
assert_eq!(v.as_bool(), None);
assert_eq!(v.as_byte(), None);
assert_eq!(v.as_f64(), None);
assert_eq!(v.as_pointer(), None);
assert!(Value::void() == Value::void(), "void is not a singleton");
}
#[test]
fn pointer_round_trips_slot_zero_and_slot_u32_max() {
for slot in [0u32, 1, 0xABCD, u32::MAX] {
let v = Value::pointer(slot);
assert!(v.is_tagged(), "a pointer must be tagged");
assert_eq!(
v.as_pointer(),
Some(slot),
"pointer round-trip failed for slot {slot}"
);
assert!(!v.as_void());
assert_eq!(v.as_bool(), None);
}
}
#[test]
fn is_tagged_is_false_for_every_float_and_true_for_every_box() {
let floats = [
Value::from_f64(0.0),
Value::from_f64(-0.0),
Value::from_f64(1.0),
Value::from_f64(-7.25),
Value::from_f64(f64::INFINITY),
Value::from_f64(f64::NEG_INFINITY),
Value::from_f64(f64::NAN),
Value::from_f64(f64::MIN),
Value::from_f64(f64::MAX),
];
for f in floats {
assert!(!f.is_tagged(), "float {:#018x} read as tagged", f.bits());
}
let boxes = [
Value::function(0),
Value::function(u16::MAX),
Value::bool(true),
Value::bool(false),
Value::byte(0),
Value::byte(0xFF),
Value::void(),
Value::pointer(0),
Value::pointer(u32::MAX),
];
for b in boxes {
assert!(b.is_tagged(), "box {:#018x} read as not-tagged", b.bits());
}
}
#[test]
fn the_five_tag_prefixes_are_distinct_and_none_equals_the_bare_qnan() {
const _: () = assert!(
(QNAN & KIND_MASK) < TAG_FN,
"QNAN prefix is inside the tagged range"
);
let tags = [TAG_FN, TAG_BOOL, TAG_BYTE, TAG_VOID, TAG_PTR];
for (i, a) in tags.iter().enumerate() {
for b in &tags[i + 1..] {
assert_ne!(a, b, "two tag prefixes collide");
}
assert!(*a > QNAN, "tag prefix {a:#018x} not above QNAN");
}
}
#[test]
fn function_round_trips_low_high_and_mid_ids() {
for id in [0u16, 1, 0x1234, u16::MAX] {
let v = Value::function(id);
assert!(v.is_tagged(), "a function must be tagged");
assert_eq!(
v.as_function(),
Some(id),
"function round-trip failed for id {id}"
);
assert_eq!(v.as_bool(), None);
assert_eq!(v.as_byte(), None);
assert_eq!(v.as_f64(), None);
assert_eq!(v.as_pointer(), None);
assert!(!v.as_void());
}
}
#[test]
fn a_function_is_not_a_pointer_and_a_pointer_is_not_a_function() {
let ptr = Value::pointer(5);
let func = Value::function(5);
assert_ne!(ptr.bits(), func.bits(), "pointer and function tags alias");
assert_eq!(ptr.as_pointer(), Some(5));
assert_eq!(ptr.as_function(), None);
assert_eq!(func.as_function(), Some(5));
assert_eq!(func.as_pointer(), None);
}
}