1use crate::{
2 cons::{Cons, Tag},
3 number::Number,
4};
5use cfg_elif::expr::feature;
6use core::fmt::{self, Display, Formatter};
7
8#[derive(Copy, Clone, Debug)]
10pub struct Value(u64);
11
12#[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 #[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 #[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 #[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 #[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 #[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 #[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 #[inline]
79 pub const fn is_number(&self) -> bool {
80 !self.is_cons()
81 }
82
83 #[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 #[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}