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    const fn from_cons(cons: Cons) -> Self {
104        Self(cons.to_raw())
105    }
106}
107
108impl Default for Value {
109    #[inline]
110    fn default() -> Self {
111        Number::default().into()
112    }
113}
114
115impl PartialEq for Value {
116    #[inline]
117    fn eq(&self, other: &Self) -> bool {
118        self.is_cons() && self.to_cons() == other.to_cons()
119            || self.is_number() && self.to_number() == other.to_number()
120    }
121}
122
123impl Eq for Value {}
124
125impl From<Cons> for Value {
126    #[inline]
127    fn from(cons: Cons) -> Self {
128        Self(cons.to_raw())
129    }
130}
131
132impl From<Number> for Value {
133    #[inline]
134    fn from(number: Number) -> Self {
135        Self(number.to_raw())
136    }
137}
138
139impl Display for Value {
140    fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
141        match self.to_typed() {
142            TypedValue::Cons(cons) => write!(formatter, "{cons}"),
143            TypedValue::Number(number) => write!(formatter, "{number}"),
144        }
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151    use crate::{Type, cons::NEVER};
152
153    #[test]
154    fn convert_cons() {
155        let cons = Cons::new(42);
156
157        assert_eq!(Value::from(cons).to_cons().unwrap(), cons);
158    }
159
160    #[test]
161    fn convert_tagged_cons() {
162        const TAG: Tag = 0b111;
163
164        let cons = Cons::new(42).set_tag(TAG);
165        let converted = Value::from(cons).to_cons().unwrap();
166
167        assert_eq!(converted, cons);
168        assert_eq!(converted.tag(), TAG);
169    }
170
171    #[test]
172    fn convert_number() {
173        let number = Number::from_i64(42);
174
175        assert_eq!(Value::from(number).to_number().unwrap(), number);
176    }
177
178    #[test]
179    fn convert_moved() {
180        assert_eq!(Value::from(NEVER).to_cons().unwrap(), NEVER);
181    }
182
183    #[test]
184    fn get_tag_from_number() {
185        let tag = Value::from(Number::from_i64(42)).tag();
186
187        assert_eq!(tag, Default::default());
188        assert_eq!(tag, Type::default() as Tag);
189    }
190
191    #[test]
192    fn set_tag_to_number() {
193        let value = Value::from(Number::from_i64(42)).set_tag(0b111);
194
195        assert_eq!(value.tag(), Default::default());
196        assert_eq!(value.to_number(), Some(Number::from_i64(42)));
197    }
198}