use sandpit::{Gc, Mutator, Tag, Tagged, Trace};
use super::func::Func;
use super::hash_map::GcHashMap;
use super::list::List;
use super::native_func::NativeFunc;
use super::string::VMString;
use super::value::Value;
#[derive(Trace, Clone)]
pub struct TaggedValue<'gc> {
pub ptr: Tagged<'gc, ValueTag>
}
#[derive(Debug, Tag, PartialEq)]
pub enum ValueTag {
Packed,
#[ptr(f64)]
Float,
#[ptr(i64)]
Int,
#[ptr(List<'gc>)]
List,
#[ptr(Func<'gc>)]
Func,
#[ptr(VMString<'gc>)]
String,
#[ptr(GcHashMap<'gc>)]
Map,
#[ptr(NativeFunc)]
NativeFunc,
}
impl<'gc> From<&TaggedValue<'gc>> for Value<'gc> {
fn from(value: &TaggedValue<'gc>) -> Self {
let ptr = value.ptr.clone();
match ptr.get_tag() {
ValueTag::Float => {
let v = ValueTag::get_float(ptr).unwrap();
Value::Float(*v)
}
ValueTag::Func => {
let v = ValueTag::get_func(ptr).unwrap();
Value::Func(v)
}
ValueTag::Int => {
let v = ValueTag::get_int(ptr).unwrap();
Value::Int(*v)
}
ValueTag::List => {
let v = ValueTag::get_list(ptr).unwrap();
Value::List(v)
}
ValueTag::String => {
let v = ValueTag::get_string(ptr).unwrap();
Value::String(v)
}
ValueTag::Map => {
let v = ValueTag::get_map(ptr).unwrap();
Value::Map(v)
}
ValueTag::NativeFunc => {
let v = ValueTag::get_nativefunc(ptr).unwrap();
Value::NativeFunc(v)
}
ValueTag::Packed => {
let raw = ptr.get_stripped_raw() as u64;
TaggedValue::unpack(raw)
}
}
}
}
enum PackedTag {
SymId,
Int,
Bool,
Null,
}
impl<'gc> TaggedValue<'gc> {
pub fn __new(ptr: Tagged<'gc, ValueTag>) -> Self {
Self { ptr }
}
pub fn __get_ptr(&self) -> Tagged<'gc, ValueTag> {
self.ptr.clone()
}
pub fn new_null() -> Self {
let raw: u64 = (PackedTag::Null as u64) << 3;
let ptr = Tagged::from_raw(raw as usize, ValueTag::Packed);
Self {
ptr
}
}
pub fn new_bool(b: bool) -> Self {
let mut raw: u64 = (PackedTag::Bool as u64) << 3;
if b {
let value_mask: u64 = (u32::MAX as u64) ^ u64::MAX;
raw ^= value_mask;
}
let ptr = Tagged::from_raw(raw as usize, ValueTag::Packed);
Self { ptr }
}
pub fn from_value(value: Value<'gc>, mu: &'gc Mutator) -> Self {
if let Some(tagged) = Self::try_packing(&value) {
return tagged;
}
let ptr =
match value {
Value::List(gc_list) => ValueTag::from_list(gc_list),
Value::Func(func) => ValueTag::from_func(func),
Value::Float(f) => ValueTag::from_float(Gc::new(mu, f)),
Value::String(s) => ValueTag::from_string(s),
Value::Map(c) => ValueTag::from_map(c),
Value::NativeFunc(nf) => ValueTag::from_nativefunc(nf),
Value::Int(i) => ValueTag::from_int(Gc::new(mu, i)),
_ => panic!("failed to convert value into tagged value"),
};
Self { ptr }
}
pub fn set_null(&self) {
let raw: u64 = (PackedTag::Null as u64) << 3;
self.ptr.set_raw(raw as usize, ValueTag::Packed);
}
pub fn is_truthy(&self) -> bool {
match self.ptr.get_tag() {
ValueTag::Packed => {
let raw = self.ptr.get_stripped_raw() as u64;
match TaggedValue::unpack(raw) {
Value::Null |
Value::Int(0) |
Value::Bool(false) => false,
_ => true
}
}
_ => {
Value::from(self).is_truthy()
}
}
}
fn unpack(raw: u64) -> Value<'gc> {
let packed_tag_mask: u64 = 7 << 3;
let value_mask: u64 = (u32::MAX as u64) ^ u64::MAX;
let packed_tag: u64 = (raw & packed_tag_mask) >> 3;
let packed_value: u32 = u32::try_from((raw & value_mask) >> 32).unwrap();
if (PackedTag::SymId as u64) == packed_tag {
return Value::SymId(packed_value);
}
if (PackedTag::Null as u64) == packed_tag {
return Value::Null;
}
if (PackedTag::Int as u64) == packed_tag {
let packed_int = i32::from_ne_bytes(packed_value.to_ne_bytes());
return Value::Int(packed_int as i64);
}
if (PackedTag::Bool as u64) == packed_tag {
let packed_bool = packed_value != 0;
return Value::Bool(packed_bool);
}
panic!("Bad packed value")
}
fn try_packing(value: &Value<'gc>) -> Option<TaggedValue<'gc>> {
let tagged = match value {
Value::Null => {
return Some(TaggedValue::new_null())
}
Value::Bool(b) => {
return Some(TaggedValue::new_bool(*b))
}
Value::SymId(id) => {
let mut raw: u64 = (*id as u64) << 32;
raw ^= (PackedTag::SymId as u64) << 3;
Tagged::from_raw(raw as usize, ValueTag::Packed)
}
Value::Int(i) => match i32::try_from(*i) {
Ok(i) => {
let mut raw = u32::from_ne_bytes(i32::to_ne_bytes(i)) as u64;
raw <<= 32;
raw ^= (PackedTag::Int as u64) << 3;
Tagged::from_raw(raw as usize, ValueTag::Packed)
}
Err(_) => return None,
},
_ => return None,
};
Some(TaggedValue { ptr: tagged })
}
}
#[cfg(test)]
mod tests {
use super::*;
use sandpit::{Arena, Root};
#[test]
fn pack_and_unpack_null_value() {
let _: Arena<Root![()]> = Arena::new(|_| {
let tagged = TaggedValue::new_null();
assert_eq!(tagged.ptr.get_tag(), ValueTag::Packed);
let unpacked = Value::from(&tagged);
if let Value::Null = unpacked {
assert!(true);
} else {
assert!(false);
}
});
}
#[test]
fn pack_and_unpack_bool_value() {
let _: Arena<Root![()]> = Arena::new(|_| {
let tagged = TaggedValue::new_bool(false);
assert_eq!(tagged.ptr.get_tag(), ValueTag::Packed);
let unpacked = Value::from(&tagged);
if let Value::Bool(false) = unpacked {
assert!(true);
} else {
assert!(false);
}
});
}
#[test]
fn pack_and_unpack_sym_id() {
let _: Arena<Root![()]> = Arena::new(|mu| {
let v = Value::SymId(18);
let tagged = v.as_tagged(mu);
assert_eq!(tagged.ptr.get_tag(), ValueTag::Packed);
let unpacked = Value::from(&tagged);
if let Value::SymId(18) = unpacked {
assert!(true);
} else {
assert!(false);
}
});
}
#[test]
fn pack_and_unpack_small_float() {
let _: Arena<Root![()]> = Arena::new(|mu| {
let v = Value::Float(420.69);
let tagged = v.as_tagged(mu);
assert_eq!(tagged.ptr.get_tag(), ValueTag::Float);
let unpacked = Value::from(&tagged);
if let Value::Float(f) = unpacked {
assert_eq!(420.69, f);
} else {
assert!(false);
}
});
}
#[test]
fn pack_and_unpack_int() {
let _: Arena<Root![()]> = Arena::new(|mu| {
let v = Value::Int(-333);
let tagged = v.as_tagged(mu);
assert_eq!(tagged.ptr.get_tag(), ValueTag::Packed);
let unpacked = Value::from(&tagged);
if let Value::Int(-333) = unpacked {
assert!(true);
} else {
assert!(false);
}
});
}
#[test]
fn tagged_native_func_round_trip() {
use sandpit::Gc;
use crate::runtime::native_func::NativeFunc;
use crate::runtime::error::RuntimeError;
fn dummy_fn<'gc>(_args: &[Value<'gc>], _mu: &'gc Mutator) -> Result<Value<'gc>, RuntimeError> {
Ok(Value::Null)
}
let _: Arena<Root![()]> = Arena::new(|mu| {
let nf = NativeFunc { arity: 0, func: dummy_fn };
let gc_nf = Gc::new(mu, nf);
let v = Value::NativeFunc(gc_nf);
let tagged = v.as_tagged(mu);
assert_eq!(tagged.ptr.get_tag(), ValueTag::NativeFunc);
let unpacked = Value::from(&tagged);
if let Value::NativeFunc(recovered) = unpacked {
assert_eq!(recovered.arity, 0);
} else {
panic!("expected NativeFunc variant after round-trip");
}
});
}
#[test]
fn native_func_type_metadata() {
use sandpit::Gc;
use crate::runtime::native_func::NativeFunc;
use crate::runtime::error::RuntimeError;
use crate::symbol_map::FN_SYM;
fn dummy_fn<'gc>(_args: &[Value<'gc>], _mu: &'gc Mutator) -> Result<Value<'gc>, RuntimeError> {
Ok(Value::Null)
}
let _: Arena<Root![()]> = Arena::new(|mu| {
let nf = NativeFunc { arity: 2, func: dummy_fn };
let gc_nf = Gc::new(mu, nf);
let v = Value::NativeFunc(gc_nf);
assert_eq!(v.type_str(), "NativeFunc");
assert_eq!(v.get_type_id(), FN_SYM);
assert!(v.is_truthy());
});
}
#[test]
fn native_func_equality_is_identity() {
use sandpit::Gc;
use crate::runtime::native_func::NativeFunc;
use crate::runtime::error::RuntimeError;
fn dummy_fn<'gc>(_args: &[Value<'gc>], _mu: &'gc Mutator) -> Result<Value<'gc>, RuntimeError> {
Ok(Value::Null)
}
let _: Arena<Root![()]> = Arena::new(|mu| {
let nf = NativeFunc { arity: 0, func: dummy_fn };
let gc_a = Gc::new(mu, nf);
let gc_b = Gc::new(mu, nf);
let val_a1 = Value::NativeFunc(gc_a.clone());
let val_a2 = Value::NativeFunc(gc_a);
let val_b = Value::NativeFunc(gc_b);
assert!(val_a1.is_equal_to(&val_a2));
assert!(!val_a1.is_equal_to(&val_b));
});
}
#[test]
fn native_func_direct_call() {
use sandpit::Gc;
use crate::runtime::native_func::NativeFunc;
use crate::runtime::error::RuntimeError;
fn add_ints<'gc>(args: &[Value<'gc>], _mu: &'gc Mutator) -> Result<Value<'gc>, RuntimeError> {
if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) {
Ok(Value::Int(a + b))
} else {
panic!("expected two ints");
}
}
let _: Arena<Root![()]> = Arena::new(|mu| {
let nf = NativeFunc { arity: 2, func: add_ints };
let gc_nf = Gc::new(mu, nf);
let args = vec![Value::Int(10), Value::Int(32)];
let result = (gc_nf.func)(&args, mu).unwrap();
if let Value::Int(42) = result {
assert!(true);
} else {
panic!("expected Int(42)");
}
});
}
}