use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum IntWidth {
I8,
U8,
I16,
U16,
I32,
U32,
U64,
}
macro_rules! define_int_width_spec {
($($variant:ident => {
bits: $bits:expr,
signed: $signed:expr,
mask: $mask:expr,
sign_shift: $sign_shift:expr,
min_i64: $min_i64:expr,
max_i64: $max_i64:expr,
max_u64: $max_u64:expr,
name: $name:expr,
};)*) => {
impl IntWidth {
pub const ALL: [IntWidth; 7] = [
$(IntWidth::$variant,)*
];
#[inline]
pub const fn bits(self) -> u32 {
match self {
$(IntWidth::$variant => $bits,)*
}
}
#[inline]
pub const fn is_signed(self) -> bool {
match self {
$(IntWidth::$variant => $signed,)*
}
}
#[inline]
pub const fn is_unsigned(self) -> bool {
!self.is_signed()
}
#[inline]
pub const fn mask(self) -> u64 {
match self {
$(IntWidth::$variant => $mask,)*
}
}
#[inline]
pub const fn sign_shift(self) -> u32 {
match self {
$(IntWidth::$variant => $sign_shift,)*
}
}
#[inline]
pub const fn min_value(self) -> i64 {
match self {
$(IntWidth::$variant => $min_i64,)*
}
}
#[inline]
pub const fn max_value(self) -> i64 {
match self {
$(IntWidth::$variant => $max_i64,)*
}
}
#[inline]
pub const fn max_unsigned(self) -> u64 {
match self {
$(IntWidth::$variant => $max_u64,)*
}
}
#[inline]
pub const fn type_name(self) -> &'static str {
match self {
$(IntWidth::$variant => $name,)*
}
}
#[inline]
pub const fn truncate(self, value: i64) -> i64 {
match self {
$(IntWidth::$variant => {
if $signed {
let masked = (value as u64) & $mask;
if masked & (1u64 << $sign_shift) != 0 {
(masked | !$mask) as i64
} else {
masked as i64
}
} else if $bits == 64 {
value
} else {
((value as u64) & $mask) as i64
}
})*
}
}
#[inline]
pub const fn truncate_u64(self, value: u64) -> u64 {
match self {
$(IntWidth::$variant => {
if $bits == 64 {
value } else if $signed {
let masked = value & $mask;
if masked & (1u64 << $sign_shift) != 0 {
masked | !$mask
} else {
masked
}
} else {
value & $mask
}
})*
}
}
pub fn from_name(name: &str) -> Option<IntWidth> {
match name {
$($name => Some(IntWidth::$variant),)*
_ => None,
}
}
}
};
}
define_int_width_spec! {
I8 => {
bits: 8,
signed: true,
mask: 0xFF_u64,
sign_shift: 7,
min_i64: -128_i64,
max_i64: 127_i64,
max_u64: 127_u64,
name: "i8",
};
U8 => {
bits: 8,
signed: false,
mask: 0xFF_u64,
sign_shift: 7,
min_i64: 0_i64,
max_i64: 255_i64,
max_u64: 255_u64,
name: "u8",
};
I16 => {
bits: 16,
signed: true,
mask: 0xFFFF_u64,
sign_shift: 15,
min_i64: -32768_i64,
max_i64: 32767_i64,
max_u64: 32767_u64,
name: "i16",
};
U16 => {
bits: 16,
signed: false,
mask: 0xFFFF_u64,
sign_shift: 15,
min_i64: 0_i64,
max_i64: 65535_i64,
max_u64: 65535_u64,
name: "u16",
};
I32 => {
bits: 32,
signed: true,
mask: 0xFFFF_FFFF_u64,
sign_shift: 31,
min_i64: -2147483648_i64,
max_i64: 2147483647_i64,
max_u64: 2147483647_u64,
name: "i32",
};
U32 => {
bits: 32,
signed: false,
mask: 0xFFFF_FFFF_u64,
sign_shift: 31,
min_i64: 0_i64,
max_i64: 4294967295_i64,
max_u64: 4294967295_u64,
name: "u32",
};
U64 => {
bits: 64,
signed: false,
mask: u64::MAX,
sign_shift: 63,
min_i64: 0_i64,
max_i64: i64::MAX,
max_u64: u64::MAX,
name: "u64",
};
}
impl IntWidth {
pub fn join(a: IntWidth, b: IntWidth) -> Result<IntWidth, ()> {
if a == b {
return Ok(a);
}
if a.is_signed() == b.is_signed() {
return Ok(if a.bits() >= b.bits() { a } else { b });
}
let (unsigned, signed) = if a.is_unsigned() { (a, b) } else { (b, a) };
if unsigned == IntWidth::U64 {
return Err(());
}
match (unsigned, signed) {
(IntWidth::U8, IntWidth::I8) => Ok(IntWidth::I16),
(IntWidth::U8, s) => Ok(s),
(IntWidth::U16, IntWidth::I8 | IntWidth::I16) => Ok(IntWidth::I32),
(IntWidth::U16, IntWidth::I32) => Ok(IntWidth::I32),
(IntWidth::U32, _) => Err(()),
_ => Err(()),
}
}
#[inline]
pub const fn in_range_i64(self, value: i64) -> bool {
value >= self.min_value() && value <= self.max_value()
}
#[inline]
pub const fn in_range_u64(self, value: u64) -> bool {
value <= self.max_unsigned()
}
}
impl std::fmt::Display for IntWidth {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.type_name())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn truncate_i8_boundaries() {
assert_eq!(IntWidth::I8.truncate(127), 127);
assert_eq!(IntWidth::I8.truncate(128), -128);
assert_eq!(IntWidth::I8.truncate(-128), -128);
assert_eq!(IntWidth::I8.truncate(-129), 127);
assert_eq!(IntWidth::I8.truncate(255), -1);
assert_eq!(IntWidth::I8.truncate(256), 0);
}
#[test]
fn truncate_u8_boundaries() {
assert_eq!(IntWidth::U8.truncate(0), 0);
assert_eq!(IntWidth::U8.truncate(255), 255);
assert_eq!(IntWidth::U8.truncate(256), 0);
assert_eq!(IntWidth::U8.truncate(-1), 255);
}
#[test]
fn truncate_i16_boundaries() {
assert_eq!(IntWidth::I16.truncate(32767), 32767);
assert_eq!(IntWidth::I16.truncate(32768), -32768);
assert_eq!(IntWidth::I16.truncate(-32768), -32768);
assert_eq!(IntWidth::I16.truncate(-32769), 32767);
}
#[test]
fn truncate_u16_boundaries() {
assert_eq!(IntWidth::U16.truncate(0), 0);
assert_eq!(IntWidth::U16.truncate(65535), 65535);
assert_eq!(IntWidth::U16.truncate(65536), 0);
assert_eq!(IntWidth::U16.truncate(-1), 65535);
}
#[test]
fn truncate_i32_boundaries() {
assert_eq!(IntWidth::I32.truncate(2147483647), 2147483647);
assert_eq!(IntWidth::I32.truncate(2147483648), -2147483648);
assert_eq!(IntWidth::I32.truncate(-2147483648), -2147483648);
}
#[test]
fn truncate_u32_boundaries() {
assert_eq!(IntWidth::U32.truncate(0), 0);
assert_eq!(IntWidth::U32.truncate(4294967295), 4294967295);
assert_eq!(IntWidth::U32.truncate(4294967296), 0);
assert_eq!(IntWidth::U32.truncate(-1), 4294967295);
}
#[test]
fn truncate_u64_identity() {
assert_eq!(IntWidth::U64.truncate(0), 0);
assert_eq!(IntWidth::U64.truncate(i64::MAX), i64::MAX);
assert_eq!(IntWidth::U64.truncate(-1), -1); }
#[test]
fn truncate_u64_unsigned() {
assert_eq!(IntWidth::U64.truncate_u64(0), 0);
assert_eq!(IntWidth::U64.truncate_u64(u64::MAX), u64::MAX);
assert_eq!(IntWidth::U64.truncate_u64(u64::MAX - 1), u64::MAX - 1);
}
#[test]
fn join_same_width() {
assert_eq!(IntWidth::join(IntWidth::I8, IntWidth::I8), Ok(IntWidth::I8));
assert_eq!(
IntWidth::join(IntWidth::U64, IntWidth::U64),
Ok(IntWidth::U64)
);
}
#[test]
fn join_same_sign_different_width() {
assert_eq!(
IntWidth::join(IntWidth::I8, IntWidth::I16),
Ok(IntWidth::I16)
);
assert_eq!(
IntWidth::join(IntWidth::I16, IntWidth::I32),
Ok(IntWidth::I32)
);
assert_eq!(
IntWidth::join(IntWidth::U8, IntWidth::U16),
Ok(IntWidth::U16)
);
assert_eq!(
IntWidth::join(IntWidth::U16, IntWidth::U32),
Ok(IntWidth::U32)
);
}
#[test]
fn join_mixed_sign_widens() {
assert_eq!(
IntWidth::join(IntWidth::U8, IntWidth::I8),
Ok(IntWidth::I16)
);
assert_eq!(
IntWidth::join(IntWidth::I8, IntWidth::U8),
Ok(IntWidth::I16)
);
assert_eq!(
IntWidth::join(IntWidth::U16, IntWidth::I16),
Ok(IntWidth::I32)
);
assert_eq!(
IntWidth::join(IntWidth::U8, IntWidth::I16),
Ok(IntWidth::I16)
);
assert_eq!(
IntWidth::join(IntWidth::U8, IntWidth::I32),
Ok(IntWidth::I32)
);
assert_eq!(
IntWidth::join(IntWidth::U16, IntWidth::I32),
Ok(IntWidth::I32)
);
}
#[test]
fn join_u64_signed_error() {
assert_eq!(IntWidth::join(IntWidth::U64, IntWidth::I8), Err(()));
assert_eq!(IntWidth::join(IntWidth::U64, IntWidth::I16), Err(()));
assert_eq!(IntWidth::join(IntWidth::U64, IntWidth::I32), Err(()));
assert_eq!(IntWidth::join(IntWidth::I8, IntWidth::U64), Err(()));
}
#[test]
fn join_u32_signed_promotes_to_i64() {
assert_eq!(IntWidth::join(IntWidth::U32, IntWidth::I8), Err(()));
assert_eq!(IntWidth::join(IntWidth::U32, IntWidth::I32), Err(()));
}
#[test]
fn from_name_roundtrip() {
for w in IntWidth::ALL {
assert_eq!(IntWidth::from_name(w.type_name()), Some(w));
}
assert_eq!(IntWidth::from_name("i64"), None);
assert_eq!(IntWidth::from_name("float"), None);
}
#[test]
fn in_range_checks() {
assert!(IntWidth::I8.in_range_i64(0));
assert!(IntWidth::I8.in_range_i64(127));
assert!(IntWidth::I8.in_range_i64(-128));
assert!(!IntWidth::I8.in_range_i64(128));
assert!(!IntWidth::I8.in_range_i64(-129));
assert!(IntWidth::U8.in_range_i64(0));
assert!(IntWidth::U8.in_range_i64(255));
assert!(!IntWidth::U8.in_range_i64(-1));
assert!(!IntWidth::U8.in_range_i64(256));
assert!(IntWidth::U64.in_range_u64(u64::MAX));
assert!(IntWidth::U64.in_range_u64(0));
}
#[test]
fn display_impl() {
assert_eq!(format!("{}", IntWidth::I8), "i8");
assert_eq!(format!("{}", IntWidth::U64), "u64");
}
}