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