use std::fmt;
use std::marker::PhantomData;
use std::num::NonZeroU32;
pub struct Idx<T> {
raw: NonZeroU32,
_marker: PhantomData<fn() -> T>,
}
impl<T> Idx<T> {
pub const MAX: u32 = u32::MAX - 1;
#[inline]
pub const fn new(raw: u32) -> Self {
assert!(raw <= Self::MAX, "index out of range");
match NonZeroU32::new(raw + 1) {
Some(raw) => Self { raw, _marker: PhantomData },
None => unreachable!(),
}
}
#[inline]
pub fn from_usize(raw: usize) -> Self {
Self::new(u32::try_from(raw).expect("index out of range"))
}
#[inline]
pub const fn raw(self) -> u32 {
self.raw.get() - 1
}
#[inline]
pub const fn index(self) -> usize {
self.raw() as usize
}
}
impl<T> Clone for Idx<T> {
#[inline]
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for Idx<T> {}
impl<T> PartialEq for Idx<T> {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.raw == other.raw
}
}
impl<T> Eq for Idx<T> {}
impl<T> PartialOrd for Idx<T> {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<T> Ord for Idx<T> {
#[inline]
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.raw.cmp(&other.raw)
}
}
impl<T> std::hash::Hash for Idx<T> {
#[inline]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.raw.hash(state);
}
}
impl<T> fmt::Debug for Idx<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = std::any::type_name::<T>();
let short = name.rsplit("::").next().unwrap_or(name);
write!(f, "{short}#{}", self.raw())
}
}
pub struct IdxRange<T> {
start: u32,
end: u32,
_marker: PhantomData<fn() -> T>,
}
impl<T> Clone for IdxRange<T> {
#[inline]
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for IdxRange<T> {}
impl<T> PartialEq for IdxRange<T> {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.start == other.start && self.end == other.end
}
}
impl<T> Eq for IdxRange<T> {}
impl<T> fmt::Debug for IdxRange<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = std::any::type_name::<T>();
let short = name.rsplit("::").next().unwrap_or(name);
write!(f, "{short}#{}..{}", self.start, self.end)
}
}
impl<T> IdxRange<T> {
pub const EMPTY: Self = Self { start: 0, end: 0, _marker: PhantomData };
#[inline]
pub fn new(start: Idx<T>, end: Idx<T>) -> Self {
assert!(start.raw() <= end.raw(), "reversed index range");
Self { start: start.raw(), end: end.raw(), _marker: PhantomData }
}
#[inline]
pub fn empty_at(at: Idx<T>) -> Self {
Self { start: at.raw(), end: at.raw(), _marker: PhantomData }
}
#[inline]
pub const fn len(self) -> usize {
(self.end - self.start) as usize
}
#[inline]
pub const fn is_empty(self) -> bool {
self.start == self.end
}
pub fn iter(self) -> impl Iterator<Item = Idx<T>> {
(self.start..self.end).map(Idx::new)
}
#[inline]
pub const fn as_usize_range(self) -> std::ops::Range<usize> {
self.start as usize..self.end as usize
}
}
#[cfg(test)]
mod tests {
use super::*;
struct Block;
struct Inst;
#[test]
fn an_index_is_four_bytes_and_so_is_an_optional_one() {
assert_eq!(size_of::<Idx<Block>>(), 4);
assert_eq!(size_of::<Option<Idx<Block>>>(), 4);
}
#[test]
fn round_trips_through_usize() {
let i = Idx::<Inst>::from_usize(7);
assert_eq!(i.index(), 7);
assert_eq!(i.raw(), 7);
}
#[test]
fn debug_names_the_table() {
assert_eq!(format!("{:?}", Idx::<Block>::new(3)), "Block#3");
}
#[test]
fn a_range_iterates_half_open() {
let r = IdxRange::new(Idx::<Inst>::new(2), Idx::<Inst>::new(5));
let got: Vec<u32> = r.iter().map(Idx::raw).collect();
assert_eq!(got, vec![2, 3, 4]);
assert_eq!(r.len(), 3);
assert_eq!(r.as_usize_range(), 2..5);
}
#[test]
fn an_empty_range_is_empty() {
let r = IdxRange::empty_at(Idx::<Inst>::new(9));
assert!(r.is_empty());
assert_eq!(r.iter().count(), 0);
}
#[test]
fn the_empty_range_slices_an_empty_table() {
let r = IdxRange::<Inst>::EMPTY;
assert!(r.is_empty());
assert_eq!(r.len(), 0);
let table: Vec<u8> = Vec::new();
assert!(table[r.as_usize_range()].is_empty());
}
#[test]
#[should_panic(expected = "reversed index range")]
fn a_reversed_range_is_rejected() {
let _ = IdxRange::new(Idx::<Inst>::new(5), Idx::<Inst>::new(2));
}
#[test]
#[should_panic(expected = "index out of range")]
fn the_niche_value_is_rejected() {
let _ = Idx::<Inst>::new(u32::MAX);
}
}