Skip to main content

sim_citizen/
runtime.rs

1//! Runtime installation helpers for registering citizens with a context.
2
3use std::{
4    fmt::Debug,
5    marker::PhantomData,
6    sync::{
7        Arc,
8        atomic::{AtomicU32, Ordering},
9    },
10};
11
12use sim_kernel::{
13    Args, Callable, Class, ClassId, ClassRef, Cx, DefaultFactory, Expr, Factory, Object,
14    ObjectEncode, ObjectEncoding, ReadConstructor, ReadConstructorRef, Result, ShapeRef, Symbol,
15    TableRef, Value,
16};
17
18use crate::{arity_error, parse_symbol};
19
20/// Static citizen identity: the metadata `#[derive(Citizen)]` supplies.
21///
22/// Reports the citizen's class symbol, encoding version, constructor arity, and
23/// field names. The kernel owns `Symbol`; this trait is the Rust-side identity
24/// a citizen registers against the kernel's class and read-construct contracts.
25///
26/// # Examples
27///
28/// `#[derive(Citizen)]` supplies this identity from the `#[citizen(...)]`
29/// attribute and the struct's fields:
30///
31/// ```
32/// # use sim_citizen::Citizen;
33/// # use sim_citizen_derive::Citizen;
34/// #[derive(Clone, Debug, Default, PartialEq, Citizen)]
35/// #[citizen(symbol = "doc/Point", version = 1)]
36/// struct Point {
37///     x: i64,
38///     y: i64,
39/// }
40///
41/// assert_eq!(Point::citizen_symbol().to_string(), "doc/Point");
42/// assert_eq!(Point::citizen_version(), 1);
43/// assert_eq!(Point::citizen_arity(), 2);
44/// assert_eq!(Point::citizen_fields(), &["x", "y"]);
45/// ```
46pub trait Citizen: Clone + Send + Sync + 'static {
47    /// The citizen's `namespace/name` class symbol.
48    fn citizen_symbol() -> Symbol;
49    /// The citizen's encoding version.
50    fn citizen_version() -> u32;
51    /// Number of constructor fields (excluding the version argument).
52    fn citizen_arity() -> usize;
53    /// The citizen's field names, in constructor order.
54    fn citizen_fields() -> &'static [&'static str];
55}
56
57/// A fully runnable citizen: identity plus kernel object and read-construct.
58///
59/// Combines [`Citizen`] with the kernel object, encoding, and equality bounds
60/// needed to install the type as a class and round-trip it through
61/// read-construct. Read-construct stays capability-gated by the runtime path,
62/// not by implementing this trait.
63pub trait CitizenRuntime:
64    Citizen + Object + sim_kernel::ObjectCompat + ObjectEncode + PartialEq + Debug
65{
66    /// Builds the citizen from decoded constructor argument values.
67    fn construct_from_values(cx: &mut Cx, args: Vec<Value>) -> Result<Self>;
68    /// Returns the canonical example value used as a conformance fixture.
69    fn example() -> Self;
70}
71
72/// Installs a derived citizen `T` as a class value in `linker`.
73///
74/// Registers a class backing `T`'s callable, class, and read-constructor
75/// contracts and binds it under `T::citizen_symbol`. This is the call a
76/// citizen's generated [`InstallFn`](crate::InstallFn) makes.
77pub fn install_derived<T>(linker: &mut sim_kernel::Linker<'_>) -> Result<()>
78where
79    T: CitizenRuntime,
80{
81    let class = Arc::new(DerivedCitizenClass::<T>::new());
82    let id = linker.class_value(
83        T::citizen_symbol(),
84        DefaultFactory
85            .opaque(class.clone())
86            .expect("citizen class should be boxable"),
87    )?;
88    class.set_id(id);
89    Ok(())
90}
91
92/// Encodes a citizen value as its read-construct `Expr` extension form.
93///
94/// Maps the value's [`ObjectEncoding`] to the matching `Expr::Extension`: a
95/// constructor encoding becomes a `citizen/read-construct` vector, tagged data
96/// becomes a tagged map, and an opaque encoding becomes a tagged stable id.
97pub fn constructor_expr<T>(cx: &mut Cx, value: &T) -> Result<Expr>
98where
99    T: Citizen + ObjectEncode,
100{
101    match value.object_encoding(cx)? {
102        ObjectEncoding::Constructor { class, args } => Ok(Expr::Extension {
103            tag: Symbol::qualified("citizen", "read-construct"),
104            payload: Box::new(Expr::Vector(
105                std::iter::once(Expr::Symbol(class)).chain(args).collect(),
106            )),
107        }),
108        ObjectEncoding::TaggedData { tag, fields } => Ok(Expr::Extension {
109            tag,
110            payload: Box::new(Expr::Map(
111                fields
112                    .into_iter()
113                    .map(|(key, value)| (Expr::Symbol(key), value))
114                    .collect(),
115            )),
116        }),
117        ObjectEncoding::Opaque { class, stable_id } => Ok(Expr::Extension {
118            tag: class,
119            payload: Box::new(Expr::String(stable_id)),
120        }),
121    }
122}
123
124pub struct DerivedCitizenClass<T> {
125    id: AtomicU32,
126    marker: PhantomData<T>,
127}
128
129impl<T> DerivedCitizenClass<T> {
130    fn new() -> Self {
131        Self {
132            id: AtomicU32::new(0),
133            marker: PhantomData,
134        }
135    }
136
137    fn set_id(&self, id: ClassId) {
138        self.id.store(id.0, Ordering::Relaxed);
139    }
140}
141
142impl<T> Object for DerivedCitizenClass<T>
143where
144    T: CitizenRuntime,
145{
146    fn display(&self, _cx: &mut Cx) -> Result<String> {
147        Ok(format!("#<class {}>", T::citizen_symbol()))
148    }
149
150    fn as_any(&self) -> &dyn std::any::Any {
151        self
152    }
153}
154
155impl<T> sim_kernel::ObjectCompat for DerivedCitizenClass<T>
156where
157    T: CitizenRuntime,
158{
159    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
160        if let Some(value) = cx
161            .registry()
162            .class_by_symbol(&Symbol::qualified("core", "Class"))
163        {
164            return Ok(value.clone());
165        }
166        cx.factory().class_stub(
167            sim_kernel::CORE_CLASS_CLASS_ID,
168            Symbol::qualified("core", "Class"),
169        )
170    }
171
172    fn as_expr(&self, _cx: &mut Cx) -> Result<Expr> {
173        Ok(Expr::Symbol(T::citizen_symbol()))
174    }
175
176    fn as_callable(&self) -> Option<&dyn Callable> {
177        Some(self)
178    }
179
180    fn as_class(&self) -> Option<&dyn Class> {
181        Some(self)
182    }
183
184    fn as_read_constructor(&self) -> Option<&dyn ReadConstructor> {
185        Some(self)
186    }
187}
188
189impl<T> Callable for DerivedCitizenClass<T>
190where
191    T: CitizenRuntime,
192{
193    fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
194        let value = T::construct_from_values(cx, args.into_vec())?;
195        cx.factory().opaque(Arc::new(value))
196    }
197}
198
199impl<T> Class for DerivedCitizenClass<T>
200where
201    T: CitizenRuntime,
202{
203    fn id(&self) -> ClassId {
204        ClassId(self.id.load(Ordering::Relaxed))
205    }
206
207    fn symbol(&self) -> Symbol {
208        T::citizen_symbol()
209    }
210
211    fn constructor_shape(&self, cx: &mut Cx) -> Result<ShapeRef> {
212        cx.factory().nil()
213    }
214
215    fn instance_shape(&self, cx: &mut Cx) -> Result<ShapeRef> {
216        cx.factory().nil()
217    }
218
219    fn read_constructor(&self, cx: &mut Cx) -> Result<Option<ReadConstructorRef>> {
220        Ok(cx.registry().class_by_symbol(&T::citizen_symbol()).cloned())
221    }
222
223    fn members(&self, cx: &mut Cx) -> Result<TableRef> {
224        let fields = T::citizen_fields()
225            .iter()
226            .map(|field| cx.factory().symbol(Symbol::new((*field).to_owned())))
227            .collect::<Result<Vec<_>>>()?;
228        cx.factory().table(vec![
229            (
230                Symbol::new("version"),
231                cx.factory().number_literal(
232                    parse_symbol("citizen/int"),
233                    T::citizen_version().to_string(),
234                )?,
235            ),
236            (Symbol::new("fields"), cx.factory().list(fields)?),
237        ])
238    }
239}
240
241impl<T> ReadConstructor for DerivedCitizenClass<T>
242where
243    T: CitizenRuntime,
244{
245    fn symbol(&self) -> Symbol {
246        T::citizen_symbol()
247    }
248
249    fn args_shape(&self, cx: &mut Cx) -> Result<ShapeRef> {
250        cx.factory().nil()
251    }
252
253    fn construct_read(&self, cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
254        if args.len() != T::citizen_arity() + 1 {
255            return Err(arity_error(
256                T::citizen_symbol(),
257                T::citizen_arity() + 1,
258                args.len(),
259            ));
260        }
261        self.call(cx, Args::new(args))
262    }
263}