use core::fmt;
use intern_lang::Symbol;
#[derive(Clone, Copy)]
pub struct Value(u64);
const QNAN: u64 = 0x7ffc_0000_0000_0000;
const CANON_NAN: u64 = 0x7ff8_0000_0000_0000;
const TAG_SHIFT: u32 = 32;
const TAG_MASK: u64 = 0x7;
const TAG_NIL: u64 = 1;
const TAG_FALSE: u64 = 2;
const TAG_TRUE: u64 = 3;
const TAG_INT: u64 = 4;
const TAG_SYM: u64 = 5;
const fn boxed(tag: u64) -> u64 {
QNAN | (tag << TAG_SHIFT)
}
const NIL_BITS: u64 = boxed(TAG_NIL);
const FALSE_BITS: u64 = boxed(TAG_FALSE);
const TRUE_BITS: u64 = boxed(TAG_TRUE);
impl Value {
#[inline]
#[must_use]
pub const fn nil() -> Self {
Self(NIL_BITS)
}
#[inline]
#[must_use]
pub const fn bool(b: bool) -> Self {
Self(if b { TRUE_BITS } else { FALSE_BITS })
}
#[inline]
#[must_use]
pub const fn int(n: i32) -> Self {
Self(boxed(TAG_INT) | (n as u32 as u64))
}
#[inline]
#[must_use]
pub fn float(f: f64) -> Self {
if f.is_nan() {
Self(CANON_NAN)
} else {
Self(f.to_bits())
}
}
#[inline]
#[must_use]
pub fn sym(s: Symbol) -> Self {
Self(boxed(TAG_SYM) | (s.as_u32() as u64))
}
#[inline]
#[must_use]
pub fn is_nil(self) -> bool {
self.0 == NIL_BITS
}
#[inline]
#[must_use]
pub fn is_bool(self) -> bool {
self.0 == TRUE_BITS || self.0 == FALSE_BITS
}
#[inline]
#[must_use]
pub fn is_int(self) -> bool {
self.is_boxed() && self.tag() == TAG_INT
}
#[inline]
#[must_use]
pub fn is_float(self) -> bool {
!self.is_boxed()
}
#[inline]
#[must_use]
pub fn is_sym(self) -> bool {
self.is_boxed() && self.tag() == TAG_SYM
}
#[inline]
#[must_use]
pub fn as_bool(self) -> Option<bool> {
match self.0 {
TRUE_BITS => Some(true),
FALSE_BITS => Some(false),
_ => None,
}
}
#[inline]
#[must_use]
pub fn as_int(self) -> Option<i32> {
if self.is_int() {
Some(self.0 as u32 as i32)
} else {
None
}
}
#[inline]
#[must_use]
pub fn as_float(self) -> Option<f64> {
if self.is_float() {
Some(f64::from_bits(self.0))
} else {
None
}
}
#[inline]
#[must_use]
pub fn as_sym(self) -> Option<Symbol> {
if self.is_sym() {
Symbol::from_u32(self.0 as u32)
} else {
None
}
}
#[inline]
#[must_use]
pub const fn bits(self) -> u64 {
self.0
}
#[inline]
#[must_use]
pub fn unpack(self) -> Unpacked {
if self.is_float() {
return Unpacked::Float(f64::from_bits(self.0));
}
if self.0 == NIL_BITS {
Unpacked::Nil
} else if self.0 == FALSE_BITS {
Unpacked::Bool(false)
} else if self.0 == TRUE_BITS {
Unpacked::Bool(true)
} else if self.tag() == TAG_INT {
Unpacked::Int(self.0 as u32 as i32)
} else {
match Symbol::from_u32(self.0 as u32) {
Some(s) => Unpacked::Sym(s),
None => Unpacked::Nil,
}
}
}
#[inline]
const fn is_boxed(self) -> bool {
(self.0 & QNAN) == QNAN
}
#[inline]
const fn tag(self) -> u64 {
(self.0 >> TAG_SHIFT) & TAG_MASK
}
}
impl Default for Value {
#[inline]
fn default() -> Self {
Self::nil()
}
}
impl PartialEq for Value {
#[inline]
fn eq(&self, other: &Self) -> bool {
if self.is_float() && other.is_float() {
f64::from_bits(self.0) == f64::from_bits(other.0)
} else {
self.0 == other.0
}
}
}
impl fmt::Debug for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Value").field(&self.unpack()).finish()
}
}
impl From<bool> for Value {
#[inline]
fn from(b: bool) -> Self {
Self::bool(b)
}
}
impl From<i32> for Value {
#[inline]
fn from(n: i32) -> Self {
Self::int(n)
}
}
impl From<f64> for Value {
#[inline]
fn from(f: f64) -> Self {
Self::float(f)
}
}
impl From<Symbol> for Value {
#[inline]
fn from(s: Symbol) -> Self {
Self::sym(s)
}
}
impl From<Unpacked> for Value {
#[inline]
fn from(u: Unpacked) -> Self {
match u {
Unpacked::Nil => Self::nil(),
Unpacked::Bool(b) => Self::bool(b),
Unpacked::Int(n) => Self::int(n),
Unpacked::Float(f) => Self::float(f),
Unpacked::Sym(s) => Self::sym(s),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Unpacked {
Nil,
Bool(bool),
Int(i32),
Float(f64),
Sym(Symbol),
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use super::*;
#[test]
fn test_value_size_is_one_word() {
assert_eq!(core::mem::size_of::<Value>(), 8);
assert_eq!(core::mem::align_of::<Value>(), 8);
}
#[test]
fn test_nil_roundtrips_and_is_default() {
let v = Value::nil();
assert!(v.is_nil());
assert_eq!(v.unpack(), Unpacked::Nil);
assert_eq!(v, Value::default());
assert!(!v.is_bool());
assert!(!v.is_int());
assert!(!v.is_float());
assert!(!v.is_sym());
}
#[test]
fn test_bool_roundtrips_both_values() {
for b in [true, false] {
let v = Value::bool(b);
assert!(v.is_bool());
assert_eq!(v.as_bool(), Some(b));
assert_eq!(v.unpack(), Unpacked::Bool(b));
}
assert_ne!(Value::bool(true), Value::bool(false));
}
#[test]
fn test_int_roundtrips_including_extremes() {
for n in [0, 1, -1, i32::MIN, i32::MAX, 123_456, -987_654] {
let v = Value::int(n);
assert!(v.is_int());
assert_eq!(v.as_int(), Some(n));
assert_eq!(v.unpack(), Unpacked::Int(n));
}
}
#[test]
fn test_float_roundtrips_including_infinities() {
for f in [
0.0,
-0.0,
1.5,
-2.5,
f64::MIN,
f64::MAX,
f64::INFINITY,
f64::NEG_INFINITY,
] {
let v = Value::float(f);
assert!(v.is_float());
assert_eq!(v.as_float(), Some(f));
}
}
#[test]
fn test_float_nan_is_canonical_and_unequal_to_itself() {
let v = Value::float(f64::NAN);
assert!(v.is_float());
assert!(v.as_float().unwrap().is_nan());
assert_ne!(v, v);
let noisy = Value::float(f64::from_bits(0x7ff8_0000_dead_beef));
assert_eq!(v.bits(), noisy.bits());
}
#[test]
fn test_float_signed_zero_compares_equal() {
assert_eq!(Value::float(0.0), Value::float(-0.0));
assert_ne!(Value::float(0.0).bits(), Value::float(-0.0).bits());
}
#[test]
fn test_sym_roundtrips() {
let s = Symbol::from_u32(7).unwrap();
let v = Value::sym(s);
assert!(v.is_sym());
assert_eq!(v.as_sym(), Some(s));
assert_eq!(v.unpack(), Unpacked::Sym(s));
}
#[test]
fn test_wrong_accessor_returns_none() {
let v = Value::int(1);
assert_eq!(v.as_bool(), None);
assert_eq!(v.as_float(), None);
assert_eq!(v.as_sym(), None);
assert_eq!(Value::float(1.0).as_int(), None);
}
#[test]
fn test_distinct_kinds_never_compare_equal() {
assert_ne!(Value::int(1), Value::float(1.0));
assert_ne!(Value::nil(), Value::bool(false));
assert_ne!(Value::int(0), Value::nil());
}
#[test]
fn test_from_impls_match_constructors() {
assert_eq!(Value::from(true), Value::bool(true));
assert_eq!(Value::from(9_i32), Value::int(9));
assert_eq!(Value::from(1.25_f64), Value::float(1.25));
assert_eq!(Value::from(Unpacked::Nil), Value::nil());
}
#[test]
fn test_unpack_from_roundtrips() {
let s = Symbol::from_u32(3).unwrap();
for v in [
Value::nil(),
Value::bool(true),
Value::bool(false),
Value::int(-42),
Value::float(12.5),
Value::sym(s),
] {
assert_eq!(Value::from(v.unpack()), v);
}
}
#[test]
fn test_debug_names_the_kind() {
extern crate alloc;
use alloc::format;
assert_eq!(format!("{:?}", Value::int(5)), "Value(Int(5))");
assert_eq!(format!("{:?}", Value::nil()), "Value(Nil)");
}
}