Skip to main content

sim_lib_interference_runtime/
citizen.rs

1//! Citizen plumbing shared by the validated interference records.
2
3use sim_citizen::{CitizenField, CitizenRegistry, CitizenRuntime};
4use sim_kernel::{
5    AbiVersion, Cx, Error, Export, Expr, Lib, LibManifest, LibTarget, Linker, Result, Symbol,
6    Value, Version,
7};
8
9use crate::{
10    EmitterDescriptor, MediumDescriptor, PhasorFieldDescriptor, PlaneDescriptor, ProblemDescriptor,
11    ProjectionCertificateDescriptor, ProjectionRequestDescriptor, SamplingCertificateDescriptor,
12    ScalarProjectionDescriptor, StudyDescriptor, StudyEvidenceDescriptor, WorkEstimateDescriptor,
13};
14
15pub(crate) trait RecordCitizenSpec:
16    Clone + core::fmt::Debug + PartialEq + Send + Sync + 'static
17{
18    const FIELDS: &'static [&'static str];
19
20    fn encode_fields(&self, cx: &mut Cx) -> Result<Vec<Expr>>;
21    fn decode_fields(cx: &mut Cx, fields: Vec<Value>) -> Result<Self>;
22    fn example() -> Self;
23    fn validate(&self) -> Result<()>;
24}
25
26macro_rules! impl_record_citizen {
27    ($ty:ty, $symbol:literal, $arity:literal) => {
28        impl ::sim_citizen::Citizen for $ty {
29            fn citizen_symbol() -> ::sim_kernel::Symbol {
30                ::sim_citizen::parse_symbol($symbol)
31            }
32
33            fn citizen_version() -> u32 {
34                1
35            }
36
37            fn citizen_arity() -> usize {
38                <Self as $crate::citizen::RecordCitizenSpec>::FIELDS.len()
39            }
40
41            fn citizen_fields() -> &'static [&'static str] {
42                <Self as $crate::citizen::RecordCitizenSpec>::FIELDS
43            }
44        }
45
46        impl ::sim_kernel::Object for $ty {
47            fn display(&self, cx: &mut ::sim_kernel::Cx) -> ::sim_kernel::Result<String> {
48                let fields = <Self as $crate::citizen::RecordCitizenSpec>::encode_fields(self, cx)?;
49                Ok(format!("#<citizen {}:{}>", $symbol, fields.len()))
50            }
51
52            fn as_any(&self) -> &dyn ::std::any::Any {
53                self
54            }
55        }
56
57        impl ::sim_kernel::ObjectCompat for $ty {
58            fn class(
59                &self,
60                cx: &mut ::sim_kernel::Cx,
61            ) -> ::sim_kernel::Result<::sim_kernel::ClassRef> {
62                let symbol = <Self as ::sim_citizen::Citizen>::citizen_symbol();
63                if let Some(value) = cx.registry().class_by_symbol(&symbol) {
64                    return Ok(value.clone());
65                }
66                ::sim_kernel::Factory::class_stub(cx.factory(), ::sim_kernel::ClassId(0), symbol)
67            }
68
69            fn as_expr(
70                &self,
71                cx: &mut ::sim_kernel::Cx,
72            ) -> ::sim_kernel::Result<::sim_kernel::Expr> {
73                ::sim_citizen::constructor_expr(cx, self)
74            }
75
76            fn as_object_encoder(&self) -> Option<&dyn ::sim_kernel::ObjectEncode> {
77                Some(self)
78            }
79        }
80
81        impl ::sim_kernel::ObjectEncode for $ty {
82            fn object_encoding(
83                &self,
84                cx: &mut ::sim_kernel::Cx,
85            ) -> ::sim_kernel::Result<::sim_kernel::ObjectEncoding> {
86                <Self as $crate::citizen::RecordCitizenSpec>::validate(self)?;
87                let mut args = vec![::sim_kernel::Expr::Symbol(::sim_kernel::Symbol::new("v1"))];
88                args.extend(<Self as $crate::citizen::RecordCitizenSpec>::encode_fields(
89                    self, cx,
90                )?);
91                Ok(::sim_kernel::ObjectEncoding::Constructor {
92                    class: <Self as ::sim_citizen::Citizen>::citizen_symbol(),
93                    args,
94                })
95            }
96        }
97
98        impl ::sim_citizen::CitizenRuntime for $ty {
99            fn citizen_info() -> ::sim_citizen::CitizenInfo {
100                ::sim_citizen::CitizenInfo {
101                    symbol: $symbol,
102                    version: 1,
103                    crate_name: env!("CARGO_PKG_NAME"),
104                    arity: <Self as ::sim_citizen::Citizen>::citizen_arity(),
105                    install: <Self as ::sim_citizen::CitizenRuntime>::install,
106                    conformance: <Self as ::sim_citizen::CitizenRuntime>::conformance,
107                }
108            }
109
110            fn conformance(cx: &mut ::sim_kernel::Cx) -> ::sim_kernel::Result<()> {
111                ::sim_citizen::check_fixture(
112                    cx,
113                    <Self as $crate::citizen::RecordCitizenSpec>::example(),
114                )
115            }
116
117            fn construct_from_values(
118                cx: &mut ::sim_kernel::Cx,
119                args: Vec<::sim_kernel::Value>,
120            ) -> ::sim_kernel::Result<Self> {
121                let expected = <Self as ::sim_citizen::Citizen>::citizen_arity() + 1;
122                if args.len() != expected {
123                    return Err(::sim_citizen::arity_error(
124                        <Self as ::sim_citizen::Citizen>::citizen_symbol(),
125                        expected,
126                        args.len(),
127                    ));
128                }
129                let mut args = args.into_iter();
130                let version = args.next().expect("arity checked");
131                ::sim_citizen::decode_version(
132                    cx,
133                    version,
134                    1,
135                    <Self as ::sim_citizen::Citizen>::citizen_symbol(),
136                )?;
137                let value = <Self as $crate::citizen::RecordCitizenSpec>::decode_fields(
138                    cx,
139                    args.collect(),
140                )?;
141                <Self as $crate::citizen::RecordCitizenSpec>::validate(&value)?;
142                Ok(value)
143            }
144
145            fn example() -> Self {
146                <Self as $crate::citizen::RecordCitizenSpec>::example()
147            }
148        }
149
150        const _: () = {
151            ::sim_citizen::inventory::submit! {
152                ::sim_citizen::CitizenInfo {
153                    symbol: $symbol,
154                    version: 1,
155                    crate_name: env!("CARGO_PKG_NAME"),
156                    arity: $arity,
157                    install: <$ty as ::sim_citizen::CitizenRuntime>::install,
158                    conformance: <$ty as ::sim_citizen::CitizenRuntime>::conformance,
159                }
160            }
161        };
162    };
163}
164
165pub(crate) fn invalid(record: &str, message: impl Into<String>) -> Error {
166    Error::Eval(format!("interference {record}: {}", message.into()))
167}
168
169pub(crate) fn next_field<T>(
170    cx: &mut Cx,
171    fields: &mut impl Iterator<Item = Value>,
172    name: &'static str,
173) -> Result<T>
174where
175    T: CitizenField,
176{
177    T::decode_field_value(
178        cx,
179        fields
180            .next()
181            .ok_or_else(|| invalid("record", format!("missing field {name}")))?,
182        name,
183    )
184}
185
186pub(crate) fn encode_field<T: CitizenField>(value: &T) -> Expr {
187    value.encode_field()
188}
189
190pub(crate) fn encode_record<T>(cx: &mut Cx, value: &T) -> Result<Expr>
191where
192    T: CitizenRuntime,
193{
194    sim_citizen::constructor_expr(cx, value)
195}
196
197pub(crate) fn decode_record<T>(cx: &mut Cx, value: Value, field: &'static str) -> Result<T>
198where
199    T: CitizenRuntime,
200{
201    let expr = sim_citizen::value_to_expr(cx, value, field)?;
202    decode_record_expr(cx, &expr, field)
203}
204
205pub(crate) fn decode_record_expr<T>(cx: &mut Cx, expr: &Expr, field: &'static str) -> Result<T>
206where
207    T: CitizenRuntime,
208{
209    let (class, args) = read_construct_parts(expr, field)?;
210    if class != T::citizen_symbol() {
211        return Err(invalid(
212            field,
213            format!("expected nested {}, found {class}", T::citizen_symbol()),
214        ));
215    }
216    let values = args
217        .iter()
218        .map(|arg| sim_citizen::value_from_expr(cx, arg))
219        .collect::<Result<Vec<_>>>()?;
220    T::construct_from_values(cx, values)
221}
222
223pub(crate) fn encode_records<T>(cx: &mut Cx, values: &[T]) -> Result<Expr>
224where
225    T: CitizenRuntime,
226{
227    values
228        .iter()
229        .map(|value| encode_record(cx, value))
230        .collect::<Result<Vec<_>>>()
231        .map(Expr::List)
232}
233
234pub(crate) fn decode_records<T>(cx: &mut Cx, value: Value, field: &'static str) -> Result<Vec<T>>
235where
236    T: CitizenRuntime,
237{
238    let expr = sim_citizen::value_to_expr(cx, value, field)?;
239    let Expr::List(items) = expr else {
240        return Err(invalid(field, "expected a list of nested citizens"));
241    };
242    items
243        .iter()
244        .map(|item| decode_record_expr(cx, item, field))
245        .collect()
246}
247
248pub(crate) fn read_construct_parts<'a>(
249    expr: &'a Expr,
250    field: &'static str,
251) -> Result<(Symbol, &'a [Expr])> {
252    let Expr::Extension { tag, payload } = expr else {
253        return Err(invalid(field, "expected a read-construct extension"));
254    };
255    if *tag != Symbol::qualified("citizen", "read-construct") {
256        return Err(invalid(
257            field,
258            format!("expected citizen/read-construct, found {tag}"),
259        ));
260    }
261    let Expr::Vector(items) = payload.as_ref() else {
262        return Err(invalid(field, "read-construct payload must be a vector"));
263    };
264    let Some((Expr::Symbol(class), args)) = items.split_first() else {
265        return Err(invalid(
266            field,
267            "read-construct payload must start with a class symbol",
268        ));
269    };
270    Ok((class.clone(), args))
271}
272
273/// Builds the DCE-safe registry containing every interference record Citizen.
274pub fn interference_citizen_registry() -> Result<CitizenRegistry> {
275    let mut registry = CitizenRegistry::new();
276    registry
277        .register::<MediumDescriptor>()?
278        .register::<EmitterDescriptor>()?
279        .register::<ProblemDescriptor>()?
280        .register::<PlaneDescriptor>()?
281        .register::<SamplingCertificateDescriptor>()?
282        .register::<WorkEstimateDescriptor>()?
283        .register::<PhasorFieldDescriptor>()?
284        .register::<StudyEvidenceDescriptor>()?
285        .register::<StudyDescriptor>()?
286        .register::<ProjectionCertificateDescriptor>()?
287        .register::<ProjectionRequestDescriptor>()?
288        .register::<ScalarProjectionDescriptor>()?;
289    Ok(registry)
290}
291
292/// Loadable class and Shape registrations for interference records.
293pub struct InterferenceRecordsLib;
294
295impl Lib for InterferenceRecordsLib {
296    fn manifest(&self) -> LibManifest {
297        let mut exports = RECORD_SYMBOLS
298            .iter()
299            .map(|symbol| Export::Class {
300                symbol: sim_citizen::parse_symbol(symbol),
301                class_id: None,
302            })
303            .collect::<Vec<_>>();
304        exports.extend(
305            crate::shapes::interference_shape_symbols()
306                .into_iter()
307                .map(|symbol| Export::Shape {
308                    symbol,
309                    shape_id: None,
310                }),
311        );
312        LibManifest {
313            id: records_lib_symbol(),
314            version: Version(env!("CARGO_PKG_VERSION").to_owned()),
315            abi: AbiVersion { major: 0, minor: 1 },
316            target: LibTarget::HostRegistered,
317            requires: Vec::new(),
318            capabilities: Vec::new(),
319            exports,
320        }
321    }
322
323    fn load(&self, _cx: &mut sim_kernel::LoadCx, linker: &mut Linker<'_>) -> Result<()> {
324        interference_citizen_registry()?.install_all(linker)?;
325        crate::shapes::register_interference_shapes(linker)
326    }
327}
328
329/// Installs the record classes and Shapes once in `cx`.
330pub fn install_interference_records(cx: &mut Cx) -> Result<()> {
331    if cx.registry().lib(&records_lib_symbol()).is_none() {
332        cx.load_lib(&InterferenceRecordsLib)?;
333    }
334    Ok(())
335}
336
337fn records_lib_symbol() -> Symbol {
338    Symbol::qualified("sim", "interference-records")
339}
340
341const RECORD_SYMBOLS: [&str; 12] = [
342    "interference/Medium",
343    "interference/Emitter",
344    "interference/Problem",
345    "interference/Plane",
346    "interference/SamplingCertificate",
347    "interference/WorkEstimate",
348    "interference/PhasorField",
349    "interference/StudyEvidence",
350    "interference/Study",
351    "interference/ProjectionCertificate",
352    "interference/ProjectionRequest",
353    "interference/Projection",
354];