#[macro_export]
macro_rules! component {
(@value_or_default: $t:ty ) => {{ <$t>::default() }};
(@value_or_default: $t:ty = $value:expr) => {{ $value }};
(
// Match a component, its name, and constraints.
component $component_name:ident {
// Match variables, their types, and default values.
let $($i:ident: $val_ty:ty $( = $e:expr )? ),*;
$(
// Match a constraint, its name, and methods.
constraint $constraint_name:ident {
// Match a precondition of the constraint.
$( precondition $precondition:expr; )?
// Match a postcondition of the constraint.
$( postcondition $postcondition:expr; )?
$(
// Match a method, its inputs, outputs and body.
$method_name:ident
($($inp:ident: $inp_ty:ty),*)
$(-> [$($out:ident),+])?
= $m_expr:expr;
)+
}
)*
}
) => {{
let variables = vec![ $( stringify!($i) ),* ];
let values = vec![ $( {
let value: $val_ty = $crate::component!(@value_or_default: $val_ty $( = $e.into() )?);
value.into()
}
),* ];
$crate::macros::RawComponent::new(
stringify!($component_name),
variables,
values,
vec![ $(
$crate::macros::RawConstraint::new_with_assert(
stringify!($constraint_name),
vec![ $(
#[allow(unused_mut)]
$crate::macros::RawMethod::new(
stringify!($method_name),
vec![ $( stringify!($inp) ),* ],
vec![ $( $( stringify!($out) ),* )? ],
{
#[allow(unused_assignments)]
std::sync::Arc::new(move |values| {
let mut var_idx = 0;
$(
let $inp = &values.get(var_idx);
if $inp.is_none() {
return Err($crate::planner::MethodFailure::NoSuchVariable(stringify!($inp).to_owned()));
}
let $inp = std::convert::TryInto::<$inp_ty>::try_into(&**$inp.unwrap());
if $inp.is_err() {
return Err($crate::planner::MethodFailure::TypeConversionFailure(stringify!($inp), stringify!($inp_ty)));
}
let $inp = $inp.unwrap();
var_idx += 1;
)*
use std::sync::Arc;
$m_expr.map(|v| v.into_iter().map(Arc::new).collect())
})
}
)
),* ], None,
)
),* ] ).into_component()
}};
}
#[macro_export]
macro_rules! ret {
($($e:expr),*) => {{ Ok(vec![$($e.into()),*]) }}
}
#[macro_export]
macro_rules! fail {
($($arg:tt)*) => {{
Err($crate::planner::MethodFailure::Custom(format!($($arg)*)))
}};
}
#[cfg(test)]
mod tests {
use crate::{
component_type,
model::Component,
planner::{ComponentSpec, ConstraintSpec, MethodFailure, MethodSpec},
};
use std::convert::TryFrom;
macro_rules! all_into {
($($e:expr),*) => {{ vec![$(std::sync::Arc::new($e.into())),*] }}
}
component_type! {
#[derive(Debug, PartialEq, Clone)]
enum Standard { i32, f64, String }
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Circle {
x: usize,
y: usize,
r: usize,
}
component_type! {
#[derive(Debug, PartialEq, Clone)]
enum Custom { i32, Circle }
}
#[test]
fn gen_val_allows_standard_types() {
let i = 4;
assert_eq!(Standard::from(i), Standard::i32(i));
let f = 13.0;
assert_eq!(Standard::from(f), Standard::f64(f));
}
#[test]
fn gen_val_allows_custom_types() {
let c = Circle { x: 3, y: 8, r: 10 };
let v = Custom::from(c);
assert_eq!(v, Custom::Circle(c));
assert_eq!(TryFrom::try_from(&v), Ok(&c));
}
#[test]
fn methods_automatically_unwrap_arguments() {
let comp: Component<Standard> = component! {
component comp {
let i: i32 = 4, s: String = "abc";
constraint constr {
m(i: &i32, s: &String) = {
assert_eq!(i, &4);
assert_eq!(s, &"abc".to_string());
ret![]
};
}
}
};
let constr = comp.constraints()[0].clone();
let m = constr.methods()[0].clone();
assert_eq!(m.apply(all_into![4, "abc".to_string()]), Ok(vec![]));
}
#[ignore = "This can not be verified with `apply` anymore. Must be done higher up."]
#[test]
fn methods_fail_when_undefined_variable() {
component_type! {
enum Value { i32, String }
}
#[allow(unused_variables)]
let comp: Component<Standard> = component! {
component comp {
let i: i32 = 4, s: String = "abc";
constraint constr {
m(not_defined: &i32) = ret![];
}
}
};
let constr = comp.constraints()[0].clone();
let m = constr.methods()[0].clone();
assert_eq!(
m.apply(all_into![4]),
Err(MethodFailure::NoSuchVariable("not_defined".to_string()))
);
}
#[ignore = "This can not be verified with `apply` anymore. Must be done higher up."]
#[test]
fn methods_fail_when_wrong_type() {
#[allow(unused_variables)]
let comp: Component<Standard> = component! {
component comp {
let i: i32 = 4, s: String = "abc";
constraint constr {
m(s: &i32) = ret![];
}
}
};
let constr = comp.constraints()[0].clone();
let m = constr.methods()[0].clone();
assert_eq!(
m.apply(all_into![0]),
Err(MethodFailure::TypeConversionFailure("s", "&i32"))
);
}
#[test]
fn method_output_is_saved() {
let comp: Component<Standard> = component! {
component comp {
let i: i32 = 4, s: String = "abc";
constraint constr {
m(i: &i32) -> [i] = {
ret![2*i]
};
}
}
};
let constr = &comp.constraints()[0].clone();
let m = &constr.methods()[0].clone();
assert_eq!(m.apply(all_into![3]), Ok(all_into![6]));
}
#[test]
fn component_macro_provides_default_values() {
let _: Component<i32> = component! {
component Comp {
let i: i32 = 0, j: i32;
}
};
let _: Component<String> = component! {
component Comp {
let i: String = "abc", j: String;
}
};
}
}