You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
371 lines
11 KiB
371 lines
11 KiB
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<X: ToArtifact> {
|
|
fn handle(&mut self, x: X) -> Result<(X::Artifact, bool), Box<dyn Error>>;
|
|
}
|
|
|
|
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<dyn Error>>
|
|
{
|
|
$(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<Option<T>>
|
|
for Setup<_SR, _L, _R, _B>
|
|
where
|
|
_B: SymbolBuilder<T>,
|
|
<_B as SymbolBuilder<T>>::Symbol: Runnable + Debug,
|
|
_L: ResourceLocator<T, Prerequisites = Option<T>>,
|
|
_R: From<T> + BorrowResource<T>,
|
|
Self: CanHandle<<_B as SymbolBuilder<T>>::Prerequisites>,
|
|
{
|
|
fn handle(
|
|
&mut self,
|
|
r: Option<T>,
|
|
) -> Result<(<Option<T> as ToArtifact>::Artifact, bool), Box<dyn Error>> {
|
|
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<T> for Setup<_SR, _L, _R, _B>
|
|
where
|
|
_B: SymbolBuilder<T>,
|
|
<_B as SymbolBuilder<T>>::Symbol: Runnable + Debug,
|
|
_L: ResourceLocator<T>,
|
|
_R: From<T> + Debug + BorrowResource<T>,
|
|
Self: CanHandle<<_B as SymbolBuilder<T>>::Prerequisites>
|
|
+ CanHandle<<_L as ResourceLocator<T>>::Prerequisites>,
|
|
{
|
|
fn handle(&mut self, r: T) -> Result<(<T as ToArtifact>::Artifact, bool), Box<dyn Error>> {
|
|
self.add(r)
|
|
}
|
|
}
|
|
|
|
pub struct Setup<
|
|
SR,
|
|
L = DefaultLocator,
|
|
R = DefaultResources<'static, &'static str>,
|
|
B = DefaultBuilder,
|
|
> {
|
|
symbol_runner: SR,
|
|
resources: HashSet<R>,
|
|
phantom: PhantomData<(L, B)>,
|
|
}
|
|
|
|
// https://github.com/rust-lang/rust/issues/27336
|
|
impl<SR> Setup<SR> {
|
|
pub fn new(symbol_runner: SR) -> Self {
|
|
Self {
|
|
symbol_runner,
|
|
resources: Default::default(),
|
|
phantom: Default::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<SR, L, R: Hash + Eq, B> Setup<SR, L, R, B> {
|
|
pub fn new_with(symbol_runner: SR) -> Self {
|
|
Self {
|
|
symbol_runner,
|
|
resources: Default::default(),
|
|
phantom: Default::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub trait Runnable {
|
|
fn run<R: SymbolRunner>(&self, runner: &R, force: bool) -> Result<bool, Box<dyn Error>>;
|
|
}
|
|
|
|
impl<S: Symbol + Debug> Runnable for S {
|
|
fn run<R: SymbolRunner>(&self, runner: &R, force: bool) -> Result<bool, Box<dyn Error>> {
|
|
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<bool, Box<dyn Error>> {
|
|
let ($($name,)*) = self;
|
|
let mut result = false;
|
|
$(result = runner.run_symbol($name, force || result)? || result;)*
|
|
Ok(result)
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
for_each_tuple!(runnable_for_tuple);
|
|
|
|
impl<SR: SymbolRunner, L, Rs: Hash + Eq, B> Setup<SR, L, Rs, B> {
|
|
pub fn add<R: Resource>(&mut self, resource: R) -> Result<(R::Artifact, bool), Box<dyn Error>>
|
|
where
|
|
B: SymbolBuilder<R>,
|
|
<B as SymbolBuilder<R>>::Symbol: Runnable + Debug,
|
|
L: ResourceLocator<R>,
|
|
Rs: From<R> + BorrowResource<R>,
|
|
Self: CanHandle<B::Prerequisites> + CanHandle<<L as ResourceLocator<R>>::Prerequisites>,
|
|
{
|
|
self.add_force(resource, false)
|
|
}
|
|
|
|
pub fn add_force<R: Resource>(
|
|
&mut self,
|
|
resource: R,
|
|
force_run: bool,
|
|
) -> Result<(R::Artifact, bool), Box<dyn Error>>
|
|
where
|
|
B: SymbolBuilder<R>,
|
|
<B as SymbolBuilder<R>>::Symbol: Runnable + Debug,
|
|
L: ResourceLocator<R>,
|
|
Rs: From<R> + BorrowResource<R>,
|
|
Self: CanHandle<B::Prerequisites> + CanHandle<<L as ResourceLocator<R>>::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<R: Resource>(
|
|
&mut self,
|
|
resource: &R,
|
|
target: &R::Artifact,
|
|
) -> Result<(<B as SymbolBuilder<R>>::Symbol, bool), Box<dyn Error>>
|
|
where
|
|
B: SymbolBuilder<R>,
|
|
Self: CanHandle<B::Prerequisites>,
|
|
{
|
|
let (prereqs, prereqs_did_run) = self.handle(B::prerequisites(resource))?;
|
|
Ok((B::create(resource, target, prereqs), prereqs_did_run))
|
|
}
|
|
|
|
pub fn run_symbol<S: Runnable>(&self, symbol: S, force: bool) -> Result<bool, Box<dyn Error>> {
|
|
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<RefCell<usize>>,
|
|
}
|
|
|
|
impl SymbolRunner for TestSymbolRunner {
|
|
fn run_symbol<S: Symbol + Debug>(
|
|
&self,
|
|
symbol: &S,
|
|
force: bool,
|
|
) -> Result<bool, Box<dyn Error>> {
|
|
let run = force || !symbol.target_reached()?;
|
|
if run {
|
|
*self.count.borrow_mut() += 1;
|
|
}
|
|
Ok(run)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
|
struct TestResource<T>(&'static str, T);
|
|
impl<T> Resource for TestResource<T> {
|
|
type Artifact = ();
|
|
}
|
|
|
|
#[derive(Debug, Hash, PartialEq, Eq)]
|
|
enum Resources {
|
|
A(TestResource<&'static str>),
|
|
B(TestResource<()>),
|
|
}
|
|
impl From<TestResource<&'static str>> for Resources {
|
|
fn from(from: TestResource<&'static str>) -> Self {
|
|
Self::A(from)
|
|
}
|
|
}
|
|
impl From<TestResource<()>> for Resources {
|
|
fn from(from: TestResource<()>) -> Self {
|
|
Self::B(from)
|
|
}
|
|
}
|
|
|
|
impl BorrowResource<TestResource<&'static str>> for Resources {
|
|
fn borrow_resource(&self) -> Option<&TestResource<&'static str>> {
|
|
match self {
|
|
Self::A(a) => Some(a),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
impl BorrowResource<TestResource<()>> for Resources {
|
|
fn borrow_resource(&self) -> Option<&TestResource<()>> {
|
|
match self {
|
|
Self::B(b) => Some(b),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
struct TestResourceLocator;
|
|
impl<T> ResourceLocator<TestResource<T>> for TestResourceLocator {
|
|
type Prerequisites = ();
|
|
fn locate(_resource: &TestResource<T>) -> (<TestResource<T> as ToArtifact>::Artifact, ()) {
|
|
((), ())
|
|
}
|
|
}
|
|
|
|
struct TestSymbolBuilder;
|
|
impl SymbolBuilder<TestResource<&'static str>> 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: <Self::Prerequisites as ToArtifact>::Artifact,
|
|
) -> Self::Symbol {
|
|
TestSymbol {
|
|
reached: resource.0.chars().next().unwrap().is_uppercase(),
|
|
}
|
|
}
|
|
}
|
|
impl SymbolBuilder<TestResource<()>> 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<bool, Box<dyn Error>> {
|
|
Ok(self.reached)
|
|
}
|
|
fn execute(&self) -> Result<(), Box<dyn Error>> {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn get_setup() -> (
|
|
Rc<RefCell<usize>>,
|
|
Setup<TestSymbolRunner, TestResourceLocator, Resources, TestSymbolBuilder>,
|
|
) {
|
|
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);
|
|
}
|
|
}
|