From cdaf2e7013b0017228aba328f4a9bc511f79ad38 Mon Sep 17 00:00:00 2001 From: Camden Dixie O'Brien Date: Sat, 26 Sep 2026 11:49:20 +0100 Subject: [PATCH] Implement evaluator composition --- src/eval.rs | 1 + src/eval/compose.rs | 148 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+) create mode 100644 src/eval/compose.rs diff --git a/src/eval.rs b/src/eval.rs index 4cd38b6..00a85f2 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -1,5 +1,6 @@ pub mod normalise; pub mod const_fold; +pub mod compose; #[derive(Debug)] pub enum Error { diff --git a/src/eval/compose.rs b/src/eval/compose.rs new file mode 100644 index 0000000..6791d3b --- /dev/null +++ b/src/eval/compose.rs @@ -0,0 +1,148 @@ +use crate::eval::Evaluator; + +pub struct Composition { + first: E1, + second: E2, +} + +impl Composition { + pub fn new(first: E1, second: E2) -> Self { + Self { first, second } + } +} + +impl Evaluator for Composition +where + E1: Evaluator, + E2: Evaluator +{ + type Expr = Expr; + type Error = Error; + + fn eval(&mut self, input: Expr) -> Result { + self.second.eval(self.first.eval(input)?) + } +} + +#[macro_export] +macro_rules! pipeline { + ($last:expr) => { + $last + }; + ($first:expr, $($rest:expr),+) => { + $crate::eval::compose::Composition::new( + $first, $crate::pipeline!($($rest),+)) + }; +} + +#[cfg(test)] +mod tests { + use super::*; + + #[derive(Debug, PartialEq)] + enum Expr { + Foo, + Bar, + Baz, + Quux, + } + + #[derive(Debug, PartialEq)] + enum Err { + First, + Second, + Third, + } + + struct First; + struct Second; + struct Third; + + impl Evaluator for First { + type Expr = Expr; + type Error = Err; + + fn eval(&mut self, input: Expr) -> Result { + match input { + Expr::Foo => Ok(Expr::Bar), + Expr::Baz => Ok(Expr::Foo), + Expr::Quux => Ok(Expr::Baz), + _ => Err(Err::First), + } + } + } + + impl Evaluator for Second { + type Expr = Expr; + type Error = Err; + + fn eval(&mut self, input: Expr) -> Result { + match input { + Expr::Bar => Ok(Expr::Baz), + Expr::Baz => Ok(Expr::Foo), + _ => Err(Err::Second), + } + } + } + + impl Evaluator for Third { + type Expr = Expr; + type Error = Err; + + fn eval(&mut self, input: Expr) -> Result { + match input { + Expr::Baz => Ok(Expr::Quux), + _ => Err(Err::Third), + } + } + } + + #[test] + fn composition_chains_evaluators_in_order() { + let mut comp = Composition::new(First {}, Second {}); + let result = comp.eval(Expr::Foo).unwrap(); + assert_eq!(result, Expr::Baz); + } + + #[test] + fn error_in_first_of_composition_is_returned() { + let mut comp = Composition::new(First {}, Second {}); + let err = comp.eval(Expr::Bar).unwrap_err(); + assert_eq!(err, Err::First); + } + + #[test] + fn error_in_second_of_composition_is_returned() { + let mut comp = Composition::new(First {}, Second {}); + let err = comp.eval(Expr::Baz).unwrap_err(); + assert_eq!(err, Err::Second); + } + + #[test] + fn pipeline_chains_evaluators_in_order() { + let mut pipe = pipeline!(First {}, Second {}, Third {}); + let result = pipe.eval(Expr::Foo).unwrap(); + assert_eq!(result, Expr::Quux); + } + + #[test] + fn error_in_first_of_pipeline_is_returned() { + let mut pipe = pipeline!(First {}, Second {}, Third {}); + let err = pipe.eval(Expr::Bar).unwrap_err(); + assert_eq!(err, Err::First); + } + + #[test] + fn error_in_second_of_pipeline_is_returned() { + let mut pipe = pipeline!(First {}, Second {}, Third {}); + let err = pipe.eval(Expr::Baz).unwrap_err(); + assert_eq!(err, Err::Second); + } + + #[test] + fn error_in_third_of_pipeline_is_returned() { + let mut pipe = pipeline!(First {}, Second {}, Third {}); + let err = pipe.eval(Expr::Quux).unwrap_err(); + assert_eq!(err, Err::Third); + } +}