use self::{ numops::{Add, Stringify, Subtract}, strops::{Concatenate, Lower, Upper}, }; trait PipelineElement { fn runner(&self) -> fn(&Inputs) -> Outputs; fn signature(&self) -> ElementIo; } struct ElementIo { pub inputs: Vec, pub outputs: Vec, } // TODO: // - Bind additional inputs if instruction has more then one and is passd without any additional // - allow binding to pointers to other pipelines? // - allow referencing earlier data pub struct PipelineBuilder { elements: Vec>, } pub struct Pipeline { runners: Vec Outputs>, } impl Pipeline { pub fn run(&self, inputs: Inputs) -> Outputs { let mut out: Outputs = inputs.into(); for runner in &self.runners { out = runner(&(&out).into()); } out } } impl PipelineBuilder { pub fn new() -> Self { Self { elements: Vec::new(), } } fn insert(mut self, el: T) -> Self { if let Some(previous_item) = self.elements.last() { assert_eq!( previous_item.signature().outputs[0], el.signature().inputs[0] ); } self.elements.push(Box::new(el)); self } #[must_use] pub fn concatenate(self, sec: String) -> Self { self.insert(Concatenate(sec)) } #[must_use] pub fn upper(self) -> Self { self.insert(Upper) } #[must_use] pub fn lower(self) -> Self { self.insert(Lower) } #[must_use] #[allow( clippy::should_implement_trait, reason = "is not equivalent to addition" )] pub fn add(self, sec: i32) -> Self { self.insert(Add(sec)) } #[must_use] pub fn subtract(self, sec: i32) -> Self { self.insert(Subtract(sec)) } #[must_use] pub fn stringify(self) -> Self { self.insert(Stringify) } pub fn build(&self) -> Pipeline { let mut r = Vec::new(); self.elements.iter().for_each(|el| r.push(el.runner())); Pipeline { runners: r } } } impl Default for PipelineBuilder { fn default() -> Self { Self::new() } } #[derive(Clone, Copy)] pub enum Data<'a> { String(&'a str), Int(i32), } impl Data<'_> { pub fn to_owned_data(&self) -> OwnedData { match self { Data::String(s) => (*s).to_owned().into(), Data::Int(i) => (*i).into(), } } } impl<'a> From<&'a str> for Data<'a> { fn from(value: &'a str) -> Self { Self::String(value) } } impl From for Data<'_> { fn from(value: i32) -> Self { Self::Int(value) } } impl<'a> From<&'a OwnedData> for Data<'a> { fn from(value: &'a OwnedData) -> Self { match value { OwnedData::String(s) => Data::String(s), OwnedData::Int(i) => Data::Int(*i), } } } #[derive(Clone, Debug)] pub enum OwnedData { String(String), Int(i32), } impl From for OwnedData { fn from(value: String) -> Self { Self::String(value) } } impl From for OwnedData { fn from(value: i32) -> Self { Self::Int(value) } } #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum DataType { String, Int, } pub struct Inputs<'a>(Vec>); impl<'a> Inputs<'a> { fn inner(&self) -> Vec> { self.0.clone() } } impl<'a> From>> for Inputs<'a> { fn from(value: Vec>) -> Self { Self(value) } } impl<'a, T: Into>> From for Inputs<'a> { fn from(value: T) -> Self { Self(vec![value.into()]) } } impl<'a> From<&'a Outputs> for Inputs<'a> { fn from(value: &'a Outputs) -> Self { Self(value.0.iter().map(std::convert::Into::into).collect()) } } pub struct Outputs(Vec); impl Outputs { pub fn into_inner(self) -> Vec { self.0 } } impl From> for Outputs { fn from(value: Vec) -> Self { Self(value) } } impl> From for Outputs { fn from(value: T) -> Self { Self(vec![value.into()]) } } impl From> for Outputs { fn from(value: Inputs) -> Self { Self( value .0 .into_iter() .map(|i: Data<'_>| Data::to_owned_data(&i)) .collect(), ) } } mod strops { use super::{Data, DataType, ElementIo, Inputs, Outputs, PipelineElement}; pub struct Concatenate(pub String); impl PipelineElement for Concatenate { fn runner(&self) -> fn(&Inputs) -> Outputs { |input| { if let [Data::String(s0), Data::String(s1), ..] = input.inner()[..] { format!("{s0}{s1}").into() } else { panic!("Invalid data passed") } } } fn signature(&self) -> ElementIo { ElementIo { inputs: vec![DataType::String, DataType::String], outputs: vec![DataType::String], } } } pub struct Upper; impl PipelineElement for Upper { fn runner(&self) -> fn(&Inputs) -> Outputs { |input| { if let [Data::String(s), ..] = input.inner()[..] { s.to_uppercase().into() } else { panic!("Invalid data passed") } } } fn signature(&self) -> ElementIo { ElementIo { inputs: vec![DataType::String], outputs: vec![DataType::String], } } } pub struct Lower; impl PipelineElement for Lower { fn runner(&self) -> fn(&Inputs) -> Outputs { |input| { if let [Data::String(s), ..] = input.inner()[..] { s.to_lowercase().into() } else { panic!("Invalid data passed") } } } fn signature(&self) -> ElementIo { ElementIo { inputs: vec![DataType::String], outputs: vec![DataType::String], } } } } mod numops { use core::panic; use super::{Data, DataType, ElementIo, Inputs, Outputs, PipelineElement}; pub struct Add(pub i32); impl PipelineElement for Add { fn runner(&self) -> fn(&Inputs) -> Outputs { |input| { if let [Data::Int(i0), Data::Int(i1), ..] = input.inner()[..] { (i0 + i1).into() } else { panic!("Invalid data passed") } } } fn signature(&self) -> ElementIo { ElementIo { inputs: vec![DataType::Int, DataType::Int], outputs: vec![DataType::Int], } } } pub struct Subtract(pub i32); impl PipelineElement for Subtract { fn runner(&self) -> fn(&Inputs) -> Outputs { |input| { if let [Data::Int(i0), Data::Int(i1), ..] = input.inner()[..] { (i0 + i1).into() } else { panic!("Invalid data passed") } } } fn signature(&self) -> ElementIo { ElementIo { inputs: vec![DataType::Int, DataType::Int], outputs: vec![DataType::Int], } } } pub struct Stringify; impl PipelineElement for Stringify { fn runner(&self) -> fn(&Inputs) -> Outputs { |input| { if let [Data::Int(int), ..] = input.inner()[..] { int.to_string().into() } else { panic!("Invalid data passed") } } } fn signature(&self) -> ElementIo { ElementIo { inputs: vec![DataType::Int], outputs: vec![DataType::String], } } } }