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::{CitizenInfo, 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    /// Returns the static registry row for this citizen type.
67    ///
68    /// The derive emits this metadata independently of the inventory row, so a
69    /// crate can register its citizens explicitly with
70    /// [`CitizenRegistry::register`](crate::CitizenRegistry::register) in
71    /// builds where link-time constructor collection is unsuitable.
72    fn citizen_info() -> CitizenInfo;
73    /// Installs this citizen's class into a kernel linker.
74    ///
75    /// The default implementation backs the generated registry row without
76    /// reserving an inherent helper name on the user's type.
77    fn install(linker: &mut sim_kernel::Linker<'_>) -> Result<()>
78    where
79        Self: Sized,
80    {
81        install_derived::<Self>(linker)
82    }
83    /// Runs this citizen's conformance fixture set.
84    fn conformance(cx: &mut Cx) -> Result<()>;
85    /// Builds the citizen from decoded constructor argument values.
86    fn construct_from_values(cx: &mut Cx, args: Vec<Value>) -> Result<Self>;
87    /// Returns the canonical example value used as a conformance fixture.
88    fn example() -> Self;
89}
90
91/// Installs a derived citizen `T` as a class value in `linker`.
92///
93/// Registers a class backing `T`'s callable, class, and read-constructor
94/// contracts and binds it under `T::citizen_symbol`. This is the call a
95/// citizen's generated [`InstallFn`](crate::InstallFn) makes.
96pub fn install_derived<T>(linker: &mut sim_kernel::Linker<'_>) -> Result<()>
97where
98    T: CitizenRuntime,
99{
100    let class = Arc::new(DerivedCitizenClass::<T>::new());
101    let id = linker.class_value(
102        T::citizen_symbol(),
103        DefaultFactory
104            .opaque(class.clone())
105            .expect("citizen class should be boxable"),
106    )?;
107    class.set_id(id);
108    Ok(())
109}
110
111/// Encodes a citizen value as its read-construct `Expr` extension form.
112///
113/// Maps the value's [`ObjectEncoding`] to the matching `Expr::Extension`: a
114/// constructor encoding becomes a `citizen/read-construct` vector, tagged data
115/// becomes a tagged map, and an opaque encoding becomes a tagged stable id.
116pub fn constructor_expr<T>(cx: &mut Cx, value: &T) -> Result<Expr>
117where
118    T: Citizen + ObjectEncode,
119{
120    match value.object_encoding(cx)? {
121        ObjectEncoding::Constructor { class, args } => Ok(Expr::Extension {
122            tag: Symbol::qualified("citizen", "read-construct"),
123            payload: Box::new(Expr::Vector(
124                std::iter::once(Expr::Symbol(class)).chain(args).collect(),
125            )),
126        }),
127        ObjectEncoding::TaggedData { tag, fields } => Ok(Expr::Extension {
128            tag,
129            payload: Box::new(Expr::Map(
130                fields
131                    .into_iter()
132                    .map(|(key, value)| (Expr::Symbol(key), value))
133                    .collect(),
134            )),
135        }),
136        ObjectEncoding::Opaque { class, stable_id } => Ok(Expr::Extension {
137            tag: class,
138            payload: Box::new(Expr::String(stable_id)),
139        }),
140    }
141}
142
143pub struct DerivedCitizenClass<T> {
144    id: AtomicU32,
145    marker: PhantomData<T>,
146}
147
148impl<T> DerivedCitizenClass<T> {
149    fn new() -> Self {
150        Self {
151            id: AtomicU32::new(0),
152            marker: PhantomData,
153        }
154    }
155
156    fn set_id(&self, id: ClassId) {
157        self.id.store(id.0, Ordering::Relaxed);
158    }
159}
160
161impl<T> Object for DerivedCitizenClass<T>
162where
163    T: CitizenRuntime,
164{
165    fn display(&self, _cx: &mut Cx) -> Result<String> {
166        Ok(format!("#<class {}>", T::citizen_symbol()))
167    }
168
169    fn as_any(&self) -> &dyn std::any::Any {
170        self
171    }
172}
173
174impl<T> sim_kernel::ObjectCompat for DerivedCitizenClass<T>
175where
176    T: CitizenRuntime,
177{
178    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
179        if let Some(value) = cx
180            .registry()
181            .class_by_symbol(&Symbol::qualified("core", "Class"))
182        {
183            return Ok(value.clone());
184        }
185        cx.factory().class_stub(
186            sim_kernel::CORE_CLASS_CLASS_ID,
187            Symbol::qualified("core", "Class"),
188        )
189    }
190
191    fn as_expr(&self, _cx: &mut Cx) -> Result<Expr> {
192        Ok(Expr::Symbol(T::citizen_symbol()))
193    }
194
195    fn as_callable(&self) -> Option<&dyn Callable> {
196        Some(self)
197    }
198
199    fn as_class(&self) -> Option<&dyn Class> {
200        Some(self)
201    }
202
203    fn as_read_constructor(&self) -> Option<&dyn ReadConstructor> {
204        Some(self)
205    }
206}
207
208impl<T> Callable for DerivedCitizenClass<T>
209where
210    T: CitizenRuntime,
211{
212    fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
213        let value = T::construct_from_values(cx, args.into_vec())?;
214        cx.factory().opaque(Arc::new(value))
215    }
216}
217
218impl<T> Class for DerivedCitizenClass<T>
219where
220    T: CitizenRuntime,
221{
222    fn id(&self) -> ClassId {
223        ClassId(self.id.load(Ordering::Relaxed))
224    }
225
226    fn symbol(&self) -> Symbol {
227        T::citizen_symbol()
228    }
229
230    fn constructor_shape(&self, cx: &mut Cx) -> Result<ShapeRef> {
231        any_shape(cx)
232    }
233
234    fn instance_shape(&self, cx: &mut Cx) -> Result<ShapeRef> {
235        any_shape(cx)
236    }
237
238    fn read_constructor(&self, cx: &mut Cx) -> Result<Option<ReadConstructorRef>> {
239        Ok(cx.registry().class_by_symbol(&T::citizen_symbol()).cloned())
240    }
241
242    fn members(&self, cx: &mut Cx) -> Result<TableRef> {
243        citizen_metadata_table(
244            cx,
245            T::citizen_version(),
246            T::citizen_arity(),
247            T::citizen_fields(),
248        )
249    }
250}
251
252impl<T> ReadConstructor for DerivedCitizenClass<T>
253where
254    T: CitizenRuntime,
255{
256    fn symbol(&self) -> Symbol {
257        T::citizen_symbol()
258    }
259
260    fn args_shape(&self, cx: &mut Cx) -> Result<ShapeRef> {
261        any_shape(cx)
262    }
263
264    fn construct_read(&self, cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
265        if args.len() != T::citizen_arity() + 1 {
266            return Err(arity_error(
267                T::citizen_symbol(),
268                T::citizen_arity() + 1,
269                args.len(),
270            ));
271        }
272        self.call(cx, Args::new(args))
273    }
274}
275
276fn any_shape(cx: &mut Cx) -> Result<ShapeRef> {
277    if let Some(shape) = cx
278        .registry()
279        .shape_by_symbol(&Symbol::qualified("core", "Any"))
280    {
281        Ok(shape.clone())
282    } else {
283        cx.factory().nil()
284    }
285}
286
287fn citizen_metadata_table(
288    cx: &mut Cx,
289    version: u32,
290    arity: usize,
291    fields: &[&str],
292) -> Result<TableRef> {
293    let fields = fields
294        .iter()
295        .map(|field| cx.factory().symbol(Symbol::new((*field).to_owned())))
296        .collect::<Result<Vec<_>>>()?;
297    cx.factory().table(vec![
298        (
299            Symbol::new("version"),
300            cx.factory()
301                .number_literal(parse_symbol("citizen/int"), version.to_string())?,
302        ),
303        (
304            Symbol::new("arity"),
305            cx.factory()
306                .number_literal(parse_symbol("citizen/int"), arity.to_string())?,
307        ),
308        (Symbol::new("fields"), cx.factory().list(fields)?),
309    ])
310}