1use std::{cmp::Ordering, sync::Arc};
5
6use sim_kernel::{
7 CORE_LIST_CLASS_ID, ClassRef, Cx, Error, Expr, LengthResult, ListValue, Object, ObjectEncode,
8 ObjectEncoding, Result, Symbol, Value, force_list_to_vec,
9};
10
11use crate::citizen::cons_list_class_symbol;
12
13#[derive(Clone)]
53pub struct ConsList {
54 car: Option<Value>,
56 cdr: Option<Rest>,
58}
59
60#[derive(Clone)]
63enum Rest {
64 Cons(Arc<ConsList>),
66 Foreign(Value),
70}
71
72impl ConsList {
73 pub fn empty() -> Self {
75 Self {
76 car: None,
77 cdr: None,
78 }
79 }
80
81 pub fn cell(car: Value, cdr: Arc<ConsList>) -> Self {
83 Self {
84 car: Some(car),
85 cdr: Some(Rest::Cons(cdr)),
86 }
87 }
88
89 pub fn cell_foreign(car: Value, tail: Value) -> Self {
94 Self {
95 car: Some(car),
96 cdr: Some(Rest::Foreign(tail)),
97 }
98 }
99
100 pub fn from_vec(items: Vec<Value>) -> Arc<Self> {
102 let mut acc = Arc::new(Self::empty());
103 for item in items.into_iter().rev() {
104 acc = Arc::new(Self::cell(item, acc));
105 }
106 acc
107 }
108}
109
110fn foreign_as_list(value: &Value) -> Result<&dyn ListValue> {
113 value.object().as_list().ok_or(Error::TypeMismatch {
114 expected: "list",
115 found: "non-list",
116 })
117}
118
119impl Object for ConsList {
120 fn display(&self, _cx: &mut Cx) -> Result<String> {
121 Ok("cons[...]".to_owned())
122 }
123
124 fn as_any(&self) -> &dyn std::any::Any {
125 self
126 }
127}
128
129impl sim_kernel::ObjectCompat for ConsList {
130 fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
131 let symbol = cons_list_class_symbol();
132 if let Some(value) = cx.registry().class_by_symbol(&symbol) {
133 return Ok(value.clone());
134 }
135 let symbol = Symbol::qualified("core", "List");
136 if let Some(value) = cx.registry().class_by_symbol(&symbol) {
137 return Ok(value.clone());
138 }
139 cx.factory().class_stub(CORE_LIST_CLASS_ID, symbol)
140 }
141 fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
142 Ok(Expr::List(
143 force_list_to_vec(cx, self, "list as_expr")?
144 .into_iter()
145 .map(|value| value.object().as_expr(cx))
146 .collect::<Result<Vec<_>>>()?,
147 ))
148 }
149 fn truth(&self, _cx: &mut Cx) -> Result<bool> {
150 Ok(self.car.is_some())
151 }
152 fn as_list(&self) -> Option<&dyn ListValue> {
153 Some(self)
154 }
155 fn as_object_encoder(&self) -> Option<&dyn ObjectEncode> {
156 Some(self)
157 }
158}
159
160impl ObjectEncode for ConsList {
161 fn object_encoding(&self, cx: &mut Cx) -> Result<ObjectEncoding> {
162 let items = force_list_to_vec(cx, self, "list/ConsList citizen")?
163 .into_iter()
164 .map(|value| value.object().as_expr(cx))
165 .collect::<Result<Vec<_>>>()?;
166 Ok(ObjectEncoding::Constructor {
167 class: cons_list_class_symbol(),
168 args: vec![
169 Expr::Symbol(Symbol::new("v0")),
170 crate::citizen::expr_items::encode(&items),
171 ],
172 })
173 }
174}
175
176impl sim_citizen::Citizen for ConsList {
177 fn citizen_symbol() -> Symbol {
178 cons_list_class_symbol()
179 }
180
181 fn citizen_version() -> u32 {
182 0
183 }
184
185 fn citizen_arity() -> usize {
186 1
187 }
188
189 fn citizen_fields() -> &'static [&'static str] {
190 &["items"]
191 }
192}
193
194impl ListValue for ConsList {
195 fn is_empty(&self, _cx: &mut Cx) -> Result<bool> {
196 Ok(self.car.is_none())
197 }
198
199 fn car(&self, _cx: &mut Cx) -> Result<Option<Value>> {
200 Ok(self.car.clone())
201 }
202
203 fn cdr(&self, cx: &mut Cx) -> Result<Option<Value>> {
204 match &self.cdr {
205 Some(Rest::Cons(next)) => Ok(Some(cx.factory().opaque(next.clone())?)),
206 Some(Rest::Foreign(tail)) => Ok(Some(tail.clone())),
207 None => Ok(None),
208 }
209 }
210
211 fn len(&self, cx: &mut Cx) -> Result<LengthResult> {
212 if self.car.is_none() {
213 return Ok(LengthResult::Known(0));
214 }
215 let mut count = 1usize;
216 let mut rest = self.cdr.clone();
217 loop {
218 match rest {
219 None => return Ok(LengthResult::Known(count)),
220 Some(Rest::Cons(node)) => {
221 if node.car.is_none() {
222 return Ok(LengthResult::Known(count));
223 }
224 count += 1;
225 rest = node.cdr.clone();
226 }
227 Some(Rest::Foreign(tail)) => {
228 return Ok(match foreign_as_list(&tail)?.len(cx)? {
229 LengthResult::Known(k) => LengthResult::Known(count + k),
230 LengthResult::Unknown => LengthResult::Unknown,
231 });
232 }
233 }
234 }
235 }
236
237 fn len_cmp(&self, cx: &mut Cx, n: usize) -> Result<Ordering> {
238 if self.car.is_none() {
239 return Ok(0usize.cmp(&n));
240 }
241 let mut count = 1usize;
242 if count > n {
243 return Ok(Ordering::Greater);
244 }
245 let mut rest = self.cdr.clone();
246 loop {
247 match rest {
248 None => return Ok(count.cmp(&n)),
249 Some(Rest::Cons(node)) => {
250 if node.car.is_none() {
251 return Ok(count.cmp(&n));
252 }
253 count += 1;
254 if count > n {
255 return Ok(Ordering::Greater);
256 }
257 rest = node.cdr.clone();
258 }
259 Some(Rest::Foreign(tail)) => {
260 return foreign_as_list(&tail)?.len_cmp(cx, n - count);
264 }
265 }
266 }
267 }
268
269 fn get(&self, cx: &mut Cx, index: usize) -> Result<Option<Value>> {
270 let mut node_car = self.car.clone();
271 let mut rest = self.cdr.clone();
272 let mut i = index;
273 loop {
274 let Some(car) = node_car else {
275 return Ok(None);
276 };
277 if i == 0 {
278 return Ok(Some(car));
279 }
280 i -= 1;
281 match rest {
282 None => return Ok(None),
283 Some(Rest::Cons(node)) => {
284 node_car = node.car.clone();
285 rest = node.cdr.clone();
286 }
287 Some(Rest::Foreign(tail)) => {
288 return foreign_as_list(&tail)?.get(cx, i);
289 }
290 }
291 }
292 }
293
294 fn for_each(
295 &self,
296 cx: &mut Cx,
297 limit: Option<usize>,
298 visit: &mut dyn FnMut(&Value),
299 ) -> Result<()> {
300 if matches!(limit, Some(0)) {
301 return Ok(());
302 }
303
304 let mut node_car = self.car.clone();
305 let mut rest = self.cdr.clone();
306 let mut count = 0usize;
307 loop {
308 let Some(car) = node_car else {
309 return Ok(());
310 };
311 if matches!(limit, Some(max) if count >= max) {
312 return Ok(());
313 }
314 visit(&car);
315 count += 1;
316 match rest {
317 None => return Ok(()),
318 Some(Rest::Cons(node)) => {
319 node_car = node.car.clone();
320 rest = node.cdr.clone();
321 }
322 Some(Rest::Foreign(tail)) => {
323 let remaining = limit.map(|max| max - count);
324 return foreign_as_list(&tail)?.for_each(cx, remaining, visit);
325 }
326 }
327 }
328 }
329}