macro_rules! range_u64 {
( $(#[$comment:meta])? $T: ident, $display_name: expr) => {
range!($(#[$comment])? $T, $display_name, u64, serialize_u64);
from_u64!($T);
mul!($T, u64, u32, u16, u8);
div!($T, u64, u32, u16, u8);
}
}
macro_rules! range_u128 {
( $(#[$comment:meta])? $T: ident, $display_name: expr) => {
range!($(#[$comment])? $T, $display_name, u128, serialize_u128);
from_u128!($T);
mul!($T, u128, u64, u32, u16, u8);
div!($T, u128, u64, u32, u16, u8);
}
}
macro_rules! range {
( $(#[$comment:meta])? $T: ident, $display_name: expr, $inner:ty, $serde_method:ident) => {
$(
#[$comment]
)?
#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct $T(pub $inner);
checked_add!($T);
debug_macro!($T);
display!($T, $display_name);
serde_macro!($T, $serde_method);
sum!($T);
add!($T);
add_assign!($T);
sub!($T);
sub_assign!($T);
rem!($T);
deref!($T, $inner);
};
}
macro_rules! add {
($T:ident) => {
impl std::ops::Add<$T> for $T {
type Output = $T;
fn add(self, rhs: $T) -> $T {
$T(self.0 + *rhs)
}
}
};
}
macro_rules! sub {
($T:ident) => {
impl std::ops::Sub<$T> for $T {
type Output = $T;
fn sub(self, rhs: $T) -> $T {
$T(self.0 - *rhs)
}
}
};
}
macro_rules! add_assign {
($T:ident) => {
impl std::ops::AddAssign<$T> for $T {
fn add_assign(&mut self, rhs: $T) {
*self = $T(self.0 + *rhs)
}
}
};
}
macro_rules! sub_assign {
($T:ident) => {
impl std::ops::SubAssign<$T> for $T {
fn sub_assign(&mut self, rhs: $T) {
*self = $T(self.0 - *rhs)
}
}
};
}
macro_rules! deref {
($T:ident, $inner:ty) => {
impl std::ops::Deref for $T {
type Target = $inner;
fn deref(&self) -> &$inner {
&self.0
}
}
};
}
macro_rules! from {
($T:ident, $inner:ty, $($t:ty),+) => {
$(
impl From<$t> for $T {
fn from(t: $t) -> $T {
$T(<$inner>::from(t))
}
}
)+
impl From<usize> for $T {
fn from(t: usize) -> $T {
$T(t as $inner)
}
}
impl From<$inner> for $T {
fn from(t: $inner) -> $T {
$T(t)
}
}
};
}
macro_rules! from_u64 {
($T:ident) => {
from!($T, u64, u32, u16, u8);
};
}
macro_rules! from_u128 {
($T:ident) => {
from!($T, u128, u64, u32, u16, u8);
};
}
macro_rules! debug_macro {
($T:ident) => {
impl std::fmt::Debug for $T {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, stringify!($T))?;
write!(f, "({})", **self)
}
}
};
}
macro_rules! display {
($T:ident, $display_name:expr) => {
impl std::fmt::Display for $T {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} {}", self.0, $display_name)
}
}
};
}
macro_rules! serde_macro {
($T:ident, $serde_method:ident) => {
impl serde::Serialize for $T {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.$serde_method(**self)
}
}
impl<'de> serde::Deserialize<'de> for $T {
fn deserialize<D>(deserializer: D) -> Result<$T, D::Error>
where
D: serde::de::Deserializer<'de>,
{
Ok($T(serde::Deserialize::deserialize(deserializer)?))
}
}
};
}
macro_rules! sum {
($T:ident) => {
impl std::iter::Sum for $T {
fn sum<I: Iterator<Item = $T>>(iter: I) -> $T {
iter.fold($T::default(), std::ops::Add::add)
}
}
};
}
macro_rules! div {
($T:ident, $inner:ty, $($t:ty),+) => {
$(
impl std::ops::Div<$t> for $T {
type Output = $T;
fn div(self, rhs: $t) -> $T {
$T(self.0 / <$inner>::from(rhs))
}
}
)+
impl std::ops::Div<usize> for $T {
type Output = $T;
fn div(self, rhs: usize) -> $T {
$T(self.0 / rhs as $inner)
}
}
impl std::ops::Div<$inner> for $T {
type Output = $T;
fn div(self, rhs: $inner) -> $T {
$T(self.0 / rhs)
}
}
impl std::ops::Div for $T {
type Output = $inner;
fn div(self, rhs: $T) -> $inner {
self.0 / *rhs
}
}
};
}
macro_rules! mul {
($T:ident, $inner:ty, $($t:ty),+) => {
$(
impl std::ops::Mul<$t> for $T {
type Output = $T;
fn mul(self, rhs: $t) -> $T {
$T(self.0 * <$inner>::from(rhs))
}
}
impl std::ops::Mul<$T> for $t {
type Output = $T;
fn mul(self, rhs: $T) -> $T {
$T(rhs.0 * <$inner>::from(self))
}
}
)+
impl std::ops::Mul<$inner> for $T {
type Output = $T;
fn mul(self, rhs: $inner) -> $T {
$T(self.0 * rhs)
}
}
impl std::ops::Mul<$T> for $inner {
type Output = $T;
fn mul(self, rhs: $T) -> $T {
$T(rhs.0 * self)
}
}
impl std::ops::Mul<usize> for $T {
type Output = $T;
fn mul(self, rhs: usize) -> $T {
$T(self.0 * rhs as $inner)
}
}
impl std::ops::Mul<$T> for usize {
type Output = $T;
fn mul(self, rhs: $T) -> $T {
$T(rhs.0 * self as $inner)
}
}
};
}
macro_rules! rem {
($T: ident) => {
impl<T> std::ops::Rem<T> for $T
where
$T: From<T>,
{
type Output = $T;
fn rem(self, rhs: T) -> $T {
$T(self.0 % <$T>::from(rhs).0)
}
}
};
}
macro_rules! checked_add {
($T: ident) => {
impl $T {
pub fn checked_add(&self, other: $T) -> Option<$T> {
self.0.checked_add(other.0).map($T)
}
}
};
}
#[cfg(test)]
mod tests {
range_u64!(Units, "units");
#[test]
fn test_implicit_derivations() {
#[allow(clippy::clone_on_copy)]
let _ = Units(1).clone();
assert_eq!(Units::default(), Units(0));
assert!(Units::default() < Units(1));
assert!(Units(1) >= Units::default());
let z = Units(3);
let mut d = z;
d += Units(1);
assert_eq!(z, Units(3));
assert_eq!(d, Units(4));
}
#[test]
fn test_explicit_derivations() {
assert_eq!(Units::from(3u8) + Units(3), Units(6));
assert_eq!(*Units(3) + 3, 6);
assert_eq!(format!("{:?}", Units(3)), "Units(3)");
assert_eq!(format!("{:}", Units(3)), "3 units");
}
#[test]
fn test_summary_operations() {
assert_eq!(
[Units(2), Units(3)].iter().cloned().sum::<Units>(),
Units(5)
);
}
#[test]
fn test_addition() {
assert_eq!(Units(1) + Units(3), Units(4));
let mut z = Units(1);
z += Units(3);
assert_eq!(z, Units(4));
assert_eq!(Units(u64::MAX).checked_add(Units(1)), None);
}
#[test]
fn test_subtraction() {
assert_eq!(Units(3) - Units(1), Units(2));
let mut z = Units(3);
z -= Units(1);
assert_eq!(z, Units(2));
}
#[test]
fn test_multiplication() {
assert_eq!(Units(3) * 2u8, Units(6));
assert_eq!(2u8 * Units(3), Units(6));
assert_eq!(Units(3) * 2u16, Units(6));
assert_eq!(2u16 * Units(3), Units(6));
assert_eq!(Units(3) * 2u32, Units(6));
assert_eq!(2u32 * Units(3), Units(6));
assert_eq!(Units(3) * 2u64, Units(6));
assert_eq!(2u64 * Units(3), Units(6));
assert_eq!(Units(3) * 2usize, Units(6));
assert_eq!(2usize * Units(3), Units(6));
}
#[test]
fn test_division_and_remainder() {
assert_eq!(Units(3) / Units(2), 1);
assert_eq!(Units(3) % Units(2), Units(1));
assert_eq!(Units(5) / 2u8, Units(2));
assert_eq!(Units(3) % 2u8, Units(1));
assert_eq!(Units(5) / 2u16, Units(2));
assert_eq!(Units(3) % 2u16, Units(1));
assert_eq!(Units(5) / 2u32, Units(2));
assert_eq!(Units(3) % 2u32, Units(1));
assert_eq!(Units(5) / 2u64, Units(2));
assert_eq!(Units(3) % 2u64, Units(1));
assert_eq!(Units(5) / 2usize, Units(2));
assert_eq!(Units(3) % 2usize, Units(1));
}
}