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, 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/// 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 remainder of the list, or `None` for the empty list.
57    cdr: Option<Rest>,
58}
59
60/// The remainder of a [`ConsList`] cell: either another native cons node (a
61/// cheap shared-pointer tail) or a foreign list value kept lazily.
62#[derive(Clone)]
63enum Rest {
64    /// A native cons node; a `cdr` is a cheap [`Arc`] pointer clone.
65    Cons(Arc<ConsList>),
66    /// A foreign list value. Consing onto a non-`ConsList` tail keeps the tail
67    /// as-is rather than materializing its (possibly unbounded) spine, so the
68    /// laziness of an `iter`/`lazy` tail is preserved.
69    Foreign(Value),
70}
71
72impl ConsList {
73    /// Returns the empty list (a node with no head and no tail).
74    pub fn empty() -> Self {
75        Self {
76            car: None,
77            cdr: None,
78        }
79    }
80
81    /// Builds a non-empty cell prepending `car` onto the shared tail `cdr`.
82    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    /// Builds a non-empty cell prepending `car` onto a foreign list `tail`.
90    ///
91    /// The tail is kept as an opaque list value rather than materialized, so
92    /// consing onto a lazy or unbounded list stays lazy.
93    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    /// Builds a shared list from `items` in order, the first item at the head.
101    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
110/// Reports the tail value referenced by a foreign cell as a list, or a type
111/// mismatch if it is not one.
112fn 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                    // total len = count + tail_len; compare against n = count +
261                    // (n - count), so it suffices to compare the tail against
262                    // the residual budget.
263                    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}