1use crate::{Error, value::Value, value_inner};
2use core::fmt::{self, Display, Formatter};
3
4pub type Tag = u16;
6
7pub(crate) const NEVER: Cons = Cons::new(1); const TAG_SIZE: usize = Tag::BITS as usize;
15const TAG_MASK: u64 = Tag::MAX as u64;
16
17#[derive(Clone, Copy, Debug)]
19pub struct Cons(u64);
20
21impl Cons {
22 #[inline]
24 pub const fn new(index: u64) -> Self {
25 Self::r#box(index << TAG_SIZE)
26 }
27
28 #[inline]
30 pub const fn index(self) -> usize {
31 (self.unbox() >> TAG_SIZE) as _
32 }
33
34 #[inline]
36 pub const fn tag(self) -> Tag {
37 (self.unbox() & TAG_MASK) as _
38 }
39
40 #[inline]
42 pub const fn set_tag(self, tag: Tag) -> Self {
43 Self::r#box(self.unbox() & !TAG_MASK | (tag as u64 & TAG_MASK))
44 }
45
46 #[inline]
47 const fn r#box(value: u64) -> Self {
48 Self(value_inner::box_cons(value))
49 }
50
51 #[inline]
52 const fn unbox(self) -> u64 {
53 value_inner::unbox_cons(self.0)
54 }
55
56 #[inline]
57 pub(crate) const fn from_raw(raw: u64) -> Self {
58 Self(raw)
59 }
60
61 #[inline]
62 pub(crate) const fn to_raw(self) -> u64 {
63 self.0
64 }
65}
66
67impl PartialEq for Cons {
68 #[inline]
69 fn eq(&self, other: &Self) -> bool {
70 self.index() == other.index()
71 }
72}
73
74impl Eq for Cons {}
75
76impl TryFrom<Value> for Cons {
77 type Error = Error;
78
79 #[inline]
80 fn try_from(value: Value) -> Result<Self, Self::Error> {
81 value.to_cons().ok_or(Error::ConsExpected)
82 }
83}
84
85impl Display for Cons {
86 fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
87 if *self == NEVER {
88 return write!(formatter, "!");
89 }
90
91 write!(formatter, "c{:x}", self.index())?;
92
93 if self.tag() > 0 {
94 write!(formatter, ":{}", self.tag())?;
95 }
96
97 Ok(())
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn tag() {
107 let cons = Cons::new(42);
108
109 assert_eq!(cons.index(), 42);
110 assert_eq!(cons.tag(), 0);
111
112 let cons = cons.set_tag(1);
113
114 assert_eq!(cons.index(), 42);
115 assert_eq!(cons.tag(), 1);
116
117 let cons = cons.set_tag(3);
118
119 assert_eq!(cons.index(), 42);
120 assert_eq!(cons.tag(), 3);
121 }
122
123 #[test]
124 fn reset_tag() {
125 assert_eq!(Cons::new(42).set_tag(2).set_tag(1).tag(), 1);
126 }
127
128 #[test]
129 fn set_too_large_tag() {
130 let cons = Cons::new(0).set_tag(Tag::MAX);
131
132 assert_eq!(cons.index(), 0);
133 assert_eq!(cons.tag(), TAG_MASK as Tag);
134 }
135}