Skip to main content

sim_kernel/
ref_resolver.rs

1//! The [`RefResolver`] contract: turning references back into values.
2//!
3//! This is protocol the libraries implement; the kernel defines the resolver
4//! trait and a temporary in-memory resolver, not the durable resolution policy.
5
6use crate::{
7    datum::Datum,
8    datum_store::DatumStore,
9    env::Cx,
10    error::{Error, Result},
11    expr::Expr,
12    handle_store::HandleStore,
13    id::Symbol,
14    object::is_default_object_header,
15    ref_id::{Coordinate, Ref},
16    value::Value,
17};
18
19/// Outcome of resolving a [`Ref`] back toward a value or datum.
20#[derive(Clone, Debug, PartialEq, Eq)]
21pub enum ResolvedRef {
22    /// The reference named a symbol.
23    Symbol(Symbol),
24    /// The reference resolved to interned content data.
25    Datum(Datum),
26    /// The reference resolved to a live runtime value.
27    Value(Value),
28    /// The reference named a coordinate in some space.
29    Coordinate(Coordinate),
30    /// The reference could not be resolved in the current context.
31    Missing(Ref),
32}
33
34/// Contract for assigning references to values and resolving them back.
35///
36/// This is protocol the libraries implement; the kernel defines the trait and a
37/// [`TemporaryRefResolver`], not the durable resolution policy.
38pub trait RefResolver {
39    /// Returns a stable [`Ref`] for `value`, interning it if needed.
40    fn ref_for_value(&mut self, cx: &mut Cx, value: &Value) -> Result<Ref>;
41    /// Resolves `reference` to a [`ResolvedRef`] under the current context.
42    fn resolve_ref(&self, cx: &mut Cx, reference: &Ref) -> Result<ResolvedRef>;
43}
44
45/// In-context [`RefResolver`] backed by the registry, datum store, and handle
46/// store; holds no state of its own.
47#[derive(Default)]
48pub struct TemporaryRefResolver;
49
50impl TemporaryRefResolver {
51    /// Creates a resolver.
52    pub fn new() -> Self {
53        Self
54    }
55}
56
57impl RefResolver for TemporaryRefResolver {
58    fn ref_for_value(&mut self, cx: &mut Cx, value: &Value) -> Result<Ref> {
59        if let Some(symbol) = cx.registry().export_symbol_for_value(value) {
60            return Ok(Ref::Symbol(symbol));
61        }
62
63        if let Some(datum) = value.object().snapshot(cx)? {
64            let id = cx.datum_store_mut().intern(datum)?;
65            return Ok(Ref::Content(id));
66        }
67
68        let header = value.object().header();
69        if !is_default_object_header(header) {
70            return Ok(header.id.clone());
71        }
72
73        let handle = cx.handles_mut().intern(value.clone());
74        Ok(Ref::Handle(handle))
75    }
76
77    fn resolve_ref(&self, cx: &mut Cx, reference: &Ref) -> Result<ResolvedRef> {
78        Ok(match reference {
79            Ref::Symbol(symbol) => ResolvedRef::Symbol(symbol.clone()),
80            Ref::Coord(coordinate) => ResolvedRef::Coordinate(coordinate.clone()),
81            Ref::Handle(handle) => cx.handles().get(handle).cloned().map_or_else(
82                || ResolvedRef::Missing(reference.clone()),
83                ResolvedRef::Value,
84            ),
85            Ref::Content(id) => cx.datum_store().get(id)?.cloned().map_or_else(
86                || ResolvedRef::Missing(reference.clone()),
87                ResolvedRef::Datum,
88            ),
89        })
90    }
91}
92
93/// Resolves `reference` to a runtime [`Value`], erroring when it cannot be
94/// turned into one (a missing or coordinate reference, or an unresolved symbol).
95pub fn value_from_ref(cx: &mut Cx, reference: &Ref) -> Result<Value> {
96    match TemporaryRefResolver::new().resolve_ref(cx, reference)? {
97        ResolvedRef::Symbol(symbol) => value_from_symbol(cx, &symbol)
98            .ok_or_else(|| Error::Eval(format!("unresolved symbol ref {symbol}"))),
99        ResolvedRef::Datum(datum) => value_from_datum(cx, datum),
100        ResolvedRef::Value(value) => Ok(value),
101        ResolvedRef::Coordinate(_) | ResolvedRef::Missing(_) => {
102            Err(Error::Eval(format!("unresolved value ref {reference:?}")))
103        }
104    }
105}
106
107/// Builds a runtime [`Value`] from interned content `datum`, mapping symbol-keyed
108/// maps to tables and other maps to map expressions.
109pub fn value_from_datum(cx: &mut Cx, datum: Datum) -> Result<Value> {
110    match datum {
111        Datum::Nil => cx.factory().nil(),
112        Datum::Bool(value) => cx.factory().bool(value),
113        Datum::Number(number) => cx.factory().number_literal(number.domain, number.canonical),
114        Datum::Symbol(symbol) => cx.factory().symbol(symbol),
115        Datum::String(value) => cx.factory().string(value),
116        Datum::Bytes(value) => cx.factory().bytes(value),
117        Datum::List(items) | Datum::Vector(items) | Datum::Set(items) => {
118            let values = items
119                .into_iter()
120                .map(|item| value_from_datum(cx, item))
121                .collect::<Result<Vec<_>>>()?;
122            cx.factory().list(values)
123        }
124        Datum::Map(entries) => table_from_datum_entries(cx, entries),
125        Datum::Node { tag, fields } => cx.factory().expr(Expr::from(Datum::Node { tag, fields })),
126    }
127}
128
129fn value_from_symbol(cx: &Cx, symbol: &Symbol) -> Option<Value> {
130    cx.registry()
131        .value_by_symbol(symbol)
132        .or_else(|| cx.registry().function_by_symbol(symbol))
133        .or_else(|| cx.registry().class_by_symbol(symbol))
134        .or_else(|| cx.registry().shape_by_symbol(symbol))
135        .or_else(|| cx.registry().codec_by_symbol(symbol))
136        .or_else(|| cx.registry().number_domain_by_symbol(symbol))
137        .cloned()
138}
139
140fn table_from_datum_entries(cx: &mut Cx, entries: Vec<(Datum, Datum)>) -> Result<Value> {
141    if entries
142        .iter()
143        .any(|(key, _)| !matches!(key, Datum::Symbol(_)))
144    {
145        return cx.factory().expr(Expr::from(Datum::Map(entries)));
146    }
147    let table_entries = entries
148        .into_iter()
149        .map(|(key, value)| {
150            let Datum::Symbol(symbol) = key else {
151                return Err(Error::Eval(
152                    "non-symbol map key reached table conversion".to_owned(),
153                ));
154            };
155            Ok((symbol, value_from_datum(cx, value)?))
156        })
157        .collect::<Result<Vec<_>>>()?;
158    cx.factory().table(table_entries)
159}
160
161#[cfg(test)]
162mod tests {
163    use std::sync::Arc;
164
165    use super::*;
166    use crate::{ClassRef, Object};
167
168    use crate::testing::bare_cx as cx;
169
170    // sim-non-citizen(reason = "unit-test opaque value fixture", kind = "test-fixture", descriptor = "")
171    struct OpaqueValue(&'static str);
172
173    impl Object for OpaqueValue {
174        fn display(&self, _cx: &mut Cx) -> Result<String> {
175            Ok(format!("#<opaque {}>", self.0))
176        }
177
178        fn as_any(&self) -> &dyn std::any::Any {
179            self
180        }
181    }
182
183    impl crate::ObjectCompat for OpaqueValue {
184        fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
185            cx.factory().nil()
186        }
187    }
188
189    #[test]
190    fn ref_resolver_returns_symbol_ref_for_registered_function_value() {
191        let mut cx = cx();
192        let symbol = Symbol::qualified("test", "registered-fn");
193        let value = cx.factory().string("registered".to_owned()).unwrap();
194        cx.registry_mut()
195            .register_function_value(symbol.clone(), value.clone())
196            .unwrap();
197        let mut resolver = TemporaryRefResolver::new();
198
199        let reference = resolver.ref_for_value(&mut cx, &value).unwrap();
200
201        assert_eq!(reference, Ref::Symbol(symbol));
202    }
203
204    #[test]
205    fn ref_resolver_returns_content_ref_for_pure_value() {
206        let mut cx = cx();
207        let value = cx.factory().string("pure".to_owned()).unwrap();
208        let mut resolver = TemporaryRefResolver::new();
209
210        let reference = resolver.ref_for_value(&mut cx, &value).unwrap();
211
212        assert!(matches!(reference, Ref::Content(_)), "expected content ref");
213        let Ref::Content(id) = reference else {
214            return;
215        };
216        assert!(cx.datum_store().contains(&id));
217        let resolved = resolver.resolve_ref(&mut cx, &Ref::Content(id)).unwrap();
218        assert_eq!(
219            resolved,
220            ResolvedRef::Datum(Datum::String("pure".to_owned()))
221        );
222    }
223
224    #[test]
225    fn ref_resolver_returns_handle_ref_for_opaque_value() {
226        let mut cx = cx();
227        let value = cx.factory().opaque(Arc::new(OpaqueValue("one"))).unwrap();
228        let mut resolver = TemporaryRefResolver::new();
229
230        let reference = resolver.ref_for_value(&mut cx, &value).unwrap();
231
232        assert!(matches!(reference, Ref::Handle(_)));
233    }
234
235    #[test]
236    fn ref_resolver_reuses_handle_for_same_value() {
237        let mut cx = cx();
238        let value = cx.factory().opaque(Arc::new(OpaqueValue("same"))).unwrap();
239        let mut resolver = TemporaryRefResolver::new();
240
241        let first = resolver.ref_for_value(&mut cx, &value).unwrap();
242        let second = resolver.ref_for_value(&mut cx, &value).unwrap();
243
244        assert_eq!(first, second);
245    }
246
247    #[test]
248    fn ref_resolver_resolves_allocated_handle_to_value() {
249        let mut cx = cx();
250        let value = cx.factory().opaque(Arc::new(OpaqueValue("value"))).unwrap();
251        let mut resolver = TemporaryRefResolver::new();
252        let reference = resolver.ref_for_value(&mut cx, &value).unwrap();
253
254        let resolved = resolver.resolve_ref(&mut cx, &reference).unwrap();
255
256        assert_eq!(resolved, ResolvedRef::Value(value));
257    }
258
259    #[test]
260    fn ref_resolver_assigns_distinct_handles_to_distinct_opaque_values() {
261        let mut cx = cx();
262        let first_value = cx.factory().opaque(Arc::new(OpaqueValue("first"))).unwrap();
263        let second_value = cx
264            .factory()
265            .opaque(Arc::new(OpaqueValue("second")))
266            .unwrap();
267        let mut resolver = TemporaryRefResolver::new();
268
269        let first = resolver.ref_for_value(&mut cx, &first_value).unwrap();
270        let second = resolver.ref_for_value(&mut cx, &second_value).unwrap();
271
272        assert_ne!(first, second);
273        assert!(matches!(first, Ref::Handle(_)));
274        assert!(matches!(second, Ref::Handle(_)));
275    }
276
277    #[test]
278    fn ref_resolver_reports_unknown_content_as_missing() {
279        let mut cx = cx();
280        let resolver = TemporaryRefResolver::new();
281        let reference = Ref::Content(crate::ContentId::from_bytes(
282            Symbol::qualified("core", "sha256"),
283            [4; 32],
284        ));
285
286        let resolved = resolver.resolve_ref(&mut cx, &reference).unwrap();
287
288        assert_eq!(resolved, ResolvedRef::Missing(reference));
289    }
290
291    #[test]
292    fn ref_resolver_resolves_interned_content_as_datum() {
293        let mut cx = cx();
294        let datum = Datum::String("stored".to_owned());
295        let id = cx.datum_store_mut().intern(datum.clone()).unwrap();
296        let resolver = TemporaryRefResolver::new();
297
298        let resolved = resolver.resolve_ref(&mut cx, &Ref::Content(id)).unwrap();
299
300        assert_eq!(resolved, ResolvedRef::Datum(datum));
301    }
302}