macro_rules! impl_score_ops {
($type:ident { $($field:ident),+ } => $ctor:ident) => {
impl PartialOrd for $type {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl std::ops::Add for $type {
type Output = Self;
fn add(self, other: Self) -> Self {
$type::$ctor( $(self.$field + other.$field),+ )
}
}
impl std::ops::Sub for $type {
type Output = Self;
fn sub(self, other: Self) -> Self {
$type::$ctor( $(self.$field - other.$field),+ )
}
}
impl std::ops::Neg for $type {
type Output = Self;
fn neg(self) -> Self {
$type::$ctor( $(-self.$field),+ )
}
}
};
}
macro_rules! impl_score_scale {
($type:ident { $($field:ident),+ } => $ctor:ident) => {
fn multiply(&self, multiplicand: f64) -> Self {
$type::$ctor( $( (self.$field as f64 * multiplicand).round() as i64 ),+ )
}
fn divide(&self, divisor: f64) -> Self {
$type::$ctor( $( (self.$field as f64 / divisor).round() as i64 ),+ )
}
fn abs(&self) -> Self {
$type::$ctor( $( self.$field.abs() ),+ )
}
};
}
macro_rules! impl_score_parse {
($type:ident { $($field:ident => $suffix:literal),+ } => $ctor:ident) => {
impl $crate::score::traits::ParseableScore for $type {
fn parse(s: &str) -> Result<Self, $crate::score::traits::ScoreParseError> {
let s = s.trim();
let parts: Vec<&str> = s.split('/').collect();
let suffixes: &[&str] = &[ $($suffix),+ ];
let count = suffixes.len();
if parts.len() != count {
return Err($crate::score::traits::ScoreParseError {
message: format!(
"Invalid {} format '{}': expected {} parts separated by '/'",
stringify!($type), s, count
),
});
}
let mut _idx = 0usize;
$(
let $field = {
let part = parts[_idx].trim();
let num_str = part.strip_suffix($suffix).ok_or_else(|| {
$crate::score::traits::ScoreParseError {
message: format!(
"{} part '{}' must end with '{}'",
stringify!($field), part, $suffix
),
}
})?;
let val = num_str.parse::<i64>().map_err(|e| {
$crate::score::traits::ScoreParseError {
message: format!(
"Invalid {} score '{}': {}",
$suffix, num_str, e
),
}
})?;
_idx += 1;
val
};
)+
Ok($type::$ctor( $($field),+ ))
}
fn to_string_repr(&self) -> String {
let mut parts = Vec::new();
$(
parts.push(format!("{}{}", self.$field, $suffix));
)+
parts.join("/")
}
}
};
}