rune/core/
cons.rs

1use crate::derive_GcMoveable;
2
3use super::gc::{Block, GcHeap, GcState, Trace};
4use super::object::{CloneIn, Gc, IntoObject, NIL, ObjCell, Object, ObjectType};
5use anyhow::{Result, anyhow};
6use rune_core::hashmap::HashSet;
7use rune_macros::Trace;
8use std::fmt::{self, Debug, Display, Write};
9
10mod iter;
11pub(crate) use iter::*;
12
13#[derive(Trace)]
14pub(crate) struct Cons(GcHeap<ConsInner>);
15
16derive_GcMoveable!(Cons);
17
18struct ConsInner {
19    mutable: bool,
20    car: ObjCell,
21    cdr: ObjCell,
22}
23
24// TODO: we need to handle loops in equal
25impl PartialEq for Cons {
26    fn eq(&self, other: &Self) -> bool {
27        self.car() == other.car() && self.cdr() == other.cdr()
28    }
29}
30
31impl Eq for Cons {}
32
33impl Cons {
34    // SAFETY: Cons must always be allocated in the GC heap, it cannot live on
35    // the stack. Otherwise it could outlive it's objects since it has no
36    // lifetimes.
37    unsafe fn new_unchecked(car: Object, cdr: Object) -> ConsInner {
38        ConsInner { mutable: true, car: ObjCell::new(car), cdr: ObjCell::new(cdr) }
39    }
40
41    /// Create a new cons cell
42    pub(crate) fn new<'ob, T, Tx, U, Ux, const C: bool>(
43        car: T,
44        cdr: U,
45        cx: &'ob Block<C>,
46    ) -> &'ob Self
47    where
48        T: IntoObject<Out<'ob> = Tx>,
49        Gc<Tx>: Into<Object<'ob>>,
50        U: IntoObject<Out<'ob> = Ux>,
51        Gc<Ux>: Into<Object<'ob>>,
52    {
53        let car = car.into_obj(cx).into();
54        let cdr = cdr.into_obj(cx).into();
55        let cons = unsafe { Cons::new_unchecked(car, cdr) };
56        Cons(GcHeap::new(cons, C)).into_obj(cx).untag()
57    }
58
59    /// Create a new cons cell with the cdr set to nil
60    pub(crate) fn new1<'ob, T, Tx, const C: bool>(car: T, cx: &'ob Block<C>) -> &'ob Self
61    where
62        T: IntoObject<Out<'ob> = Tx>,
63        Gc<Tx>: Into<Object<'ob>>,
64    {
65        let car = car.into_obj(cx).into();
66        let cons = unsafe { Cons::new_unchecked(car, NIL) };
67        Cons(GcHeap::new(cons, C)).into_obj(cx).untag()
68    }
69
70    pub(in crate::core) fn mark_const(&mut self) {
71        self.0.mutable = false;
72    }
73}
74
75impl Cons {
76    pub(crate) fn car(&self) -> Object {
77        self.0.car.get()
78    }
79
80    pub(crate) fn cdr(&self) -> Object {
81        self.0.cdr.get()
82    }
83
84    pub(crate) fn cadr(&self) -> Option<Object> {
85        if let ObjectType::Cons(cons) = self.cdr().untag() {
86            return Some(cons.car());
87        }
88        None
89    }
90
91    pub(crate) fn cddr(&self) -> Option<Object> {
92        if let ObjectType::Cons(cons) = self.cdr().untag() {
93            return Some(cons.cdr());
94        }
95        None
96    }
97
98    pub(crate) fn set_car(&self, new_car: Object) -> Result<()> {
99        if self.0.mutable {
100            unsafe { self.0.car.as_mut().set(new_car) }
101            Ok(())
102        } else {
103            Err(anyhow!("Attempt to call setcar on immutable cons cell"))
104        }
105    }
106
107    pub(crate) fn set_cdr(&self, new_cdr: Object) -> Result<()> {
108        if self.0.mutable {
109            unsafe { self.0.cdr.as_mut().set(new_cdr) }
110            Ok(())
111        } else {
112            Err(anyhow!("Attempt to call setcdr on immutable cons cell"))
113        }
114    }
115}
116
117impl<'new> CloneIn<'new, &'new Cons> for Cons {
118    fn clone_in<const C: bool>(&self, bk: &'new Block<C>) -> Gc<&'new Cons> {
119        Cons::new(self.car().clone_in(bk), self.cdr().clone_in(bk), bk).into_obj(bk)
120    }
121}
122
123impl Trace for ConsInner {
124    fn trace(&self, state: &mut GcState) {
125        self.car.trace(state);
126        // We want cdr to be traced second, because that way it will end on top
127        // of the stack and be closer to the current cons cell
128        self.cdr.trace(state);
129    }
130}
131
132impl Display for Cons {
133    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
134        self.display_walk(f, &mut HashSet::default())
135    }
136}
137
138impl Debug for Cons {
139    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
140        self.display_walk(f, &mut HashSet::default())
141    }
142}
143
144impl Cons {
145    pub(super) fn display_walk(
146        &self,
147        f: &mut fmt::Formatter,
148        seen: &mut HashSet<*const u8>,
149    ) -> fmt::Result {
150        if self.is_backref(seen) {
151            return f.write_str("#0");
152        }
153
154        f.write_char('(')?;
155        let mut cons = self;
156
157        loop {
158            cons.car().untag().display_walk(f, seen)?;
159            match cons.cdr().untag() {
160                ObjectType::Cons(tail) => {
161                    cons = tail;
162                    f.write_char(' ')?;
163                }
164                ObjectType::NIL => break,
165                x => {
166                    write!(f, " . ")?;
167                    x.display_walk(f, seen)?;
168                    break;
169                }
170            }
171            if cons.is_backref(seen) {
172                f.write_str(". #0")?;
173                break;
174            }
175        }
176        f.write_char(')')
177    }
178
179    fn is_backref(&self, seen: &mut HashSet<*const u8>) -> bool {
180        let ptr = (self as *const Self).cast();
181        if seen.contains(&ptr) {
182            true
183        } else {
184            seen.insert(ptr);
185            false
186        }
187    }
188}
189
190define_unbox!(Cons, &'ob Cons);
191
192#[cfg(test)]
193mod test {
194    use crate::core::gc::Context;
195    use crate::interpreter::assert_lisp;
196    use rune_core::macros::list;
197
198    use super::super::gc::RootSet;
199    use super::*;
200
201    #[test]
202    fn cons() {
203        let roots = &RootSet::default();
204        let cx = &Context::new(roots);
205        // TODO: Need to find a way to solve this
206        // assert_eq!(16, size_of::<Cons>());
207        let cons1 = Cons::new("start", Cons::new(7, Cons::new(5, 9, cx), cx), cx);
208
209        assert_eq!(cx.add("start"), cons1.car());
210        cons1.set_car(cx.add("start2")).unwrap();
211        assert_eq!(cx.add("start2"), cons1.car());
212
213        let ObjectType::Cons(cons2) = cons1.cdr().untag() else { unreachable!("Expected cons") };
214
215        let cmp: Object = 7.into();
216        assert_eq!(cmp, cons2.car());
217
218        let ObjectType::Cons(cons3) = cons2.cdr().untag() else { unreachable!("Expected cons") };
219        let cmp1: Object = 5.into();
220        assert_eq!(cmp1, cons3.car());
221        let cmp2: Object = 9.into();
222        assert_eq!(cmp2, cons3.cdr());
223
224        let lhs = Cons::new(5, "foo", cx);
225        assert_eq!(lhs, Cons::new(5, "foo", cx));
226        assert_ne!(lhs, Cons::new(5, "bar", cx));
227        let lhs = list![5, 1, 1.5, "foo"; cx];
228        assert_eq!(lhs, list![5, 1, 1.5, "foo"; cx]);
229        assert_ne!(lhs, list![5, 1, 1.5, "bar"; cx]);
230    }
231
232    #[test]
233    fn display() {
234        assert_lisp("(quote foo)", "foo");
235        assert_lisp("(quote ((quote foo)))", "('foo)");
236    }
237}