use core::marker::PhantomData;
use crate::error::DriverError;
use crate::session::Session;
use crate::stage::Stage;
#[derive(Debug, Clone)]
pub struct Then<A, B> {
first: A,
second: B,
}
impl<C, A, B> Stage<C> for Then<A, B>
where
A: Stage<C>,
B: Stage<C, Input = A::Output>,
{
type Input = A::Input;
type Output = B::Output;
fn name(&self) -> &'static str {
self.second.name()
}
fn run(
&mut self,
input: Self::Input,
session: &mut Session<C>,
) -> Result<Self::Output, DriverError> {
let mid = self
.first
.run(input, session)
.map_err(|e| e.in_stage(self.first.name()))?;
self.second
.run(mid, session)
.map_err(|e| e.in_stage(self.second.name()))
}
}
pub struct Pipeline<C, S> {
stage: S,
_config: PhantomData<fn() -> C>,
}
impl<C, S> Pipeline<C, S> {
#[must_use]
pub fn new(stage: S) -> Self {
Self {
stage,
_config: PhantomData,
}
}
}
impl<C, S: Stage<C>> Pipeline<C, S> {
#[must_use]
pub fn then<N>(self, next: N) -> Pipeline<C, Then<S, N>>
where
N: Stage<C, Input = S::Output>,
{
Pipeline {
stage: Then {
first: self.stage,
second: next,
},
_config: PhantomData,
}
}
pub fn run(
&mut self,
input: S::Input,
session: &mut Session<C>,
) -> Result<S::Output, DriverError> {
let name = self.stage.name();
self.stage.run(input, session).map_err(|e| e.in_stage(name))
}
#[must_use]
#[inline]
pub fn name(&self) -> &'static str {
self.stage.name()
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
use alloc::vec;
use alloc::vec::Vec;
struct Lex;
impl Stage<()> for Lex {
type Input = &'static str;
type Output = Vec<i64>;
fn name(&self) -> &'static str {
"lex"
}
fn run(
&mut self,
input: &'static str,
_s: &mut Session<()>,
) -> Result<Vec<i64>, DriverError> {
input
.split_whitespace()
.map(|w| {
w.parse::<i64>()
.map_err(|_| DriverError::new("not an integer"))
})
.collect()
}
}
struct Sum;
impl Stage<()> for Sum {
type Input = Vec<i64>;
type Output = i64;
fn name(&self) -> &'static str {
"sum"
}
fn run(&mut self, input: Vec<i64>, _s: &mut Session<()>) -> Result<i64, DriverError> {
Ok(input.iter().sum())
}
}
struct Negate;
impl Stage<()> for Negate {
type Input = i64;
type Output = i64;
fn name(&self) -> &'static str {
"negate"
}
fn run(&mut self, input: i64, _s: &mut Session<()>) -> Result<i64, DriverError> {
Ok(-input)
}
}
struct EmitThenAbort;
impl Stage<()> for EmitThenAbort {
type Input = i64;
type Output = i64;
fn name(&self) -> &'static str {
"emit-then-abort"
}
fn run(&mut self, _input: i64, session: &mut Session<()>) -> Result<i64, DriverError> {
session.error("boom");
session.abort_if_errors()?;
Ok(0)
}
}
#[test]
fn test_single_stage_pipeline_runs() {
let mut driver = Pipeline::new(Sum);
let mut s = Session::new(());
assert_eq!(driver.run(vec![1, 2, 3], &mut s).unwrap(), 6);
}
#[test]
fn test_two_stage_pipeline_threads_output() {
let mut driver = Pipeline::new(Lex).then(Sum);
let mut s = Session::new(());
assert_eq!(driver.run("1 2 3 4", &mut s).unwrap(), 10);
}
#[test]
fn test_three_stage_pipeline_threads_output() {
let mut driver = Pipeline::new(Lex).then(Sum).then(Negate);
let mut s = Session::new(());
assert_eq!(driver.run("1 2 3", &mut s).unwrap(), -6);
}
#[test]
fn test_pipeline_name_is_final_stage() {
let driver = Pipeline::new(Lex).then(Sum).then(Negate);
assert_eq!(driver.name(), "negate");
}
#[test]
fn test_pipeline_stops_at_first_failing_stage() {
let mut driver = Pipeline::new(Lex).then(Sum);
let mut s = Session::new(());
let err = driver.run("1 nope 3", &mut s).unwrap_err();
assert_eq!(err.stage(), "lex");
assert_eq!(err.message(), "not an integer");
}
#[test]
fn test_failing_stage_in_the_middle_is_attributed() {
let mut driver = Pipeline::new(Lex).then(Sum).then(EmitThenAbort);
let mut s = Session::new(());
let err = driver.run("1 2 3", &mut s).unwrap_err();
assert_eq!(err.stage(), "emit-then-abort");
assert_eq!(err.message(), "aborting due to 1 previous error");
assert_eq!(s.diagnostics().len(), 1);
assert_eq!(s.diagnostics()[0].message(), "boom");
}
struct TagFromConfig;
impl Stage<&'static str> for TagFromConfig {
type Input = i64;
type Output = i64;
fn name(&self) -> &'static str {
"tag-from-config"
}
fn run(
&mut self,
input: i64,
session: &mut Session<&'static str>,
) -> Result<i64, DriverError> {
session.note(*session.config());
Ok(input)
}
}
#[test]
fn test_stages_share_the_session_config() {
let mut driver = Pipeline::new(TagFromConfig).then(TagFromConfig);
let mut s = Session::new("shared");
let out = driver.run(7, &mut s).unwrap();
assert_eq!(out, 7);
let notes: Vec<_> = s.diagnostics().iter().map(|d| d.message()).collect();
assert_eq!(notes, ["shared", "shared"]);
}
#[test]
fn test_diagnostics_from_earlier_stages_survive_a_later_failure() {
struct WarnThenPass;
impl Stage<()> for WarnThenPass {
type Input = &'static str;
type Output = &'static str;
fn name(&self) -> &'static str {
"warn-then-pass"
}
fn run(
&mut self,
input: &'static str,
session: &mut Session<()>,
) -> Result<&'static str, DriverError> {
session.warn("heads up");
Ok(input)
}
}
struct AlwaysFail;
impl Stage<()> for AlwaysFail {
type Input = &'static str;
type Output = ();
fn name(&self) -> &'static str {
"always-fail"
}
fn run(
&mut self,
_input: &'static str,
_s: &mut Session<()>,
) -> Result<(), DriverError> {
Err(DriverError::new("stop"))
}
}
let mut driver = Pipeline::new(WarnThenPass).then(AlwaysFail);
let mut s = Session::new(());
let err = driver.run("x", &mut s).unwrap_err();
assert_eq!(err.stage(), "always-fail");
assert_eq!(s.diagnostics().len(), 1);
assert_eq!(s.diagnostics()[0].message(), "heads up");
}
}