use crate::resources::{BorrowResource, DefaultResources, Resource}; use crate::schema::SymbolRunner; use crate::symbols::Symbol; use crate::to_artifact::ToArtifact; use crate::{DefaultBuilder, DefaultLocator, ResourceLocator, SymbolBuilder}; use std::collections::HashSet; use std::error::Error; use std::fmt::Debug; use std::hash::Hash; use std::marker::PhantomData; pub trait CanHandle { fn handle(&mut self, x: X) -> Result<(X::Artifact, bool), Box>; } macro_rules! can_handle { ( $($name:ident)* ) => ( #[allow(non_snake_case)] impl<_SR: SymbolRunner, _L, _R: Hash + Eq, _B, $($name: Resource,)*> CanHandle<($($name,)*)> for Setup<_SR, _L, _R, _B> where $( _B: SymbolBuilder<$name>, <_B as SymbolBuilder<$name>>::Symbol: Runnable + Debug, _L: ResourceLocator<$name>, _R: From<$name> + BorrowResource<$name>, Self: CanHandle<<_B as SymbolBuilder<$name>>::Prerequisites> + CanHandle<<_L as ResourceLocator<$name>>::Prerequisites> ),* { fn handle(&mut self, ($($name,)*): ($($name,)*)) -> Result<(($($name::Artifact,)*), bool), Box> { $(let $name = self.add($name)?;)* Ok((($($name.0,)*), false $(|| $name.1)*)) } } ); } for_each_tuple!(can_handle); // This is for self-referential T // FIXME: Wait for specialization impl<_SR: SymbolRunner, _L, _R: Hash + Eq, _B, T: Resource> CanHandle> for Setup<_SR, _L, _R, _B> where _B: SymbolBuilder, <_B as SymbolBuilder>::Symbol: Runnable + Debug, _L: ResourceLocator>, _R: From + BorrowResource, Self: CanHandle<<_B as SymbolBuilder>::Prerequisites>, { fn handle( &mut self, r: Option, ) -> Result<( as ToArtifact>::Artifact, bool), Box> { Ok(match r { Some(r) => { let (result, did_run) = self.add(r)?; (Some(result), did_run) } None => (None, false), }) } } impl<_SR: SymbolRunner, _L, _R: Hash + Eq, _B, T: Resource> CanHandle for Setup<_SR, _L, _R, _B> where _B: SymbolBuilder, <_B as SymbolBuilder>::Symbol: Runnable + Debug, _L: ResourceLocator, _R: From + Debug + BorrowResource, Self: CanHandle<<_B as SymbolBuilder>::Prerequisites> + CanHandle<<_L as ResourceLocator>::Prerequisites>, { fn handle(&mut self, r: T) -> Result<(::Artifact, bool), Box> { self.add(r) } } pub struct Setup< SR, L = DefaultLocator, R = DefaultResources<'static, &'static str>, B = DefaultBuilder, > { symbol_runner: SR, resources: HashSet, phantom: PhantomData<(L, B)>, } // https://github.com/rust-lang/rust/issues/27336 impl Setup { pub fn new(symbol_runner: SR) -> Self { Self { symbol_runner, resources: Default::default(), phantom: Default::default(), } } } impl Setup { pub fn new_with(symbol_runner: SR) -> Self { Self { symbol_runner, resources: Default::default(), phantom: Default::default(), } } } pub trait Runnable { fn run(&self, runner: &R, force: bool) -> Result>; } impl Runnable for S { fn run(&self, runner: &R, force: bool) -> Result> { runner.run_symbol(self, force) } } macro_rules! runnable_for_tuple { ( $($name:ident)* ) => ( #[allow(non_snake_case)] impl<$($name: Symbol + Debug,)*> Runnable for ($($name,)*) { #[allow(unused)] fn run<_R: SymbolRunner>(&self, runner: &_R, force: bool) -> Result> { let ($($name,)*) = self; let mut result = false; $(result = runner.run_symbol($name, force || result)? || result;)* Ok(result) } } ); } for_each_tuple!(runnable_for_tuple); impl Setup { pub fn add(&mut self, resource: R) -> Result<(R::Artifact, bool), Box> where B: SymbolBuilder, >::Symbol: Runnable + Debug, L: ResourceLocator, Rs: From + BorrowResource, Self: CanHandle + CanHandle<>::Prerequisites>, { self.add_force(resource, false) } pub fn add_force( &mut self, resource: R, force_run: bool, ) -> Result<(R::Artifact, bool), Box> where B: SymbolBuilder, >::Symbol: Runnable + Debug, L: ResourceLocator, Rs: From + BorrowResource, Self: CanHandle + CanHandle<>::Prerequisites>, { let (target, target_prereqs) = L::locate(&resource); let storable_resource = Rs::from(resource); let did_run = if self.resources.get(&storable_resource).is_some() { assert!( !force_run, "Forcing to run an already-added resource is a logical error" ); false } else { let (_, target_prereqs_did_run) = self.handle(target_prereqs)?; let (symbol, prereqs_did_run) = self.get_symbol(storable_resource.borrow_resource().unwrap(), &target)?; self.resources.insert(storable_resource); self.run_symbol( symbol, force_run || target_prereqs_did_run || prereqs_did_run, )? }; Ok((target, did_run)) } fn get_symbol( &mut self, resource: &R, target: &R::Artifact, ) -> Result<(>::Symbol, bool), Box> where B: SymbolBuilder, Self: CanHandle, { let (prereqs, prereqs_did_run) = self.handle(B::prerequisites(resource))?; Ok((B::create(resource, target, prereqs), prereqs_did_run)) } pub fn run_symbol(&self, symbol: S, force: bool) -> Result> { symbol.run(&self.symbol_runner, force) } } #[cfg(test)] mod test { use crate::resources::{BorrowResource, Resource}; use crate::schema::SymbolRunner; use crate::symbols::Symbol; use crate::to_artifact::ToArtifact; use crate::{ResourceLocator, Setup, SymbolBuilder}; use std::cell::RefCell; use std::error::Error; use std::fmt::Debug; use std::rc::Rc; struct TestSymbolRunner { count: Rc>, } impl SymbolRunner for TestSymbolRunner { fn run_symbol( &self, symbol: &S, force: bool, ) -> Result> { let run = force || !symbol.target_reached()?; if run { *self.count.borrow_mut() += 1; } Ok(run) } } #[derive(Debug, PartialEq, Eq, Hash)] struct TestResource(&'static str, T); impl Resource for TestResource { type Artifact = (); } #[derive(Debug, Hash, PartialEq, Eq)] enum Resources { A(TestResource<&'static str>), B(TestResource<()>), } impl From> for Resources { fn from(from: TestResource<&'static str>) -> Self { Self::A(from) } } impl From> for Resources { fn from(from: TestResource<()>) -> Self { Self::B(from) } } impl BorrowResource> for Resources { fn borrow_resource(&self) -> Option<&TestResource<&'static str>> { match self { Self::A(a) => Some(a), _ => None, } } } impl BorrowResource> for Resources { fn borrow_resource(&self) -> Option<&TestResource<()>> { match self { Self::B(b) => Some(b), _ => None, } } } struct TestResourceLocator; impl ResourceLocator> for TestResourceLocator { type Prerequisites = (); fn locate(_resource: &TestResource) -> ( as ToArtifact>::Artifact, ()) { ((), ()) } } struct TestSymbolBuilder; impl SymbolBuilder> for TestSymbolBuilder { type Symbol = TestSymbol; type Prerequisites = TestResource<()>; fn prerequisites(resource: &TestResource<&'static str>) -> Self::Prerequisites { TestResource(resource.1, ()) } fn create( resource: &TestResource<&'static str>, (): &(), _inputs: ::Artifact, ) -> Self::Symbol { TestSymbol { reached: resource.0.chars().next().unwrap().is_uppercase(), } } } impl SymbolBuilder> for TestSymbolBuilder { type Symbol = TestSymbol; type Prerequisites = (); fn prerequisites(_resource: &TestResource<()>) -> Self::Prerequisites {} fn create(resource: &TestResource<()>, (): &(), (): ()) -> Self::Symbol { TestSymbol { reached: resource.0.chars().next().unwrap().is_uppercase(), } } } #[derive(Debug)] struct TestSymbol { reached: bool, } impl Symbol for TestSymbol { fn target_reached(&self) -> Result> { Ok(self.reached) } fn execute(&self) -> Result<(), Box> { Ok(()) } } fn get_setup() -> ( Rc>, Setup, ) { let count = Rc::new(RefCell::new(0)); let runner = TestSymbolRunner { count: Rc::clone(&count), }; (count, Setup::new_with(runner)) } #[test] fn correctly_uses_force() { let (count, mut setup) = get_setup(); setup.add(TestResource("A", "b")).unwrap(); assert_eq!(*count.borrow(), 2); setup.add(TestResource("A", "b")).unwrap(); assert_eq!(*count.borrow(), 2); let (count, mut setup) = get_setup(); setup.add(TestResource("A", "B")).unwrap(); assert_eq!(*count.borrow(), 0); } #[test] fn correctly_handles_symbol_tuples() { let (count, setup) = get_setup(); setup .run_symbol( (TestSymbol { reached: false }, TestSymbol { reached: false }), false, ) .unwrap(); assert_eq!(*count.borrow(), 2); let (count, setup) = get_setup(); setup .run_symbol( (TestSymbol { reached: true }, TestSymbol { reached: false }), false, ) .unwrap(); assert_eq!(*count.borrow(), 1); // An unreached symbol forces all further symbols let (count, setup) = get_setup(); setup .run_symbol( (TestSymbol { reached: false }, TestSymbol { reached: true }), false, ) .unwrap(); assert_eq!(*count.borrow(), 2); let (count, setup) = get_setup(); setup .run_symbol( (TestSymbol { reached: true }, TestSymbol { reached: true }), false, ) .unwrap(); assert_eq!(*count.borrow(), 0); let (count, setup) = get_setup(); setup .run_symbol( (TestSymbol { reached: true }, TestSymbol { reached: true }), true, ) .unwrap(); assert_eq!(*count.borrow(), 2); } }