feat: Add SharedValueSeries and PipelineNode

Introduces `SharedValueSeries` for read-only access to generic `Value`
buffers, and `PipelineNode` which combines an `ObservableStream` with a
`Series` view. This is a foundational step for implementing the `pipe`
operator.
This commit is contained in:
Michael Schimmel
2026-03-01 23:12:09 +01:00
parent 2579e2b1fd
commit b177aa8854
3 changed files with 138 additions and 9 deletions
+55 -7
View File
@@ -369,15 +369,63 @@ impl VM {
}
}
BoundKind::Pipe { inputs, lambda } => {
// To be implemented in next step:
// 1. Create PipeStream
// 2. Link with RootStream and Inputs
// 3. Create and return SharedSeries
use crate::ast::rtl::streams::{PipelineNode, PipeStream, StreamNode, ValuePusher, ObservableStream};
use crate::ast::rtl::series::{RingBuffer, SharedValueSeries};
let mut obs_streams = Vec::new();
for input in inputs {
self.eval_internal(obs, input)?;
let val = self.eval_internal(obs, input)?;
if let Value::Object(obj) = val {
if let Some(s) = obj.as_any().downcast_ref::<StreamNode>() {
obs_streams.push(s.inner.clone());
} else if let Some(p) = obj.as_any().downcast_ref::<PipelineNode>() {
obs_streams.push(p.stream.clone());
} else {
return Err(format!("Pipe input must be a stream, found {}", obj.type_name()));
}
} else {
return Err("Pipe input must be an object (stream)".to_string());
}
}
self.eval_internal(obs, lambda)?;
Ok(Value::Void)
let lambda_val = self.eval_internal(obs, lambda)?;
let lambda_obj = if let Value::Object(obj) = lambda_val {
obj
} else {
return Err("Pipe lambda must be a function/closure".to_string());
};
let pipe = Rc::new(RefCell::new(PipeStream::new(
"pipe".to_string(),
inputs.len(),
Some(lambda_obj)
)));
for stream in obs_streams {
stream.add_observer(pipe.clone() as Rc<RefCell<dyn crate::ast::rtl::streams::Observer>>);
}
// Create the data buffer and pusher for the pipe's output
let buffer = Rc::new(RefCell::new(RingBuffer::<Value>::new()));
let pusher = Rc::new(RefCell::new(ValuePusher {
buffer: buffer.clone(),
lookback: Some(100), // Default lookback for now
}));
// The pipe pushes to the buffer
pipe.borrow().add_observer(pusher);
// Create the Series view for script access
let series = Rc::new(SharedValueSeries {
buffer,
});
let node = Rc::new(PipelineNode {
stream: pipe,
series,
});
Ok(Value::Object(node))
}
BoundKind::Block { exprs } => {
let mut last = Value::Void;