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 *n < 1000 { return <u128 as fmt::Display>::fmt(n, f); }
else { n.to_string() },
Self::Signed(n) =>
if (-999..1000).contains(n) { return <i128 as fmt::Display>::fmt(n, f); }
else { n.to_string() },
};
let last = if out.starts_with('-') { 4 } else { 3 };
let mut idx = out.len();
while idx > last {
idx -= 3;
out.insert(idx, '_');
}
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,
}
}
}
#[derive(Clone, Copy)]
struct AnyTwo<TO, FROM>(TO, FROM)
where TO: NumberExt + Into<AnyNum>, FROM: NumberExt + Into<AnyNum>;
impl<TO, FROM> fmt::Display for AnyTwo<TO, FROM>
where TO: NumberExt + Into<AnyNum>, FROM: NumberExt + Into<AnyNum> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let to: AnyNum = TO::MIN_NUMBER.into();
let from: AnyNum = FROM::MIN_NUMBER.into();
let min = (from.signed_inner() < to.signed_inner()).then_some(to);
let to: AnyNum = TO::MAX_NUMBER.into();
let from: AnyNum = FROM::MAX_NUMBER.into();
let max = (to.unsigned_inner() < from.unsigned_inner()).then_some(to);
match (min, max) {
(Some(min), Some(max)) => writeln!(
f,
"\t\tif src <= {min} {{ {min} }}
else if src >= {max} {{ {max} }}
else {{ src as Self }}"
),
(Some(min), None) => writeln!(
f,
"\t\tif src <= {min} {{ {min} }}
else {{ src as Self }}"
),
(None, Some(max)) => writeln!(
f,
"\t\tif src >= {max} {{ {max} }}
else {{ src as Self }}"
),
(None, None) => f.write_str("\t\tsrc as Self\n"),
}
}
}
macro_rules! wrt {
($out:ident, $to:ty as $alias:ty, $($from:ty),+) => ($(
writeln!(
&mut $out,
"impl SaturatingFrom<{from}> for {to} {{
#[inline]
/// # Saturating From `{from}`.
///
/// This method will saturate and recast a `{from}` into a `{to}`, clamping to `{to}::MIN` and `{to}::MAX` as necessary.
fn saturating_from(src: {from}) -> Self {{
{body}\t}}
}}",
from=stringify!($from),
to=stringify!($to),
body=AnyTwo::<$alias, $from>(0, 0),
).unwrap();
)+);
($out:ident, $to:ty, $($from:ty),+) => (
$out.push_str(concat!(
"impl SaturatingFrom<Self> for ", stringify!($to), " {
#[inline]
/// # Saturating From `Self`.
///
/// This implementation is provided for consistency; the value is simply passed through.
fn saturating_from(src: Self) -> Self { src }
}\n"));
wrt!($out, $to as $to, $($from),+);
);
}
fn main() {
println!("cargo:rerun-if-env-changed=CARGO_PKG_VERSION");
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::with_capacity(32_768);
let mut tmp = String::with_capacity(4096);
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 );
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();
tmp.truncate(0);
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
}