use std::{
env,
fmt::{
self,
Write,
},
fs::File,
io::Write as ByteWrite,
path::PathBuf,
};
trait NumberExt: Copy {
const MIN_NUMBER: Self;
const MAX_NUMBER: Self;
}
macro_rules! numext {
($($ty:ty),+) => ($(
impl NumberExt for $ty {
const MIN_NUMBER: Self = Self::MIN;
const MAX_NUMBER: Self = Self::MAX;
}
)+);
}
numext! { u8, u16, u32, u64, u128, i8, i16, i32, i64, i128 }
#[derive(Clone, Copy)]
enum AnyNum {
Unsigned(u128),
Signed(i128),
}
macro_rules! into_any {
($($from:ty),+) => ($(
impl From<$from> for AnyNum {
#[allow(unused_comparisons)] fn from(src: $from) -> Self {
if src < 0 { Self::Signed(src as i128) }
else { Self::Unsigned(src as u128) }
}
}
)+);
}
into_any! { u8, u16, u32, u64, u128, i8, i16, i32, i64, i128 }
impl fmt::Display for AnyNum {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut out = match self {
Self::Unsigned(n) =>
if 1000.gt(n) { return write!(f, "{n}"); }
else { n.to_string() },
Self::Signed(n) =>
if (-999..1000).contains(n) { return write!(f, "{n}"); }
else { n.to_string() },
};
let neg =
if out.starts_with('-') {
out.remove(0);
true
}
else { false };
let mut idx = out.len();
while idx > 3 {
idx -= 3;
out.insert(idx, '_');
}
if neg { out.insert(0, '-'); }
f.write_str(&out)
}
}
impl AnyNum {
const fn signed_inner(self) -> i128 {
match self {
Self::Unsigned(n) =>n as i128,
Self::Signed(n) => n,
}
}
const fn unsigned_inner(self) -> u128 {
match self {
Self::Unsigned(n) => n,
Self::Signed(n) => n as u128,
}
}
}
macro_rules! wrt {
($out:ident, $to:ty as $alias:ty, $($from:ty),+) => ($(
writeln!(
&mut $out,
concat!(
"impl SaturatingFrom<", stringify!($from), "> for ", stringify!($to), "{{\n",
"\t#[inline]\n",
"\t#[doc = \"", "# Saturating From `", stringify!($from), "`\"]\n",
"\t#[doc = \"\"]\n",
"\t#[doc = \"", "This method will safely recast any `", stringify!($from), "` into a `", stringify!($to), "`, clamping the values to `", stringify!($to), "::MIN..=", stringify!($to), "::MAX` to prevent overflow or wrapping.", "\"]\n",
"\tfn saturating_from(src: ", stringify!($from), ") -> Self {{",
),
).unwrap();
write_condition::<$alias, $from>(&mut $out);
writeln!(
&mut $out,
"\t}}\n}}",
).unwrap();
)+);
($out:ident, $to:ty, $($from:ty),+) => (
wrt!($out, $to as $to, $($from),+);
);
}
macro_rules! wrt_self {
($out:ident, $($to:ty),+) => ($(
writeln!(
&mut $out,
concat!(
"impl SaturatingFrom<Self> for ", stringify!($to), "{{\n",
"\t#[inline]",
"\t#[doc = \"# Saturating From `Self`\"]\n",
"\t#[doc = \"\"]\n",
"\t#[doc = \"`Self`-to-`Self` (obviously) requires no saturation; this implementation is a noop.\"]\n",
"\tfn saturating_from(src: Self) -> Self {{ src }}\n",
"}}",
),
).unwrap();
)+);
}
fn main() {
assert_eq!(AnyNum::from(12345_u32).to_string(), "12_345", "Bug: Number formatting is wrong!");
assert_eq!(AnyNum::from(-12345_i32).to_string(), "-12_345", "Bug: Number formatting is wrong!");
let data = build_impls();
File::create(out_path("dactyl-saturation.rs"))
.and_then(|mut f| f.write_all(data.as_bytes()).and_then(|_| f.flush()))
.expect("Unable to save drive data.");
}
fn build_impls() -> String {
let mut out = String::new();
wrt!(out, u8, u16, u32, u64, u128, i8, i16, i32, i64, i128);
wrt!(out, u16, u8, u32, u64, u128, i8, i16, i32, i64, i128);
wrt!(out, u32, u8, u16, u64, u128, i8, i16, i32, i64, i128);
wrt!(out, u64, u8, u16, u32, u128, i8, i16, i32, i64, i128);
wrt!(out, u128, u8, u16, u32, u64, i8, i16, i32, i64, i128);
wrt!(out, i8, u8, u16, u32, u64, u128, i16, i32, i64, i128);
wrt!(out, i16, u8, u16, u32, u64, u128, i8, i32, i64, i128);
wrt!(out, i32, u8, u16, u32, u64, u128, i8, i16, i64, i128);
wrt!(out, i64, u8, u16, u32, u64, u128, i8, i16, i32, i128);
wrt!(out, i128, u8, u16, u32, u64, u128, i8, i16, i32, i64 );
wrt_self!(out, u8, u16, u32, u64, u128, i8, i16, i32, i64, i128);
macro_rules! sized {
($unsigned:ty, $signed:ty) => (
writeln!(
&mut out,
"
#[cfg(target_pointer_width = \"{}\")]
mod sized {{
use super::SaturatingFrom;
impl<T: SaturatingFrom<{unsigned}>> SaturatingFrom<usize> for T {{
#[inline]
/// # Saturating From `usize`
///
/// This blanket implementation uses `{unsigned}` as a go-between, since it is equivalent to `usize`.
fn saturating_from(src: usize) -> T {{
T::saturating_from(src as {unsigned})
}}
}}
impl<T: SaturatingFrom<{signed}>> SaturatingFrom<isize> for T {{
#[inline]
/// # Saturating From `isize`
///
/// This blanket implementation uses `{signed}` as a go-between, since it is equivalent to `isize`.
fn saturating_from(src: isize) -> T {{
T::saturating_from(src as {signed})
}}
}}",
<$unsigned>::BITS,
unsigned=stringify!($unsigned),
signed=stringify!($signed),
).unwrap();
let mut tmp = String::new();
wrt!(tmp, usize as $unsigned, u8, u16, u32, u64, u128, i8, i16, i32, i64, i128);
wrt!(tmp, isize as $signed, u8, u16, u32, u64, u128, i8, i16, i32, i64, i128);
for line in tmp.lines() {
out.push('\t');
out.push_str(line);
out.push('\n');
}
out.push_str("}\n");
);
}
sized!(u16, i16);
sized!(u32, i32);
sized!(u64, i64);
out
}
fn out_path(name: &str) -> PathBuf {
let dir = env::var("OUT_DIR").expect("Missing OUT_DIR.");
let mut out = std::fs::canonicalize(dir).expect("Missing OUT_DIR.");
out.push(name);
out
}
fn write_condition<TO, FROM>(out: &mut String)
where TO: NumberExt + Into<AnyNum>, FROM: NumberExt + Into<AnyNum> {
let to: AnyNum = TO::MIN_NUMBER.into();
let from: AnyNum = FROM::MIN_NUMBER.into();
let min =
if from.signed_inner() < to.signed_inner() { Some(to) }
else { None };
let to: AnyNum = TO::MAX_NUMBER.into();
let from: AnyNum = FROM::MAX_NUMBER.into();
let max =
if to.unsigned_inner() < from.unsigned_inner() { Some(to) }
else { None };
match (min, max) {
(Some(min), Some(max)) => writeln!(
out,
"\t\tif src <= {min} {{ {min} }}
else if src >= {max} {{ {max} }}
else {{ src as Self }}"
),
(Some(min), None) => writeln!(
out,
"\t\tif src <= {min} {{ {min} }}
else {{ src as Self }}"
),
(None, Some(max)) => writeln!(
out,
"\t\tif src >= {max} {{ {max} }}
else {{ src as Self }}"
),
(None, None) => writeln!(out, "\t\tsrc as Self"),
}.unwrap();
}