Skip to main content

sim_lib_stream_prelude/
function.rs

1use std::sync::Arc;
2
3mod helpers;
4
5use sim_kernel::{
6    AbiVersion, Args, Callable, ClassRef, Cx, Error, Export, Expr, Lib, LibManifest, LibTarget,
7    Linker, Object, ObjectCompat, RawArgs, Result, Symbol, Value, Version,
8};
9use sim_lib_stream_combinators::{filter_data_kind, window_by_count};
10use sim_lib_stream_core::{
11    StreamPacket, install_stream_core_classes, install_stream_core_shapes_lib,
12    stream_cancel_symbol, stream_metadata_symbol, stream_next_symbol, stream_run_symbol,
13    stream_stats_symbol,
14};
15
16use crate::{
17    cap::{
18        stream_open_capability, stream_read_capability, stream_transform_capability,
19        stream_write_capability,
20    },
21    card::{stats_value, stream_card},
22    handle::{StageHandle, StreamHandle},
23    live::StreamRuntime,
24    live_control::{
25        cancel_older_than_fn, cell_fn, cell_set_fn, cell_value_fn, describe_fn,
26        explain_diagnostic_fn, graph_lisp_fn, list_fn, reroute_fn,
27    },
28    spec::{memory_specs_value, open_spec_from_expr},
29};
30
31use helpers::{
32    collect_stream_to_handle, data_expr, ensure_done, eval_value, handle_arg, handle_stream,
33    handle_value_from_items, map_data_payload, run_report_value, symbol_arg, usize_arg,
34};
35
36const STREAM_PRELUDE_LIB_ID: &str = "stream-prelude";
37
38/// Host-registered library that installs the STREAM 6 prelude functions.
39///
40/// [`StreamPreludeLib`] reports a [`LibManifest`] exporting the memory-spec
41/// catalog value plus every capability-gated stream function, and on load it
42/// links those functions and the `stream/memory-specs` value into the runtime.
43/// Prefer [`install_stream_prelude_lib`] over loading this type directly; it
44/// also installs the underlying stream-core classes and shapes.
45pub struct StreamPreludeLib;
46
47impl Lib for StreamPreludeLib {
48    fn manifest(&self) -> LibManifest {
49        LibManifest {
50            id: manifest_name(),
51            version: Version(env!("CARGO_PKG_VERSION").to_owned()),
52            abi: AbiVersion { major: 0, minor: 1 },
53            target: LibTarget::HostRegistered,
54            requires: Vec::new(),
55            capabilities: Vec::new(),
56            exports: stream_prelude_exports(),
57        }
58    }
59
60    fn load(&self, cx: &mut sim_kernel::LoadCx, linker: &mut Linker<'_>) -> Result<()> {
61        let runtime = Arc::new(StreamRuntime::default());
62        for (symbol, implementation) in function_table() {
63            linker.function_value(
64                symbol.clone(),
65                cx.factory().opaque(Arc::new(StreamFunction {
66                    symbol,
67                    implementation,
68                    runtime: Arc::clone(&runtime),
69                }))?,
70            )?;
71        }
72        linker.value(stream_memory_specs_symbol(), memory_specs_value(cx)?)?;
73        Ok(())
74    }
75}
76
77/// Installs the stream prelude and its prerequisites into `cx`.
78///
79/// This installs the stream-core classes and shapes, then loads
80/// [`StreamPreludeLib`] exactly once. After it returns, the `stream/*`
81/// functions and the `stream/memory-specs` catalog are available to evaluated
82/// code.
83pub fn install_stream_prelude_lib(cx: &mut Cx) -> Result<()> {
84    install_stream_core_classes(cx)?;
85    install_stream_core_shapes_lib(cx)?;
86    sim_lib_core::install_once(cx, &StreamPreludeLib).map(|_| ())
87}
88
89/// Returns the export records advertised by the prelude library.
90///
91/// The list contains the `stream/memory-specs` value export followed by one
92/// function export per entry in the prelude function table.
93pub fn stream_prelude_exports() -> Vec<Export> {
94    let mut exports = vec![Export::Value {
95        symbol: stream_memory_specs_symbol(),
96    }];
97    exports.extend(
98        function_table()
99            .into_iter()
100            .map(|(symbol, _)| Export::Function {
101                symbol,
102                function_id: None,
103            }),
104    );
105    exports
106}
107
108/// Returns the manifest id symbol of the prelude library (`stream-prelude`).
109pub fn manifest_name() -> Symbol {
110    Symbol::new(STREAM_PRELUDE_LIB_ID)
111}
112
113/// Returns the `stream/open` function symbol.
114///
115/// `stream/open` builds a memory stream handle from a memory-spec table.
116///
117/// # Examples
118///
119/// ```
120/// use sim_lib_stream_prelude::stream_open_symbol;
121///
122/// assert_eq!(stream_open_symbol().as_qualified_str(), "stream/open");
123/// ```
124pub fn stream_open_symbol() -> Symbol {
125    Symbol::qualified("stream", "open")
126}
127
128/// Returns the `stream/write!` function symbol.
129///
130/// `stream/write!` pushes a single packet into a sink handle.
131pub fn stream_write_symbol() -> Symbol {
132    Symbol::qualified("stream", "write!")
133}
134
135/// Returns the `stream/pipe` function symbol.
136///
137/// `stream/pipe` connects a source handle to optional stages and at most one
138/// sink, producing a pipeline handle.
139pub fn stream_pipe_symbol() -> Symbol {
140    Symbol::qualified("stream", "pipe")
141}
142
143/// Returns the `stream/card` function symbol.
144///
145/// `stream/card` renders a browseable Card for a stream handle.
146pub fn stream_card_symbol() -> Symbol {
147    Symbol::qualified("stream", "card")
148}
149
150/// Returns the `stream/sink-packets` function symbol.
151///
152/// `stream/sink-packets` reads back the packets accumulated by a sink handle.
153pub fn stream_sink_packets_symbol() -> Symbol {
154    Symbol::qualified("stream", "sink-packets")
155}
156
157/// Returns the `stream/memory-specs` value symbol.
158///
159/// `stream/memory-specs` names the catalog value describing every supported
160/// memory source/sink spec and its required fields.
161pub fn stream_memory_specs_symbol() -> Symbol {
162    Symbol::qualified("stream", "memory-specs")
163}
164
165fn stream_identity_symbol() -> Symbol {
166    Symbol::qualified("stream", "identity")
167}
168
169fn stream_list_symbol() -> Symbol {
170    Symbol::qualified("stream", "list")
171}
172
173fn stream_describe_symbol() -> Symbol {
174    Symbol::qualified("stream", "describe")
175}
176
177fn stream_graph_lisp_symbol() -> Symbol {
178    Symbol::qualified("stream", "graph-lisp")
179}
180
181fn stream_explain_diagnostic_symbol() -> Symbol {
182    Symbol::qualified("stream", "explain-diagnostic")
183}
184
185fn stream_cell_symbol() -> Symbol {
186    Symbol::qualified("stream", "cell")
187}
188
189fn stream_cell_value_symbol() -> Symbol {
190    Symbol::qualified("stream", "cell-value")
191}
192
193fn stream_cell_set_symbol() -> Symbol {
194    Symbol::qualified("stream", "cell-set!")
195}
196
197fn stream_reroute_symbol() -> Symbol {
198    Symbol::qualified("stream", "reroute!")
199}
200
201fn stream_cancel_older_than_symbol() -> Symbol {
202    Symbol::qualified("stream", "cancel-older-than!")
203}
204
205fn stream_filter_kind_symbol() -> Symbol {
206    Symbol::qualified("stream", "filter-kind")
207}
208
209fn stream_filter_shape_symbol() -> Symbol {
210    Symbol::qualified("stream", "filter-shape")
211}
212
213fn stream_map_expr_symbol() -> Symbol {
214    Symbol::qualified("stream", "map-expr")
215}
216
217fn stream_window_symbol() -> Symbol {
218    Symbol::qualified("stream", "window")
219}
220
221fn function_table() -> Vec<(Symbol, StreamFn)> {
222    vec![
223        (stream_open_symbol(), open_fn),
224        (stream_next_symbol(), next_fn),
225        (stream_write_symbol(), write_fn),
226        (stream_pipe_symbol(), pipe_fn),
227        (stream_identity_symbol(), identity_fn),
228        (stream_run_symbol(), run_fn),
229        (stream_cancel_symbol(), cancel_fn),
230        (stream_stats_symbol(), stats_fn),
231        (stream_metadata_symbol(), metadata_fn),
232        (stream_card_symbol(), card_fn),
233        (stream_sink_packets_symbol(), sink_packets_fn),
234        (stream_list_symbol(), list_fn),
235        (stream_describe_symbol(), describe_fn),
236        (stream_graph_lisp_symbol(), graph_lisp_fn),
237        (stream_explain_diagnostic_symbol(), explain_diagnostic_fn),
238        (stream_cell_symbol(), cell_fn),
239        (stream_cell_value_symbol(), cell_value_fn),
240        (stream_cell_set_symbol(), cell_set_fn),
241        (stream_reroute_symbol(), reroute_fn),
242        (stream_cancel_older_than_symbol(), cancel_older_than_fn),
243        (stream_filter_kind_symbol(), filter_kind_fn),
244        (stream_filter_shape_symbol(), filter_shape_fn),
245        (stream_map_expr_symbol(), map_expr_fn),
246        (stream_window_symbol(), window_fn),
247    ]
248}
249
250type StreamFn = fn(&StreamRuntime, &mut Cx, &[Expr]) -> Result<Value>;
251
252struct StreamFunction {
253    symbol: Symbol,
254    implementation: StreamFn,
255    runtime: Arc<StreamRuntime>,
256}
257
258impl Object for StreamFunction {
259    fn display(&self, _cx: &mut Cx) -> Result<String> {
260        Ok(format!("#<function {}>", self.symbol))
261    }
262
263    fn as_any(&self) -> &dyn std::any::Any {
264        self
265    }
266}
267
268impl ObjectCompat for StreamFunction {
269    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
270        cx.factory().class_stub(
271            sim_kernel::CORE_FUNCTION_CLASS_ID,
272            Symbol::qualified("core", "Function"),
273        )
274    }
275
276    fn as_callable(&self) -> Option<&dyn Callable> {
277        Some(self)
278    }
279}
280
281impl Callable for StreamFunction {
282    fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
283        let exprs = args
284            .into_vec()
285            .into_iter()
286            .map(|value| value.object().as_expr(cx))
287            .collect::<Result<Vec<_>>>()?;
288        (self.implementation)(&self.runtime, cx, &exprs)
289    }
290
291    fn call_exprs(&self, cx: &mut Cx, args: RawArgs) -> Result<Value> {
292        (self.implementation)(&self.runtime, cx, args.exprs())
293    }
294}
295
296fn open_fn(runtime: &StreamRuntime, cx: &mut Cx, args: &[Expr]) -> Result<Value> {
297    cx.require(&stream_open_capability())?;
298    let [spec] = args else {
299        return Err(Error::Eval(
300            "stream/open expects one memory spec".to_owned(),
301        ));
302    };
303    let spec = open_spec_from_expr(data_expr(cx, spec)?)?;
304    let handle = spec.into_handle(cx)?;
305    runtime.register_stream(handle.clone())?;
306    cx.factory().opaque(Arc::new(handle))
307}
308
309fn next_fn(_runtime: &StreamRuntime, cx: &mut Cx, args: &[Expr]) -> Result<Value> {
310    cx.require(&stream_read_capability())?;
311    let [stream] = args else {
312        return Err(Error::Eval(
313            "stream/next! expects one stream handle".to_owned(),
314        ));
315    };
316    match handle_arg(cx, stream)?.next_packet()? {
317        Some(item) => cx.factory().expr(item.packet().to_expr()),
318        None => cx.factory().nil(),
319    }
320}
321
322fn write_fn(_runtime: &StreamRuntime, cx: &mut Cx, args: &[Expr]) -> Result<Value> {
323    cx.require(&stream_write_capability())?;
324    let [sink, packet] = args else {
325        return Err(Error::Eval(
326            "stream/write! expects a sink handle and packet".to_owned(),
327        ));
328    };
329    let sink = handle_arg(cx, sink)?;
330    let packet = StreamPacket::try_from(data_expr(cx, packet)?)?;
331    sink.write_packet(packet)?;
332    cx.factory().bool(true)
333}
334
335fn pipe_fn(_runtime: &StreamRuntime, cx: &mut Cx, args: &[Expr]) -> Result<Value> {
336    let [source, rest @ ..] = args else {
337        return Err(Error::Eval(
338            "stream/pipe expects a source handle".to_owned(),
339        ));
340    };
341    let source = handle_arg(cx, source)?;
342    let mut sink = None;
343    for expr in rest {
344        let value = eval_value(cx, expr)?;
345        if let Some(stage) = value.object().downcast_ref::<StageHandle>() {
346            if !stage.is_identity() {
347                return Err(Error::Eval("unsupported stream stage".to_owned()));
348            }
349            continue;
350        }
351        let handle = value
352            .object()
353            .downcast_ref::<StreamHandle>()
354            .cloned()
355            .ok_or(Error::TypeMismatch {
356                expected: "stream stage or sink handle",
357                found: "non-stream-pipeline-argument",
358            })?;
359        if sink.replace(handle).is_some() {
360            return Err(Error::Eval(
361                "stream/pipe accepts at most one sink handle in STR6.8".to_owned(),
362            ));
363        }
364    }
365    cx.factory()
366        .opaque(Arc::new(StreamHandle::pipeline(source, sink)?))
367}
368
369fn identity_fn(_runtime: &StreamRuntime, cx: &mut Cx, args: &[Expr]) -> Result<Value> {
370    if !args.is_empty() {
371        return Err(Error::Eval(
372            "stream/identity expects no arguments".to_owned(),
373        ));
374    }
375    cx.factory().opaque(Arc::new(StageHandle::identity()))
376}
377
378fn run_fn(_runtime: &StreamRuntime, cx: &mut Cx, args: &[Expr]) -> Result<Value> {
379    let [stream] = args else {
380        return Err(Error::Eval(
381            "stream/run! expects one stream handle".to_owned(),
382        ));
383    };
384    let stream = handle_arg(cx, stream)?;
385    cx.require(&stream_read_capability())?;
386    if stream.is_pipeline_with_sink() {
387        cx.require(&stream_write_capability())?;
388    }
389    run_report_value(cx, stream.run()?)
390}
391
392fn cancel_fn(_runtime: &StreamRuntime, cx: &mut Cx, args: &[Expr]) -> Result<Value> {
393    let [stream] = args else {
394        return Err(Error::Eval(
395            "stream/cancel! expects one stream handle".to_owned(),
396        ));
397    };
398    handle_arg(cx, stream)?.cancel()?;
399    cx.factory().nil()
400}
401
402fn stats_fn(_runtime: &StreamRuntime, cx: &mut Cx, args: &[Expr]) -> Result<Value> {
403    let [stream] = args else {
404        return Err(Error::Eval(
405            "stream/stats expects one stream handle".to_owned(),
406        ));
407    };
408    let handle = handle_arg(cx, stream)?;
409    let stats = handle.stats()?;
410    stats_value(cx, &stats)
411}
412
413fn metadata_fn(_runtime: &StreamRuntime, cx: &mut Cx, args: &[Expr]) -> Result<Value> {
414    let [stream] = args else {
415        return Err(Error::Eval(
416            "stream/metadata expects one stream handle".to_owned(),
417        ));
418    };
419    let handle = handle_arg(cx, stream)?;
420    cx.factory().expr(handle.metadata().table_expr())
421}
422
423fn card_fn(_runtime: &StreamRuntime, cx: &mut Cx, args: &[Expr]) -> Result<Value> {
424    let [stream] = args else {
425        return Err(Error::Eval(
426            "stream/card expects one stream handle".to_owned(),
427        ));
428    };
429    let handle = handle_arg(cx, stream)?;
430    stream_card(cx, &handle)
431}
432
433fn sink_packets_fn(_runtime: &StreamRuntime, cx: &mut Cx, args: &[Expr]) -> Result<Value> {
434    cx.require(&stream_read_capability())?;
435    let [sink] = args else {
436        return Err(Error::Eval(
437            "stream/sink-packets expects one sink handle".to_owned(),
438        ));
439    };
440    let packets = handle_arg(cx, sink)?.sink_packets()?;
441    cx.factory().list(
442        packets
443            .into_iter()
444            .map(|packet| cx.factory().expr(packet.to_expr()))
445            .collect::<Result<Vec<_>>>()?,
446    )
447}
448
449fn filter_kind_fn(_runtime: &StreamRuntime, cx: &mut Cx, args: &[Expr]) -> Result<Value> {
450    cx.require(&stream_read_capability())?;
451    let [stream, kind] = args else {
452        return Err(Error::Eval(
453            "stream/filter-kind expects a stream handle and data kind".to_owned(),
454        ));
455    };
456    let source = handle_arg(cx, stream)?;
457    let kind = symbol_arg(cx, kind)?;
458    let transformed = filter_data_kind(handle_stream(source), kind);
459    collect_stream_to_handle(cx, transformed)
460}
461
462fn filter_shape_fn(_runtime: &StreamRuntime, cx: &mut Cx, args: &[Expr]) -> Result<Value> {
463    cx.require(&stream_read_capability())?;
464    cx.require(&stream_transform_capability())?;
465    let [stream, shape] = args else {
466        return Err(Error::Eval(
467            "stream/filter-shape expects a stream handle and shape".to_owned(),
468        ));
469    };
470    let source = handle_arg(cx, stream)?;
471    let shape = eval_value(cx, shape)?;
472    if shape.object().as_shape().is_none() {
473        return Err(Error::TypeMismatch {
474            expected: "shape",
475            found: "non-shape",
476        });
477    }
478    let metadata = source.metadata().clone();
479    let mut items = Vec::new();
480    while let Some(item) = source.next_packet()? {
481        let StreamPacket::Data(packet) = item.packet() else {
482            continue;
483        };
484        let shape_ref = shape.object().as_shape().ok_or(Error::TypeMismatch {
485            expected: "shape",
486            found: "non-shape",
487        })?;
488        if shape_ref.check_expr(cx, &packet.payload)?.accepted {
489            items.push(item);
490        }
491    }
492    ensure_done(&source, "stream/filter-shape")?;
493    handle_value_from_items(cx, metadata, items)
494}
495
496fn map_expr_fn(_runtime: &StreamRuntime, cx: &mut Cx, args: &[Expr]) -> Result<Value> {
497    cx.require(&stream_read_capability())?;
498    cx.require(&stream_transform_capability())?;
499    let [stream, mapper] = args else {
500        return Err(Error::Eval(
501            "stream/map-expr expects a stream handle and callable".to_owned(),
502        ));
503    };
504    let source = handle_arg(cx, stream)?;
505    let mapper = eval_value(cx, mapper)?;
506    if mapper.object().as_callable().is_none() {
507        return Err(Error::TypeMismatch {
508            expected: "callable",
509            found: "non-callable",
510        });
511    }
512    let metadata = source.metadata().clone();
513    let mut items = Vec::new();
514    while let Some(item) = source.next_packet()? {
515        items.push(map_data_payload(cx, item, mapper.clone())?);
516    }
517    ensure_done(&source, "stream/map-expr")?;
518    handle_value_from_items(cx, metadata, items)
519}
520
521fn window_fn(_runtime: &StreamRuntime, cx: &mut Cx, args: &[Expr]) -> Result<Value> {
522    cx.require(&stream_read_capability())?;
523    let [stream, count] = args else {
524        return Err(Error::Eval(
525            "stream/window expects a stream handle and count".to_owned(),
526        ));
527    };
528    let count = usize_arg(cx, count)?;
529    if count == 0 {
530        return Err(Error::Eval(
531            "stream/window count must be greater than zero".to_owned(),
532        ));
533    }
534    let source = handle_arg(cx, stream)?;
535    let transformed = window_by_count(handle_stream(source), count);
536    collect_stream_to_handle(cx, transformed)
537}