Skip to main content

sim_lib_stream_core/
read_construct.rs

1//! Read/construct integration for stream metadata.
2//!
3//! The kernel defines the class, object, and read-constructor contracts
4//! (`Class`, `Object`, `ReadConstructor`); this module supplies the concrete
5//! `stream/Metadata` class so stream metadata can be parsed and constructed
6//! through the kernel read pipeline. [`StreamMetadataValue`] is the constructed
7//! object, [`stream_metadata_class_symbol`] names the class, and
8//! [`install_stream_core_classes`] registers it in a context's class registry.
9
10use std::sync::Arc;
11
12use sim_kernel::{
13    Args, CORE_CLASS_CLASS_ID, CORE_FUNCTION_CLASS_ID, Callable, Class, ClassId, ClassRef, Cx,
14    DefaultFactory, Expr, Factory, Linker, Object, ObjectCompat, ObjectEncode, ObjectEncoding,
15    ReadConstructor, ReadConstructorRef, Result, ShapeRef, Symbol, TableRef, Value,
16};
17
18use crate::{
19    BufferPolicy,
20    metadata::{StreamDirection, StreamMedia, StreamMetadata},
21};
22
23const STREAM_METADATA_CLASS_ID: ClassId = ClassId(6200);
24
25/// Runtime object wrapping [`StreamMetadata`] as a constructed `stream/Metadata`
26/// value.
27///
28/// This is the object the read/construct path produces; it exposes the metadata
29/// as an expression, table, and constructor encoding.
30#[derive(Clone)]
31pub struct StreamMetadataValue {
32    metadata: StreamMetadata,
33}
34
35impl StreamMetadataValue {
36    /// Wraps stream metadata as a constructed value.
37    pub fn new(metadata: StreamMetadata) -> Self {
38        Self { metadata }
39    }
40
41    /// Returns the wrapped stream metadata.
42    pub fn metadata(&self) -> &StreamMetadata {
43        &self.metadata
44    }
45}
46
47impl Object for StreamMetadataValue {
48    fn display(&self, _cx: &mut Cx) -> Result<String> {
49        Ok(format!("#<stream-metadata {}>", self.metadata.id()))
50    }
51
52    fn as_any(&self) -> &dyn std::any::Any {
53        self
54    }
55}
56
57impl ObjectCompat for StreamMetadataValue {
58    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
59        class_value_or_stub(cx)
60    }
61
62    fn as_expr(&self, _cx: &mut Cx) -> Result<Expr> {
63        Ok(Expr::Call {
64            operator: Box::new(Expr::Symbol(stream_metadata_class_symbol())),
65            args: self.metadata.to_constructor_args(),
66        })
67    }
68
69    fn as_table(&self, cx: &mut Cx) -> Result<Value> {
70        let Expr::Map(entries) = self.metadata.table_expr() else {
71            unreachable!("metadata table_expr returns a map");
72        };
73        cx.factory().table(
74            entries
75                .into_iter()
76                .map(|(key, value)| match key {
77                    Expr::Symbol(symbol) => Ok((symbol, cx.factory().expr(value)?)),
78                    _ => unreachable!("metadata table keys are symbols"),
79                })
80                .collect::<Result<Vec<_>>>()?,
81        )
82    }
83
84    fn as_object_encoder(&self) -> Option<&dyn ObjectEncode> {
85        Some(self)
86    }
87}
88
89impl ObjectEncode for StreamMetadataValue {
90    fn object_encoding(&self, _cx: &mut Cx) -> Result<ObjectEncoding> {
91        Ok(ObjectEncoding::Constructor {
92            class: stream_metadata_class_symbol(),
93            args: self.metadata.to_constructor_args(),
94        })
95    }
96}
97
98impl sim_citizen::Citizen for StreamMetadataValue {
99    fn citizen_symbol() -> Symbol {
100        stream_metadata_class_symbol()
101    }
102
103    fn citizen_version() -> u32 {
104        0
105    }
106
107    fn citizen_arity() -> usize {
108        5
109    }
110
111    fn citizen_fields() -> &'static [&'static str] {
112        &["id", "media", "direction", "clock", "buffer"]
113    }
114}
115
116/// Returns the qualified `stream/Metadata` class symbol.
117pub fn stream_metadata_class_symbol() -> Symbol {
118    Symbol::qualified("stream", "Metadata")
119}
120
121/// Registers the stream-core classes in a context's registry.
122///
123/// Installs the `stream/Metadata` class, which carries the read constructor used
124/// by the kernel read pipeline. Idempotent: returns immediately if the class is
125/// already registered.
126pub fn install_stream_core_classes(cx: &mut Cx) -> Result<()> {
127    if cx
128        .registry()
129        .class_by_symbol(&stream_metadata_class_symbol())
130        .is_some()
131    {
132        return Ok(());
133    }
134    let class = cx.factory().opaque(Arc::new(StreamMetadataClass))?;
135    cx.registry_mut()
136        .register_class_value(stream_metadata_class_symbol(), class)?;
137    Ok(())
138}
139
140#[derive(Clone)]
141struct StreamMetadataClass;
142
143impl Object for StreamMetadataClass {
144    fn display(&self, _cx: &mut Cx) -> Result<String> {
145        Ok("#<class stream/Metadata>".to_owned())
146    }
147
148    fn as_any(&self) -> &dyn std::any::Any {
149        self
150    }
151}
152
153impl ObjectCompat for StreamMetadataClass {
154    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
155        let symbol = Symbol::qualified("core", "Class");
156        if let Some(value) = cx.registry().class_by_symbol(&symbol) {
157            return Ok(value.clone());
158        }
159        cx.factory().class_stub(CORE_CLASS_CLASS_ID, symbol)
160    }
161
162    fn as_expr(&self, _cx: &mut Cx) -> Result<Expr> {
163        Ok(Expr::Symbol(stream_metadata_class_symbol()))
164    }
165
166    fn as_callable(&self) -> Option<&dyn Callable> {
167        Some(self)
168    }
169
170    fn as_class(&self) -> Option<&dyn Class> {
171        Some(self)
172    }
173}
174
175impl Callable for StreamMetadataClass {
176    fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
177        construct_stream_metadata_value(cx, args.into_vec())
178    }
179}
180
181impl Class for StreamMetadataClass {
182    fn id(&self) -> ClassId {
183        STREAM_METADATA_CLASS_ID
184    }
185
186    fn symbol(&self) -> Symbol {
187        stream_metadata_class_symbol()
188    }
189
190    fn constructor_shape(&self, cx: &mut Cx) -> Result<ShapeRef> {
191        cx.factory().nil()
192    }
193
194    fn instance_shape(&self, cx: &mut Cx) -> Result<ShapeRef> {
195        cx.factory().nil()
196    }
197
198    fn read_constructor(&self, _cx: &mut Cx) -> Result<Option<ReadConstructorRef>> {
199        Ok(Some(
200            DefaultFactory.opaque(Arc::new(StreamMetadataReadConstructor))?,
201        ))
202    }
203
204    fn members(&self, cx: &mut Cx) -> Result<TableRef> {
205        cx.factory().table(Vec::new())
206    }
207}
208
209#[derive(Clone)]
210struct StreamMetadataReadConstructor;
211
212impl Object for StreamMetadataReadConstructor {
213    fn display(&self, _cx: &mut Cx) -> Result<String> {
214        Ok("#<read-constructor stream/Metadata>".to_owned())
215    }
216
217    fn as_any(&self) -> &dyn std::any::Any {
218        self
219    }
220}
221
222impl ObjectCompat for StreamMetadataReadConstructor {
223    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
224        let symbol = Symbol::qualified("core", "Function");
225        if let Some(value) = cx.registry().class_by_symbol(&symbol) {
226            return Ok(value.clone());
227        }
228        cx.factory().class_stub(CORE_FUNCTION_CLASS_ID, symbol)
229    }
230
231    fn as_read_constructor(&self) -> Option<&dyn ReadConstructor> {
232        Some(self)
233    }
234}
235
236impl ReadConstructor for StreamMetadataReadConstructor {
237    fn symbol(&self) -> Symbol {
238        stream_metadata_class_symbol()
239    }
240
241    fn args_shape(&self, cx: &mut Cx) -> Result<ShapeRef> {
242        cx.factory().nil()
243    }
244
245    fn construct_read(&self, cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
246        construct_stream_metadata_value(cx, args)
247    }
248}
249
250fn construct_stream_metadata_value(cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
251    let mut exprs = Vec::with_capacity(args.len());
252    for value in args {
253        exprs.push(value.object().as_expr(cx)?);
254    }
255    cx.factory().opaque(Arc::new(StreamMetadataValue::new(
256        StreamMetadata::from_constructor_args(exprs)?,
257    )))
258}
259
260fn register_stream_metadata_class(linker: &mut Linker<'_>) -> Result<()> {
261    let class = DefaultFactory
262        .opaque(Arc::new(StreamMetadataClass))
263        .expect("stream metadata class should be boxable");
264    linker.class_value(stream_metadata_class_symbol(), class)?;
265    Ok(())
266}
267
268fn install_stream_metadata_citizen(linker: &mut Linker<'_>) -> Result<()> {
269    register_stream_metadata_class(linker)
270}
271
272fn conformance_stream_metadata_citizen(cx: &mut Cx) -> Result<()> {
273    let metadata = StreamMetadata::new(
274        Symbol::qualified("stream-citizen", "metadata"),
275        StreamMedia::Data,
276        StreamDirection::Source,
277        Symbol::qualified("stream-clock", "logical"),
278        BufferPolicy::bounded(8)?,
279    );
280    let value = cx
281        .factory()
282        .opaque(Arc::new(StreamMetadataValue::new(metadata)))?;
283    sim_citizen::check_value_fixture(cx, value)
284}
285
286sim_citizen::inventory::submit! {
287    sim_citizen::CitizenInfo {
288        symbol: "stream/Metadata",
289        version: 0,
290        crate_name: env!("CARGO_PKG_NAME"),
291        arity: 5,
292        install: install_stream_metadata_citizen,
293        conformance: conformance_stream_metadata_citizen,
294    }
295}
296
297fn class_value_or_stub(cx: &mut Cx) -> Result<Value> {
298    if let Some(value) = cx
299        .registry()
300        .class_by_symbol(&stream_metadata_class_symbol())
301    {
302        return Ok(value.clone());
303    }
304    cx.factory()
305        .class_stub(STREAM_METADATA_CLASS_ID, stream_metadata_class_symbol())
306}