stak_vm/
cons.rs

1use crate::{Error, value::Value};
2use cfg_elif::expr::feature;
3use core::fmt::{self, Display, Formatter};
4
5/// A tag.
6pub type Tag = u16;
7
8const DUMMY_INDEX: u64 = 0;
9
10/// An unreachable cons. In other words, it is a "null" pointer but not `null`
11/// in Scheme.
12///
13/// This value means:
14///
15/// - In `car`, its cons is moved already on garbage collection.
16/// - In `cdr`, nothing.
17pub 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/// A cons.
23#[derive(Clone, Copy, Debug)]
24pub struct Cons(u64);
25
26impl Cons {
27    /// Creates a cons from a memory address on heap.
28    #[inline]
29    pub const fn new(index: u64) -> Self {
30        Self::r#box(index << TAG_SIZE)
31    }
32
33    /// Returns a memory address on heap.
34    #[inline]
35    pub const fn index(self) -> usize {
36        (self.unbox() >> TAG_SIZE) as _
37    }
38
39    /// Returns a tag.
40    #[inline]
41    pub const fn tag(self) -> Tag {
42        (self.unbox() & TAG_MASK) as _
43    }
44
45    /// Sets a tag.
46    #[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}