stak_vm/
value.rs

1use crate::{
2    cons::{Cons, Tag},
3    number::Number,
4};
5use cfg_elif::expr::feature;
6use core::fmt::{self, Display, Formatter};
7
8/// A value.
9#[derive(Copy, Clone, Debug)]
10pub struct Value(u64);
11
12/// A typed value.
13#[derive(Copy, Clone, Debug, PartialEq)]
14#[cfg_attr(not(feature = "float"), derive(Eq))]
15pub enum TypedValue {
16    Cons(Cons),
17    Number(Number),
18}
19
20impl Value {
21    /// Converts a value to a cons.
22    #[inline]
23    pub const fn to_cons(self) -> Option<Cons> {
24        if let TypedValue::Cons(cons) = self.to_typed() {
25            Some(cons)
26        } else {
27            None
28        }
29    }
30
31    /// Converts a value to a number.
32    #[inline]
33    pub const fn to_number(self) -> Option<Number> {
34        if let TypedValue::Number(number) = self.to_typed() {
35            Some(number)
36        } else {
37            None
38        }
39    }
40
41    /// Converts a value to a typed value.
42    #[inline]
43    pub const fn to_typed(self) -> TypedValue {
44        if self.is_cons() {
45            TypedValue::Cons(self.assume_cons())
46        } else {
47            TypedValue::Number(self.assume_number())
48        }
49    }
50
51    /// Converts a value to a cons assuming its type.
52    #[inline]
53    pub const fn assume_cons(self) -> Cons {
54        debug_assert!(self.is_cons());
55
56        Cons::from_raw(self.0)
57    }
58
59    /// Converts a value to a number assuming its type.
60    #[inline]
61    pub const fn assume_number(self) -> Number {
62        debug_assert!(self.is_number());
63
64        Number::from_raw(self.0)
65    }
66
67    /// Checks if it is a cons.
68    #[inline]
69    pub const fn is_cons(&self) -> bool {
70        feature!(if ("float") {
71            nonbox::f64::u64::unbox_unsigned(self.0).is_some()
72        } else {
73            self.0 & 1 == 0
74        })
75    }
76
77    /// Checks if it is a number.
78    #[inline]
79    pub const fn is_number(&self) -> bool {
80        !self.is_cons()
81    }
82
83    /// Returns a tag.
84    #[inline]
85    pub const fn tag(self) -> Tag {
86        if let Some(cons) = self.to_cons() {
87            cons.tag()
88        } else {
89            0
90        }
91    }
92
93    /// Sets a tag.
94    #[inline]
95    pub const fn set_tag(self, tag: Tag) -> Self {
96        if let Some(cons) = self.to_cons() {
97            Self::from_cons(cons.set_tag(tag))
98        } else {
99            self
100        }
101    }
102
103    #[inline]
104    pub(crate) const fn raw_eq(self, value: Self) -> bool {
105        self.0 == value.0
106    }
107
108    const fn from_cons(cons: Cons) -> Self {
109        Self(cons.to_raw())
110    }
111}
112
113impl Default for Value {
114    #[inline]
115    fn default() -> Self {
116        Number::default().into()
117    }
118}
119
120impl PartialEq for Value {
121    #[inline]
122    fn eq(&self, other: &Self) -> bool {
123        self.is_cons() && self.to_cons() == other.to_cons()
124            || self.is_number() && self.to_number() == other.to_number()
125    }
126}
127
128impl Eq for Value {}
129
130impl From<Cons> for Value {
131    #[inline]
132    fn from(cons: Cons) -> Self {
133        Self(cons.to_raw())
134    }
135}
136
137impl From<Number> for Value {
138    #[inline]
139    fn from(number: Number) -> Self {
140        Self(number.to_raw())
141    }
142}
143
144impl Display for Value {
145    fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
146        match self.to_typed() {
147            TypedValue::Cons(cons) => write!(formatter, "{cons}"),
148            TypedValue::Number(number) => write!(formatter, "{number}"),
149        }
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156    use crate::{Type, cons::NEVER};
157
158    #[test]
159    fn convert_cons() {
160        let cons = Cons::new(42);
161
162        assert_eq!(Value::from(cons).to_cons().unwrap(), cons);
163    }
164
165    #[test]
166    fn convert_tagged_cons() {
167        const TAG: Tag = 0b111;
168
169        let cons = Cons::new(42).set_tag(TAG);
170        let converted = Value::from(cons).to_cons().unwrap();
171
172        assert_eq!(converted, cons);
173        assert_eq!(converted.tag(), TAG);
174    }
175
176    #[test]
177    fn convert_number() {
178        let number = Number::from_i64(42);
179
180        assert_eq!(Value::from(number).to_number().unwrap(), number);
181    }
182
183    #[test]
184    fn convert_moved() {
185        assert_eq!(Value::from(NEVER).to_cons().unwrap(), NEVER);
186    }
187
188    #[test]
189    fn get_tag_from_number() {
190        let tag = Value::from(Number::from_i64(42)).tag();
191
192        assert_eq!(tag, Default::default());
193        assert_eq!(tag, Type::default() as Tag);
194    }
195
196    #[test]
197    fn set_tag_to_number() {
198        let value = Value::from(Number::from_i64(42)).set_tag(0b111);
199
200        assert_eq!(value.tag(), Default::default());
201        assert_eq!(value.to_number(), Some(Number::from_i64(42)));
202    }
203}