use crate::error::Fail;
use crate::sink::Flow;
pub use tabnas_alchemy::shared::scan::Transition;
pub struct ScanEmit<S, I, O, Step, Finish, Out>
where
Step: FnMut(S, I) -> Result<Transition<S, O>, Fail>,
Finish: FnOnce(S) -> Result<Vec<O>, Fail>,
Out: FnMut(O) -> Result<Flow, Fail>,
{
state: Option<S>,
step: Step,
finish: Option<Finish>,
out: Out,
_item: std::marker::PhantomData<I>,
}
impl<S, I, O, Step, Finish, Out> ScanEmit<S, I, O, Step, Finish, Out>
where
Step: FnMut(S, I) -> Result<Transition<S, O>, Fail>,
Finish: FnOnce(S) -> Result<Vec<O>, Fail>,
Out: FnMut(O) -> Result<Flow, Fail>,
{
pub fn new(initial: S, step: Step, finish: Finish, out: Out) -> Self {
ScanEmit {
state: Some(initial),
step,
finish: Some(finish),
out,
_item: std::marker::PhantomData,
}
}
pub fn item(&mut self, item: I) -> Result<Flow, Fail> {
let state = self
.state
.take()
.ok_or_else(|| Fail::protocol("scan-emit received an item after it finished"))?;
let transition = (self.step)(state, item)?;
self.state = Some(transition.state);
for o in transition.outputs {
if (self.out)(o)? == Flow::Stop {
return Ok(Flow::Stop);
}
}
Ok(Flow::Continue)
}
pub fn finish(&mut self) -> Result<Flow, Fail> {
let state = self
.state
.take()
.ok_or_else(|| Fail::protocol("scan-emit finished twice"))?;
let finish = self
.finish
.take()
.ok_or_else(|| Fail::protocol("scan-emit finished twice"))?;
for o in finish(state)? {
if (self.out)(o)? == Flow::Stop {
return Ok(Flow::Stop);
}
}
Ok(Flow::Continue)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn running_sum_with_a_total_at_the_end() {
let mut seen = Vec::new();
{
let mut scan = ScanEmit::new(
0i64,
|sum: i64, x: i64| Ok(Transition::emit(sum + x, format!("+{x}"))),
|sum: i64| Ok(vec![format!("={sum}")]),
|s: String| {
seen.push(s);
Ok(Flow::Continue)
},
);
scan.item(1).unwrap();
scan.item(2).unwrap();
scan.finish().unwrap();
assert_eq!(
scan.finish().unwrap_err().code,
crate::Code::ProtocolOrderError
);
}
assert_eq!(seen, ["+1", "+2", "=3"]);
}
#[test]
fn stop_propagates() {
let mut scan = ScanEmit::new(
(),
|_: (), x: u8| Ok(Transition::emit((), x)),
|_: ()| Ok(vec![]),
|x: u8| Ok(if x == 2 { Flow::Stop } else { Flow::Continue }),
);
assert_eq!(scan.item(1).unwrap(), Flow::Continue);
assert_eq!(scan.item(2).unwrap(), Flow::Stop);
}
}