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
24impl 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 unsafe fn new_unchecked(car: Object, cdr: Object) -> ConsInner {
38 ConsInner { mutable: true, car: ObjCell::new(car), cdr: ObjCell::new(cdr) }
39 }
40
41 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 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 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 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}