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/// If this value is in a `car` field in a cons, that means the cons is moved
11/// already on garbage collection. See also [`crate::Memory::collect_garbages`].
12pub(crate) const NEVER: Cons = Cons::new(1); // A cons can never point an odd index.
13
14const TAG_SIZE: usize = Tag::BITS as usize;
15const TAG_MASK: u64 = Tag::MAX as u64;
16
17/// A cons.
18#[derive(Clone, Copy, Debug)]
19pub struct Cons(u64);
20
21impl Cons {
22    /// Creates a cons from a memory address on heap.
23    pub const fn new(index: u64) -> Self {
24        Self::r#box(index << TAG_SIZE)
25    }
26
27    /// Returns a memory address on heap.
28    pub const fn index(self) -> usize {
29        (self.unbox() >> TAG_SIZE) as _
30    }
31
32    /// Returns a tag.
33    pub const fn tag(self) -> Tag {
34        (self.unbox() & TAG_MASK) as _
35    }
36
37    /// Sets a tag.
38    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}