From 2d537cc4eefaed0cb5d19a64dc05c1aaa2fe291f Mon Sep 17 00:00:00 2001 From: Schrottkatze Date: Mon, 19 Feb 2024 20:54:24 +0100 Subject: [PATCH] prowocessing: add trait based experiment --- crates/app/src/main.rs | 14 +- crates/prowocessing/src/experimental.rs | 2 + .../src/experimental/trait_based.rs | 339 ++++++++++++++++++ crates/prowocessing/src/lib.rs | 4 +- 4 files changed, 356 insertions(+), 3 deletions(-) create mode 100644 crates/prowocessing/src/experimental/trait_based.rs diff --git a/crates/app/src/main.rs b/crates/app/src/main.rs index 2caaa2f..2534631 100644 --- a/crates/app/src/main.rs +++ b/crates/app/src/main.rs @@ -56,17 +56,20 @@ fn main() { mod dev { use clap::Subcommand; - use prowocessing::experimental::enum_based::{Pipeline, PipelineBuilder}; + use prowocessing::experimental::trait_based::DataType; #[derive(Subcommand)] pub(crate) enum DevCommands { Enums { test_str: String }, + Add { num0: i32, num1: i32 }, } impl DevCommands { pub fn run(self) { match self { DevCommands::Enums { test_str } => { + use prowocessing::experimental::enum_based::PipelineBuilder; + let upr = PipelineBuilder::new() .insert(prowocessing::experimental::enum_based::Instruction::Uppercase) .build(); @@ -77,6 +80,15 @@ mod dev { println!("Upr: {}", upr.run(test_str.clone())); println!("Lwr: {}", lwr.run(test_str.clone())); } + DevCommands::Add { num0, num1 } => { + use prowocessing::experimental::trait_based::PipelineBuilder; + + let pipe = PipelineBuilder::new().add(1).stringify().build(); + println!( + "{:?}", + pipe.run(vec![num0.into(), num1.into()].into()).into_inner()[0] + ); + } } } } diff --git a/crates/prowocessing/src/experimental.rs b/crates/prowocessing/src/experimental.rs index 090198e..fcc0d52 100644 --- a/crates/prowocessing/src/experimental.rs +++ b/crates/prowocessing/src/experimental.rs @@ -1 +1,3 @@ pub mod enum_based; + +pub mod trait_based; diff --git a/crates/prowocessing/src/experimental/trait_based.rs b/crates/prowocessing/src/experimental/trait_based.rs new file mode 100644 index 0000000..3be8f3a --- /dev/null +++ b/crates/prowocessing/src/experimental/trait_based.rs @@ -0,0 +1,339 @@ +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], + } + } + } +} diff --git a/crates/prowocessing/src/lib.rs b/crates/prowocessing/src/lib.rs index 2e7b047..aa81057 100644 --- a/crates/prowocessing/src/lib.rs +++ b/crates/prowocessing/src/lib.rs @@ -2,8 +2,7 @@ //! //! One of the design goals for this library is, however, to be a simple, generic image processing library. //! For now, it's just indev... lets see what comes of it! - -use experimental::enum_based::{Instruction, PipelineBuilder}; +#![feature(lint_reasons)] /// just some experiments, to test whether the architecture i want is even possible (or how to do it). probably temporary. /// Gonna first try string processing... @@ -11,6 +10,7 @@ pub mod experimental; #[test] fn test_enums() { + use crate::experimental::enum_based::{Instruction, PipelineBuilder}; let builder = PipelineBuilder::new().insert(Instruction::Uppercase); let upr = builder.build(); let upr_lowr = builder.insert(Instruction::Lowercase).build();