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 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}