use crate::slot::ValueSlot;
use crate::value_word::{NanTag, ValueWord};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum ScalarKind {
I8 = 0,
U8 = 1,
I16 = 2,
U16 = 3,
I32 = 4,
U32 = 5,
I64 = 6,
U64 = 7,
I128 = 8,
U128 = 9,
F32 = 10,
F64 = 11,
Bool = 12,
None = 13,
Unit = 14,
}
impl ScalarKind {
#[inline]
pub fn is_integer(self) -> bool {
matches!(
self,
ScalarKind::I8
| ScalarKind::U8
| ScalarKind::I16
| ScalarKind::U16
| ScalarKind::I32
| ScalarKind::U32
| ScalarKind::I64
| ScalarKind::U64
| ScalarKind::I128
| ScalarKind::U128
)
}
#[inline]
pub fn is_float(self) -> bool {
matches!(self, ScalarKind::F32 | ScalarKind::F64)
}
#[inline]
pub fn is_numeric(self) -> bool {
self.is_integer() || self.is_float()
}
#[inline]
pub fn is_unsigned_integer(self) -> bool {
matches!(
self,
ScalarKind::U8 | ScalarKind::U16 | ScalarKind::U32 | ScalarKind::U64 | ScalarKind::U128
)
}
#[inline]
pub fn is_signed_integer(self) -> bool {
matches!(
self,
ScalarKind::I8 | ScalarKind::I16 | ScalarKind::I32 | ScalarKind::I64 | ScalarKind::I128
)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(C)]
pub struct TypedScalar {
pub kind: ScalarKind,
pub payload_lo: u64,
pub payload_hi: u64,
}
impl TypedScalar {
#[inline]
pub fn i64(v: i64) -> Self {
Self {
kind: ScalarKind::I64,
payload_lo: v as u64,
payload_hi: 0,
}
}
#[inline]
pub fn f64(v: f64) -> Self {
Self {
kind: ScalarKind::F64,
payload_lo: v.to_bits(),
payload_hi: 0,
}
}
#[inline]
pub fn f64_from_bits(bits: u64) -> Self {
Self {
kind: ScalarKind::F64,
payload_lo: bits,
payload_hi: 0,
}
}
#[inline]
pub fn bool(v: bool) -> Self {
Self {
kind: ScalarKind::Bool,
payload_lo: v as u64,
payload_hi: 0,
}
}
#[inline]
pub fn none() -> Self {
Self {
kind: ScalarKind::None,
payload_lo: 0,
payload_hi: 0,
}
}
#[inline]
pub fn unit() -> Self {
Self {
kind: ScalarKind::Unit,
payload_lo: 0,
payload_hi: 0,
}
}
#[inline]
pub fn i8(v: i8) -> Self {
Self {
kind: ScalarKind::I8,
payload_lo: v as i64 as u64,
payload_hi: 0,
}
}
#[inline]
pub fn u8(v: u8) -> Self {
Self {
kind: ScalarKind::U8,
payload_lo: v as u64,
payload_hi: 0,
}
}
#[inline]
pub fn i16(v: i16) -> Self {
Self {
kind: ScalarKind::I16,
payload_lo: v as i64 as u64,
payload_hi: 0,
}
}
#[inline]
pub fn u16(v: u16) -> Self {
Self {
kind: ScalarKind::U16,
payload_lo: v as u64,
payload_hi: 0,
}
}
#[inline]
pub fn i32(v: i32) -> Self {
Self {
kind: ScalarKind::I32,
payload_lo: v as i64 as u64,
payload_hi: 0,
}
}
#[inline]
pub fn u32(v: u32) -> Self {
Self {
kind: ScalarKind::U32,
payload_lo: v as u64,
payload_hi: 0,
}
}
#[inline]
pub fn u64(v: u64) -> Self {
Self {
kind: ScalarKind::U64,
payload_lo: v,
payload_hi: 0,
}
}
#[inline]
pub fn f32(v: f32) -> Self {
Self {
kind: ScalarKind::F32,
payload_lo: f64::from(v).to_bits(),
payload_hi: 0,
}
}
#[inline]
pub fn as_i64(&self) -> Option<i64> {
if self.kind == ScalarKind::U64 {
i64::try_from(self.payload_lo).ok()
} else if self.kind.is_integer() {
Some(self.payload_lo as i64)
} else {
Option::None
}
}
#[inline]
pub fn as_u64(&self) -> Option<u64> {
if self.kind.is_unsigned_integer() {
Some(self.payload_lo)
} else if self.kind.is_signed_integer() {
Some(self.payload_lo)
} else {
Option::None
}
}
#[inline]
pub fn as_f64(&self) -> Option<f64> {
match self.kind {
ScalarKind::F64 => Some(f64::from_bits(self.payload_lo)),
ScalarKind::F32 => Some(f64::from_bits(self.payload_lo)),
_ => Option::None,
}
}
#[inline]
pub fn as_bool(&self) -> Option<bool> {
if self.kind == ScalarKind::Bool {
Some(self.payload_lo != 0)
} else {
Option::None
}
}
#[inline]
pub fn to_f64_lossy(&self) -> Option<f64> {
match self.kind {
ScalarKind::F64 | ScalarKind::F32 => Some(f64::from_bits(self.payload_lo)),
k if k.is_unsigned_integer() => Some(self.payload_lo as f64),
k if k.is_signed_integer() => Some(self.payload_lo as i64 as f64),
_ => Option::None,
}
}
}
impl ValueWord {
#[inline]
pub fn to_typed_scalar(&self) -> Option<TypedScalar> {
match self.tag() {
NanTag::F64 => {
let bits = self.raw_bits();
Some(TypedScalar {
kind: ScalarKind::F64,
payload_lo: bits,
payload_hi: 0,
})
}
NanTag::I48 => {
let i = unsafe { self.as_i64_unchecked() };
Some(TypedScalar::i64(i))
}
NanTag::Bool => {
let b = unsafe { self.as_bool_unchecked() };
Some(TypedScalar::bool(b))
}
NanTag::None => Some(TypedScalar::none()),
NanTag::Unit => Some(TypedScalar::unit()),
NanTag::Heap | NanTag::Function | NanTag::ModuleFunction | NanTag::Ref => Option::None,
}
}
#[inline]
pub fn from_typed_scalar(ts: TypedScalar) -> Self {
match ts.kind {
ScalarKind::I64 => ValueWord::from_i64(ts.payload_lo as i64),
ScalarKind::I8 | ScalarKind::I16 | ScalarKind::I32 => {
ValueWord::from_i64(ts.payload_lo as i64)
}
ScalarKind::U8 | ScalarKind::U16 | ScalarKind::U32 => {
ValueWord::from_i64(ts.payload_lo as i64)
}
ScalarKind::U64 => {
if ts.payload_lo <= i64::MAX as u64 {
ValueWord::from_i64(ts.payload_lo as i64)
} else {
ValueWord::from_native_u64(ts.payload_lo)
}
}
ScalarKind::I128 | ScalarKind::U128 => {
ValueWord::from_i64(ts.payload_lo as i64)
}
ScalarKind::F64 => ValueWord::from_f64(f64::from_bits(ts.payload_lo)),
ScalarKind::F32 => ValueWord::from_f64(f64::from_bits(ts.payload_lo)),
ScalarKind::Bool => ValueWord::from_bool(ts.payload_lo != 0),
ScalarKind::None => ValueWord::none(),
ScalarKind::Unit => ValueWord::unit(),
}
}
}
impl ValueSlot {
#[inline]
pub fn from_typed_scalar(ts: TypedScalar) -> (Self, bool) {
match ts.kind {
ScalarKind::I8 | ScalarKind::I16 | ScalarKind::I32 | ScalarKind::I64 => {
(ValueSlot::from_int(ts.payload_lo as i64), false)
}
ScalarKind::U8 | ScalarKind::U16 | ScalarKind::U32 => {
(ValueSlot::from_int(ts.payload_lo as i64), false)
}
ScalarKind::U64 => (ValueSlot::from_u64(ts.payload_lo), false),
ScalarKind::I128 | ScalarKind::U128 => {
(ValueSlot::from_int(ts.payload_lo as i64), false)
}
ScalarKind::F64 | ScalarKind::F32 => {
(ValueSlot::from_number(f64::from_bits(ts.payload_lo)), false)
}
ScalarKind::Bool => (ValueSlot::from_bool(ts.payload_lo != 0), false),
ScalarKind::None | ScalarKind::Unit => (ValueSlot::none(), false),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tags::{I48_MAX, I48_MIN};
#[test]
fn round_trip_i64() {
let vw = ValueWord::from_i64(42);
let ts = vw.to_typed_scalar().unwrap();
assert_eq!(ts.kind, ScalarKind::I64);
assert_eq!(ts.payload_lo, 42u64);
assert_eq!(ts.payload_hi, 0);
let vw2 = ValueWord::from_typed_scalar(ts);
assert_eq!(vw.raw_bits(), vw2.raw_bits());
}
#[test]
fn round_trip_negative_i64() {
let vw = ValueWord::from_i64(-99);
let ts = vw.to_typed_scalar().unwrap();
assert_eq!(ts.kind, ScalarKind::I64);
assert_eq!(ts.payload_lo as i64, -99);
let vw2 = ValueWord::from_typed_scalar(ts);
assert_eq!(vw.raw_bits(), vw2.raw_bits());
}
#[test]
fn round_trip_i48_max() {
let vw = ValueWord::from_i64(I48_MAX);
let ts = vw.to_typed_scalar().unwrap();
assert_eq!(ts.kind, ScalarKind::I64);
assert_eq!(ts.payload_lo as i64, I48_MAX);
let vw2 = ValueWord::from_typed_scalar(ts);
assert_eq!(vw.raw_bits(), vw2.raw_bits());
}
#[test]
fn round_trip_i48_min() {
let vw = ValueWord::from_i64(I48_MIN);
let ts = vw.to_typed_scalar().unwrap();
assert_eq!(ts.kind, ScalarKind::I64);
assert_eq!(ts.payload_lo as i64, I48_MIN);
let vw2 = ValueWord::from_typed_scalar(ts);
assert_eq!(vw.raw_bits(), vw2.raw_bits());
}
#[test]
fn round_trip_f64() {
let vw = ValueWord::from_f64(3.14);
let ts = vw.to_typed_scalar().unwrap();
assert_eq!(ts.kind, ScalarKind::F64);
assert_eq!(f64::from_bits(ts.payload_lo), 3.14);
let vw2 = ValueWord::from_typed_scalar(ts);
assert_eq!(vw.raw_bits(), vw2.raw_bits());
}
#[test]
fn round_trip_f64_nan() {
let vw = ValueWord::from_f64(f64::NAN);
let ts = vw.to_typed_scalar().unwrap();
assert_eq!(ts.kind, ScalarKind::F64);
assert!(f64::from_bits(ts.payload_lo).is_nan());
let vw2 = ValueWord::from_typed_scalar(ts);
assert_eq!(vw.raw_bits(), vw2.raw_bits());
}
#[test]
fn round_trip_f64_infinity() {
let vw = ValueWord::from_f64(f64::INFINITY);
let ts = vw.to_typed_scalar().unwrap();
assert_eq!(ts.kind, ScalarKind::F64);
assert_eq!(f64::from_bits(ts.payload_lo), f64::INFINITY);
let vw2 = ValueWord::from_typed_scalar(ts);
assert_eq!(vw.raw_bits(), vw2.raw_bits());
}
#[test]
fn round_trip_f64_neg_zero() {
let vw = ValueWord::from_f64(-0.0);
let ts = vw.to_typed_scalar().unwrap();
assert_eq!(ts.kind, ScalarKind::F64);
assert_eq!(ts.payload_lo, (-0.0f64).to_bits());
let vw2 = ValueWord::from_typed_scalar(ts);
assert_eq!(vw.raw_bits(), vw2.raw_bits());
}
#[test]
fn round_trip_bool_true() {
let vw = ValueWord::from_bool(true);
let ts = vw.to_typed_scalar().unwrap();
assert_eq!(ts.kind, ScalarKind::Bool);
assert_eq!(ts.payload_lo, 1);
let vw2 = ValueWord::from_typed_scalar(ts);
assert_eq!(vw.raw_bits(), vw2.raw_bits());
}
#[test]
fn round_trip_bool_false() {
let vw = ValueWord::from_bool(false);
let ts = vw.to_typed_scalar().unwrap();
assert_eq!(ts.kind, ScalarKind::Bool);
assert_eq!(ts.payload_lo, 0);
let vw2 = ValueWord::from_typed_scalar(ts);
assert_eq!(vw.raw_bits(), vw2.raw_bits());
}
#[test]
fn round_trip_none() {
let vw = ValueWord::none();
let ts = vw.to_typed_scalar().unwrap();
assert_eq!(ts.kind, ScalarKind::None);
let vw2 = ValueWord::from_typed_scalar(ts);
assert_eq!(vw.raw_bits(), vw2.raw_bits());
}
#[test]
fn round_trip_unit() {
let vw = ValueWord::unit();
let ts = vw.to_typed_scalar().unwrap();
assert_eq!(ts.kind, ScalarKind::Unit);
let vw2 = ValueWord::from_typed_scalar(ts);
assert_eq!(vw.raw_bits(), vw2.raw_bits());
}
#[test]
fn heap_value_returns_none() {
let vw = ValueWord::from_string(std::sync::Arc::new("hello".to_string()));
assert!(vw.to_typed_scalar().is_none());
}
#[test]
fn typed_scalar_convenience_constructors() {
assert_eq!(TypedScalar::i64(42).kind, ScalarKind::I64);
assert_eq!(TypedScalar::i64(42).payload_lo, 42);
assert_eq!(TypedScalar::f64(1.5).kind, ScalarKind::F64);
assert_eq!(TypedScalar::bool(true).payload_lo, 1);
assert_eq!(TypedScalar::none().kind, ScalarKind::None);
assert_eq!(TypedScalar::unit().kind, ScalarKind::Unit);
}
#[test]
fn scalar_kind_classification() {
assert!(ScalarKind::I64.is_integer());
assert!(ScalarKind::U32.is_integer());
assert!(!ScalarKind::F64.is_integer());
assert!(!ScalarKind::Bool.is_integer());
assert!(ScalarKind::F64.is_float());
assert!(ScalarKind::F32.is_float());
assert!(!ScalarKind::I64.is_float());
assert!(ScalarKind::I64.is_numeric());
assert!(ScalarKind::F64.is_numeric());
assert!(!ScalarKind::Bool.is_numeric());
assert!(!ScalarKind::None.is_numeric());
}
#[test]
fn value_slot_from_typed_scalar() {
let (slot, is_heap) = ValueSlot::from_typed_scalar(TypedScalar::i64(-42));
assert!(!is_heap);
assert_eq!(slot.as_i64(), -42);
let (slot, is_heap) = ValueSlot::from_typed_scalar(TypedScalar::f64(3.14));
assert!(!is_heap);
assert_eq!(slot.as_f64(), 3.14);
let (slot, is_heap) = ValueSlot::from_typed_scalar(TypedScalar::bool(true));
assert!(!is_heap);
assert!(slot.as_bool());
let (slot, is_heap) = ValueSlot::from_typed_scalar(TypedScalar::none());
assert!(!is_heap);
assert_eq!(slot.raw(), 0);
}
#[test]
fn to_f64_lossy_works() {
assert_eq!(TypedScalar::f64(3.14).to_f64_lossy(), Some(3.14));
assert_eq!(TypedScalar::i64(42).to_f64_lossy(), Some(42.0));
assert_eq!(TypedScalar::bool(true).to_f64_lossy(), Option::None);
assert_eq!(TypedScalar::none().to_f64_lossy(), Option::None);
}
#[test]
fn typed_scalar_extraction_methods() {
assert_eq!(TypedScalar::i64(42).as_i64(), Some(42));
assert_eq!(TypedScalar::f64(3.14).as_i64(), Option::None);
assert_eq!(TypedScalar::f64(3.14).as_f64(), Some(3.14));
assert_eq!(TypedScalar::i64(42).as_f64(), Option::None);
assert_eq!(TypedScalar::bool(true).as_bool(), Some(true));
assert_eq!(TypedScalar::i64(1).as_bool(), Option::None);
}
}