1use crate::{Error, value::Value};
2use cfg_elif::expr::feature;
3use core::fmt::{self, Display, Formatter};
4
5pub type Tag = u16;
7
8const DUMMY_INDEX: u64 = 0;
9
10pub const NEVER: Cons = Cons::new(DUMMY_INDEX).set_tag(Tag::MAX);
18
19const TAG_SIZE: usize = Tag::BITS as usize;
20const TAG_MASK: u64 = Tag::MAX as u64;
21
22#[derive(Clone, Copy, Debug)]
24pub struct Cons(u64);
25
26impl Cons {
27 #[inline]
29 pub const fn new(index: u64) -> Self {
30 Self::r#box(index << TAG_SIZE)
31 }
32
33 #[inline]
35 pub const fn index(self) -> usize {
36 (self.unbox() >> TAG_SIZE) as _
37 }
38
39 #[inline]
41 pub const fn tag(self) -> Tag {
42 (self.unbox() & TAG_MASK) as _
43 }
44
45 #[inline]
47 pub const fn set_tag(self, tag: Tag) -> Self {
48 Self::r#box(self.unbox() & !TAG_MASK | (tag as u64 & TAG_MASK))
49 }
50
51 #[inline]
52 const fn r#box(value: u64) -> Self {
53 Self(feature!(if ("float") {
54 nonbox::f64::u64::box_unsigned(value)
55 } else {
56 value << 1
57 }))
58 }
59
60 #[inline]
61 const fn unbox(self) -> u64 {
62 feature!(if ("float") {
63 if let Some(index) = nonbox::f64::u64::unbox_unsigned(self.0) {
64 index
65 } else {
66 DUMMY_INDEX
67 }
68 } else {
69 self.0 >> 1
70 })
71 }
72
73 #[inline]
74 pub(crate) const fn raw_eq(self, cons: Self) -> bool {
75 self.0 == cons.0
76 }
77
78 #[inline]
79 pub(crate) const fn from_raw(raw: u64) -> Self {
80 Self(raw)
81 }
82
83 #[inline]
84 pub(crate) const fn to_raw(self) -> u64 {
85 self.0
86 }
87}
88
89impl PartialEq for Cons {
90 #[inline]
91 fn eq(&self, other: &Self) -> bool {
92 self.index() == other.index()
93 }
94}
95
96impl Eq for Cons {}
97
98impl TryFrom<Value> for Cons {
99 type Error = Error;
100
101 #[inline]
102 fn try_from(value: Value) -> Result<Self, Self::Error> {
103 value.to_cons().ok_or(Error::ConsExpected)
104 }
105}
106
107impl Display for Cons {
108 fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
109 if self.raw_eq(NEVER) {
110 return write!(formatter, "!");
111 }
112
113 write!(formatter, "c{:x}", self.index())?;
114
115 if self.tag() > 0 {
116 write!(formatter, ":{}", self.tag())?;
117 }
118
119 Ok(())
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn tag() {
129 let cons = Cons::new(42);
130
131 assert_eq!(cons.index(), 42);
132 assert_eq!(cons.tag(), 0);
133
134 let cons = cons.set_tag(1);
135
136 assert_eq!(cons.index(), 42);
137 assert_eq!(cons.tag(), 1);
138
139 let cons = cons.set_tag(3);
140
141 assert_eq!(cons.index(), 42);
142 assert_eq!(cons.tag(), 3);
143 }
144
145 #[test]
146 fn reset_tag() {
147 assert_eq!(Cons::new(42).set_tag(2).set_tag(1).tag(), 1);
148 }
149
150 #[test]
151 fn set_too_large_tag() {
152 let cons = Cons::new(0).set_tag(Tag::MAX);
153
154 assert_eq!(cons.index(), 0);
155 assert_eq!(cons.tag(), TAG_MASK as Tag);
156 }
157}