use std::{
mem,
f64,
fmt::{Formatter, Debug, Error},
};
use crate::common::data::Data;
pub struct Tagged(u64);
const QNAN: u64 = 0x7ffe_0000_0000_0000;
const P_FLAG: u64 = 0x8000_0000_0000_0000;
const P_MASK: u64 = 0x0000_FFFF_FFFF_FFFF;
const S_FLAG: u64 = 0x0000_0000_0000_0000; const U_FLAG: u64 = 0x0000_0000_0000_0001; const F_FLAG: u64 = 0x0000_0000_0000_0002; const T_FLAG: u64 = 0x0000_0000_0000_0004;
impl Tagged {
pub fn new(data: Data) -> Tagged {
match data {
Data::Real(f) => Tagged(f.to_bits()),
Data::Unit => Tagged(QNAN | U_FLAG),
Data::Boolean(false) => Tagged(QNAN | F_FLAG),
Data::Boolean(true) => Tagged(QNAN | T_FLAG),
Data::Frame => Tagged(QNAN | S_FLAG),
other => Tagged(P_FLAG | QNAN | (P_MASK & (Box::into_raw(Box::new(other))) as u64)),
}
}
pub fn frame() -> Tagged {
Tagged::new(Data::Frame)
}
unsafe fn extract(&self) -> Result<Data, Box<Data>> {
let Tagged(bits) = self;
return match bits {
n if (n & QNAN) != QNAN => Ok(Data::Real(f64::from_bits(*n))),
u if u == &(QNAN | U_FLAG) => Ok(Data::Unit),
f if f == &(QNAN | F_FLAG) => Ok(Data::Boolean(false)),
t if t == &(QNAN | T_FLAG) => Ok(Data::Boolean(true)),
s if s == &(QNAN | S_FLAG) => Ok(Data::Frame),
p if (p & P_FLAG) == P_FLAG => Err({
Box::from_raw((bits & P_MASK) as *mut Data)
}),
_ => unreachable!("Corrupted tagged data"),
}
}
pub fn data(self) -> Data {
let d = unsafe {
match self.extract() {
Ok(data) => data,
Err(boxed) => {
*boxed
}
}
};
mem::drop(self.0);
mem::forget(self);
return d;
}
pub fn copy(&self) -> Data {
unsafe {
match self.extract() {
Ok(data) => data.to_owned(),
Err(boxed) => {
let copy = boxed.clone();
Box::leak(boxed);
*copy
},
}
}
}
}
impl Drop for Tagged {
fn drop(&mut self) {
unsafe { mem::drop(self.extract()) };
}
}
impl Debug for Tagged {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
write!(f, "Tagged({:?})", self.copy())
}
}
impl From<Tagged> for u64 {
fn from(tagged: Tagged) -> Self { tagged.0 }
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn reals_eq() {
let positive = 478_329.0;
let negative = -231.0;
let nan = f64::NAN;
let neg_inf = f64::NEG_INFINITY;
for n in &[positive, negative, nan, neg_inf] {
let data = Data::Real(*n);
let wrapped = Tagged::new(data);
match wrapped.data() {
Data::Real(f) if f.is_nan() => assert!(n.is_nan()),
Data::Real(f) => assert_eq!(*n, f),
_ => panic!("Didn't unwrap to a real"),
}
}
}
#[test]
fn bool_and_back() {
assert_eq!(Data::Boolean(true), Tagged::new(Data::Boolean(true) ).data());
assert_eq!(Data::Boolean(false), Tagged::new(Data::Boolean(false)).data());
}
#[test]
fn unit() {
assert_eq!(Data::Unit, Tagged::new(Data::Unit).data());
}
#[test]
fn size() {
let data_size = mem::size_of::<Data>();
let tag_size = mem::size_of::<Tagged>();
assert_eq!(tag_size, mem::size_of::<f64>());
assert!(tag_size < data_size);
}
#[test]
fn string_pointer() {
let s = "I just lost the game".to_string();
let three = "Elongated Muskrat".to_string();
let x = "It's kind of a dead giveaway, isn't it?".to_string();
for item in &[s, three, x] {
let data = Data::String(item.clone());
let wrapped = Tagged::new(data);
match wrapped.data() {
Data::String(s) => { assert_eq!(item, &s) },
_ => {
panic!("Didn't unwrap to a string");
},
}
}
}
#[test]
fn other_tests_eq() {
let tests = vec![
Data::Real(f64::consts::PI),
Data::Real(-2.12),
Data::Real(2.5E10),
Data::Real(2.5e10),
Data::Real(2.5E-10),
Data::Real(0.5),
Data::Real(f64::MAX),
Data::Real(f64::MIN),
Data::Real(f64::INFINITY),
Data::Real(f64::NEG_INFINITY),
Data::Real(f64::NAN),
Data::Boolean(true),
Data::Boolean(false),
Data::Unit,
Data::String("Hello, World!".to_string()),
Data::String("".to_string()),
Data::String("Whoop 😋".to_string()),
];
for test in tests {
let tagged = Tagged::new(test.clone());
let untagged = tagged.data();
if let Data::Real(f) = untagged {
if let Data::Real(n) = test {
if n.is_nan() {
assert!(f.is_nan())
} else {
assert_eq!(test, Data::Real(n));
}
}
} else {
assert_eq!(test, untagged);
}
}
}
#[test]
fn no_leak_round() {
let location = "This is a string".to_string();
let tagged = Tagged::new(Data::String(location.clone()));
let pointer = tagged.0 & P_MASK;
let untagged = tagged.data();
let data = unsafe { Box::from_raw(pointer as *mut Data) };
mem::forget(data);
mem::drop(untagged);
}
#[test]
fn no_leak_tagged() {
let location = "This is a string".to_string();
let tagged = Tagged::new(Data::String(location.clone()));
let pointer = tagged.0 & P_MASK;
let data = unsafe { Box::from_raw(pointer as *mut Data) };
mem::forget(data);
mem::drop(tagged);
}
}