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().macro_by_symbol(symbol))
135        .or_else(|| cx.registry().shape_by_symbol(symbol))
136        .or_else(|| cx.registry().codec_by_symbol(symbol))
137        .or_else(|| cx.registry().number_domain_by_symbol(symbol))
138        .or_else(|| cx.registry().site_by_symbol(symbol))
139        .cloned()
140}
141
142fn table_from_datum_entries(cx: &mut Cx, entries: Vec<(Datum, Datum)>) -> Result<Value> {
143    if entries
144        .iter()
145        .any(|(key, _)| !matches!(key, Datum::Symbol(_)))
146    {
147        return cx.factory().expr(Expr::from(Datum::Map(entries)));
148    }
149    let table_entries = entries
150        .into_iter()
151        .map(|(key, value)| {
152            let Datum::Symbol(symbol) = key else {
153                return Err(Error::Eval(
154                    "non-symbol map key reached table conversion".to_owned(),
155                ));
156            };
157            Ok((symbol, value_from_datum(cx, value)?))
158        })
159        .collect::<Result<Vec<_>>>()?;
160    cx.factory().table(table_entries)
161}
162
163#[cfg(test)]
164mod tests {
165    use std::sync::Arc;
166
167    use super::*;
168    use crate::{ClassRef, Object};
169
170    use crate::testing::bare_cx as cx;
171
172    // sim-non-citizen(reason = "unit-test opaque value fixture", kind = "test-fixture", descriptor = "")
173    struct OpaqueValue(&'static str);
174
175    impl Object for OpaqueValue {
176        fn display(&self, _cx: &mut Cx) -> Result<String> {
177            Ok(format!("#<opaque {}>", self.0))
178        }
179
180        fn as_any(&self) -> &dyn std::any::Any {
181            self
182        }
183    }
184
185    impl crate::ObjectCompat for OpaqueValue {
186        fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
187            cx.factory().nil()
188        }
189    }
190
191    #[test]
192    fn ref_resolver_returns_symbol_ref_for_registered_function_value() {
193        let mut cx = cx();
194        let symbol = Symbol::qualified("test", "registered-fn");
195        let value = cx.factory().string("registered".to_owned()).unwrap();
196        cx.registry_mut()
197            .register_function_value(symbol.clone(), value.clone())
198            .unwrap();
199        let mut resolver = TemporaryRefResolver::new();
200
201        let reference = resolver.ref_for_value(&mut cx, &value).unwrap();
202
203        assert_eq!(reference, Ref::Symbol(symbol));
204    }
205
206    #[test]
207    fn macro_export_round_trips_through_symbol_ref() {
208        let mut cx = cx();
209        let symbol = Symbol::qualified("macro", "fixture");
210        let value = cx.factory().bool(true).unwrap();
211        cx.registry_mut()
212            .register_macro_value(symbol.clone(), value.clone())
213            .unwrap();
214        let mut resolver = TemporaryRefResolver::new();
215
216        let reference = resolver.ref_for_value(&mut cx, &value).unwrap();
217        let resolved = value_from_ref(&mut cx, &reference).unwrap();
218
219        assert_eq!(reference, Ref::Symbol(symbol));
220        assert_eq!(resolved, value);
221    }
222
223    #[test]
224    fn site_export_round_trips_through_symbol_ref() {
225        let mut cx = cx();
226        let symbol = Symbol::qualified("model", "local");
227        let value = cx.factory().bool(true).unwrap();
228        cx.registry_mut()
229            .register_site_value(symbol.clone(), value.clone())
230            .unwrap();
231        let mut resolver = TemporaryRefResolver::new();
232
233        let reference = resolver.ref_for_value(&mut cx, &value).unwrap();
234        let resolved = value_from_ref(&mut cx, &reference).unwrap();
235
236        assert_eq!(reference, Ref::Symbol(symbol));
237        assert_eq!(resolved, value);
238    }
239
240    #[test]
241    fn ref_resolver_returns_content_ref_for_pure_value() {
242        let mut cx = cx();
243        let value = cx.factory().string("pure".to_owned()).unwrap();
244        let mut resolver = TemporaryRefResolver::new();
245
246        let reference = resolver.ref_for_value(&mut cx, &value).unwrap();
247
248        assert!(matches!(reference, Ref::Content(_)), "expected content ref");
249        let Ref::Content(id) = reference else {
250            return;
251        };
252        assert!(cx.datum_store().contains(&id));
253        let resolved = resolver.resolve_ref(&mut cx, &Ref::Content(id)).unwrap();
254        assert_eq!(
255            resolved,
256            ResolvedRef::Datum(Datum::String("pure".to_owned()))
257        );
258    }
259
260    #[test]
261    fn ref_resolver_returns_handle_ref_for_opaque_value() {
262        let mut cx = cx();
263        let value = cx.factory().opaque(Arc::new(OpaqueValue("one"))).unwrap();
264        let mut resolver = TemporaryRefResolver::new();
265
266        let reference = resolver.ref_for_value(&mut cx, &value).unwrap();
267
268        assert!(matches!(reference, Ref::Handle(_)));
269    }
270
271    #[test]
272    fn ref_resolver_reuses_handle_for_same_value() {
273        let mut cx = cx();
274        let value = cx.factory().opaque(Arc::new(OpaqueValue("same"))).unwrap();
275        let mut resolver = TemporaryRefResolver::new();
276
277        let first = resolver.ref_for_value(&mut cx, &value).unwrap();
278        let second = resolver.ref_for_value(&mut cx, &value).unwrap();
279
280        assert_eq!(first, second);
281    }
282
283    #[test]
284    fn ref_resolver_resolves_allocated_handle_to_value() {
285        let mut cx = cx();
286        let value = cx.factory().opaque(Arc::new(OpaqueValue("value"))).unwrap();
287        let mut resolver = TemporaryRefResolver::new();
288        let reference = resolver.ref_for_value(&mut cx, &value).unwrap();
289
290        let resolved = resolver.resolve_ref(&mut cx, &reference).unwrap();
291
292        assert_eq!(resolved, ResolvedRef::Value(value));
293    }
294
295    #[test]
296    fn ref_resolver_assigns_distinct_handles_to_distinct_opaque_values() {
297        let mut cx = cx();
298        let first_value = cx.factory().opaque(Arc::new(OpaqueValue("first"))).unwrap();
299        let second_value = cx
300            .factory()
301            .opaque(Arc::new(OpaqueValue("second")))
302            .unwrap();
303        let mut resolver = TemporaryRefResolver::new();
304
305        let first = resolver.ref_for_value(&mut cx, &first_value).unwrap();
306        let second = resolver.ref_for_value(&mut cx, &second_value).unwrap();
307
308        assert_ne!(first, second);
309        assert!(matches!(first, Ref::Handle(_)));
310        assert!(matches!(second, Ref::Handle(_)));
311    }
312
313    #[test]
314    fn ref_resolver_reports_unknown_content_as_missing() {
315        let mut cx = cx();
316        let resolver = TemporaryRefResolver::new();
317        let reference = Ref::Content(crate::ContentId::from_bytes(
318            Symbol::qualified("core", "sha256"),
319            [4; 32],
320        ));
321
322        let resolved = resolver.resolve_ref(&mut cx, &reference).unwrap();
323
324        assert_eq!(resolved, ResolvedRef::Missing(reference));
325    }
326
327    #[test]
328    fn ref_resolver_resolves_interned_content_as_datum() {
329        let mut cx = cx();
330        let datum = Datum::String("stored".to_owned());
331        let id = cx.datum_store_mut().intern(datum.clone()).unwrap();
332        let resolver = TemporaryRefResolver::new();
333
334        let resolved = resolver.resolve_ref(&mut cx, &Ref::Content(id)).unwrap();
335
336        assert_eq!(resolved, ResolvedRef::Datum(datum));
337    }
338}