Skip to main content

sim_list_cell/
cons.rs

1//! The [`ConsList`] object: a singly linked, shared cons-cell list that
2//! implements the kernel list value and object-encoding contracts.
3
4use std::{cmp::Ordering, sync::Arc};
5
6use sim_kernel::{
7    CORE_LIST_CLASS_ID, ClassRef, Cx, 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/// A singly linked, shared cons-cell list.
14///
15/// Each node holds an optional `car` (the head value) and an optional `cdr`
16/// (a shared reference to the next node). The unique empty list is the node
17/// with both fields `None`; a non-empty list is a chain of [`ConsList::cell`]
18/// nodes terminated by an empty node. Nodes are shared through [`Arc`], so a
19/// [`cdr`](ListValue::cdr) is a cheap pointer clone and tails can be shared
20/// across many lists.
21///
22/// `ConsList` is the concrete object behind the `cons`
23/// [`ListBackend`](sim_kernel::ListBackend) ([`crate::ConsBackend`]); it
24/// implements the kernel list and
25/// object-encoding contracts so the runtime treats it as a first-class list
26/// value.
27///
28/// # Examples
29///
30/// ```
31/// use std::sync::Arc;
32/// use sim_kernel::{Cx, DefaultFactory, EagerPolicy, Factory, ListValue, LengthResult};
33/// use sim_list_cell::ConsList;
34///
35/// let mut cx = Cx::new(Arc::new(EagerPolicy), Arc::new(DefaultFactory));
36///
37/// // The empty list.
38/// let empty = ConsList::from_vec(vec![]);
39/// assert!(empty.is_empty(&mut cx).unwrap());
40/// assert_eq!(empty.len(&mut cx).unwrap(), LengthResult::Known(0));
41///
42/// // A two-element list, read by head and tail.
43/// let a = cx.factory().bool(true).unwrap();
44/// let b = cx.factory().bool(false).unwrap();
45/// let xs = ConsList::from_vec(vec![a, b]);
46/// assert_eq!(xs.len(&mut cx).unwrap(), LengthResult::Known(2));
47/// assert!(xs.car(&mut cx).unwrap().is_some());
48/// let tail = xs.cdr(&mut cx).unwrap().unwrap();
49/// let tail = tail.object().as_list().unwrap();
50/// assert_eq!(tail.len(&mut cx).unwrap(), LengthResult::Known(1));
51/// ```
52#[derive(Clone)]
53pub struct ConsList {
54    /// The head value of this node, or `None` for the empty list.
55    car: Option<Value>,
56    /// The shared remainder of the list, or `None` for the empty list.
57    cdr: Option<Arc<ConsList>>,
58}
59
60impl ConsList {
61    /// Returns the empty list (a node with no head and no tail).
62    pub fn empty() -> Self {
63        Self {
64            car: None,
65            cdr: None,
66        }
67    }
68
69    /// Builds a non-empty cell prepending `car` onto the shared tail `cdr`.
70    pub fn cell(car: Value, cdr: Arc<ConsList>) -> Self {
71        Self {
72            car: Some(car),
73            cdr: Some(cdr),
74        }
75    }
76
77    /// Builds a shared list from `items` in order, the first item at the head.
78    pub fn from_vec(items: Vec<Value>) -> Arc<Self> {
79        let mut acc = Arc::new(Self::empty());
80        for item in items.into_iter().rev() {
81            acc = Arc::new(Self::cell(item, acc));
82        }
83        acc
84    }
85
86    fn count_cells(&self) -> usize {
87        let mut count = 0usize;
88        let mut cursor = self.cdr.as_ref().cloned();
89        if self.car.is_none() {
90            return 0;
91        }
92
93        count += 1;
94        while let Some(node) = cursor {
95            if node.car.is_none() {
96                break;
97            }
98            count += 1;
99            cursor = node.cdr.as_ref().cloned();
100        }
101        count
102    }
103
104    fn len_cmp_cells(&self, n: usize) -> Ordering {
105        if self.car.is_none() {
106            return 0usize.cmp(&n);
107        }
108
109        let mut count = 1usize;
110        if count > n {
111            return Ordering::Greater;
112        }
113
114        let mut cursor = self.cdr.as_ref().cloned();
115        while let Some(next) = cursor {
116            if next.car.is_none() {
117                break;
118            }
119            count += 1;
120            if count > n {
121                return Ordering::Greater;
122            }
123            cursor = next.cdr.as_ref().cloned();
124        }
125        count.cmp(&n)
126    }
127}
128
129impl Object for ConsList {
130    fn display(&self, _cx: &mut Cx) -> Result<String> {
131        Ok("cons[...]".to_owned())
132    }
133
134    fn as_any(&self) -> &dyn std::any::Any {
135        self
136    }
137}
138
139impl sim_kernel::ObjectCompat for ConsList {
140    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
141        let symbol = cons_list_class_symbol();
142        if let Some(value) = cx.registry().class_by_symbol(&symbol) {
143            return Ok(value.clone());
144        }
145        let symbol = Symbol::qualified("core", "List");
146        if let Some(value) = cx.registry().class_by_symbol(&symbol) {
147            return Ok(value.clone());
148        }
149        cx.factory().class_stub(CORE_LIST_CLASS_ID, symbol)
150    }
151    fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
152        Ok(Expr::List(
153            force_list_to_vec(cx, self, "list as_expr")?
154                .into_iter()
155                .map(|value| value.object().as_expr(cx))
156                .collect::<Result<Vec<_>>>()?,
157        ))
158    }
159    fn truth(&self, _cx: &mut Cx) -> Result<bool> {
160        Ok(self.car.is_some())
161    }
162    fn as_list(&self) -> Option<&dyn ListValue> {
163        Some(self)
164    }
165    fn as_object_encoder(&self) -> Option<&dyn ObjectEncode> {
166        Some(self)
167    }
168}
169
170impl ObjectEncode for ConsList {
171    fn object_encoding(&self, cx: &mut Cx) -> Result<ObjectEncoding> {
172        let items = force_list_to_vec(cx, self, "list/ConsList citizen")?
173            .into_iter()
174            .map(|value| value.object().as_expr(cx))
175            .collect::<Result<Vec<_>>>()?;
176        Ok(ObjectEncoding::Constructor {
177            class: cons_list_class_symbol(),
178            args: vec![
179                Expr::Symbol(Symbol::new("v0")),
180                crate::citizen::expr_items::encode(&items),
181            ],
182        })
183    }
184}
185
186impl sim_citizen::Citizen for ConsList {
187    fn citizen_symbol() -> Symbol {
188        cons_list_class_symbol()
189    }
190
191    fn citizen_version() -> u32 {
192        0
193    }
194
195    fn citizen_arity() -> usize {
196        1
197    }
198
199    fn citizen_fields() -> &'static [&'static str] {
200        &["items"]
201    }
202}
203
204impl ListValue for ConsList {
205    fn is_empty(&self, _cx: &mut Cx) -> Result<bool> {
206        Ok(self.car.is_none())
207    }
208
209    fn car(&self, _cx: &mut Cx) -> Result<Option<Value>> {
210        Ok(self.car.clone())
211    }
212
213    fn cdr(&self, cx: &mut Cx) -> Result<Option<Value>> {
214        match &self.cdr {
215            Some(next) => Ok(Some(cx.factory().opaque(next.clone())?)),
216            None => Ok(None),
217        }
218    }
219
220    fn len(&self, _cx: &mut Cx) -> Result<LengthResult> {
221        Ok(LengthResult::Known(self.count_cells()))
222    }
223
224    fn len_cmp(&self, _cx: &mut Cx, n: usize) -> Result<Ordering> {
225        Ok(self.len_cmp_cells(n))
226    }
227
228    fn get(&self, _cx: &mut Cx, index: usize) -> Result<Option<Value>> {
229        let mut current = Some(Arc::new(self.clone()));
230        let mut i = index;
231        while let Some(node) = current {
232            let Some(car) = &node.car else {
233                return Ok(None);
234            };
235            if i == 0 {
236                return Ok(Some(car.clone()));
237            }
238            i -= 1;
239            current = node.cdr.as_ref().cloned();
240        }
241        Ok(None)
242    }
243
244    fn for_each(
245        &self,
246        _cx: &mut Cx,
247        limit: Option<usize>,
248        visit: &mut dyn FnMut(&Value),
249    ) -> Result<()> {
250        if matches!(limit, Some(0)) {
251            return Ok(());
252        }
253
254        let mut current = Some(Arc::new(self.clone()));
255        let mut count = 0usize;
256        while let Some(node) = current {
257            let Some(car) = &node.car else {
258                return Ok(());
259            };
260            if matches!(limit, Some(max) if count >= max) {
261                return Ok(());
262            }
263            visit(car);
264            count += 1;
265            current = node.cdr.as_ref().cloned();
266        }
267        Ok(())
268    }
269}