stak_vm/
cons.rs

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