1use std::{
5 fmt,
6 ops::RangeInclusive,
7};
8
9#[derive(Debug, Clone)]
10pub struct OutOfRange<T>(pub T, pub RangeInclusive<T>);
11
12impl<T> fmt::Display for OutOfRange<T>
13where
14 T: fmt::Display,
15{
16 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
17 write!(
18 f,
19 "value out of range. got {}, expected ({}..={})",
20 self.0,
21 self.1.start(),
22 self.1.end()
23 )
24 }
25}
26
27macro_rules! constrained_num {
28 (clamp; $name:ty, $base:ty, $range:expr) => {
29 impl $name {
30 pub const fn clamp(value: $base) -> Self {
33 const RANGE: RangeInclusive<$base> = $range;
34 if value > *RANGE.end() {
35 Self(*RANGE.end())
36 } else if value < *RANGE.start() {
37 Self(*RANGE.start())
38 } else {
39 Self(value)
40 }
41 }
42 }
43 };
44 (mask; $name:ty, $base:ty, $range:expr) => {
45 impl $name {
46 pub const fn mask(value: $base) -> Self {
48 const RANGE: RangeInclusive<$base> = $range;
49 if value < *RANGE.start() {
50 Self(*RANGE.start())
51 } else if value > *RANGE.end() {
52 Self(value & *RANGE.end())
53 } else {
54 Self(value)
55 }
56 }
57 }
58 };
59 ($(#[$outer:meta])* $name:ident, $base:ty, $range:expr, $($t:tt),*) => {
60 $(#[$outer])*
61 #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
65 pub struct $name($base);
66
67 #[allow(dead_code, missing_docs)]
68 impl $name {
69 pub const MIN: $base = *$range.start();
70 pub const MAX: $base = *$range.end();
71 pub const BITS: $base = <$base>::BITS - $range.end().leading_zeros();
72 }
73
74 impl Default for $name {
75 fn default() -> Self {
76 Self(*$range.start())
77 }
78 }
79
80 impl fmt::Display for $name {
81 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
82 self.0.fmt(f)
83 }
84 }
85
86 impl Deref for $name {
87 type Target = $base;
88 fn deref(&self) -> &$base {
89 &self.0
90 }
91 }
92
93 impl TryFrom<$base> for $name {
94 type Error = OutOfRange<$base>;
95 fn try_from(other: $base) -> Result<Self, Self::Error> {
96 const RANGE: RangeInclusive<$base> = $range;
97 if RANGE.contains(&other) {
98 Ok(Self(other))
99 } else {
100 Err(OutOfRange(other, RANGE))
101 }
102 }
103 }
104
105 $(constrained_num!($t; $name, $base, $range);)*
106 };
107 ($(#[$outer:meta])* $name:ident, $base:ty, $range:expr) => {
108 constrained_num!($(#[$outer])* $name, $base, $range, clamp, mask);
109 };
110}