use std::fmt::Write as _;
use crate::descriptor::{
BuiltinTypeId, DynamicHasher, FormatSink, Payload, Tracer, TypeDescriptor,
};
#[repr(C)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct RangeVal {
start: i64,
end: i64,
}
impl RangeVal {
#[must_use]
pub const fn new(start: i64, end: i64) -> RangeVal {
RangeVal {
start,
end: if end < start { start } else { end },
}
}
#[must_use]
pub const fn new_inclusive(start: i64, end: i64) -> RangeVal {
match end.checked_add(1) {
Some(exclusive) => RangeVal::new(start, exclusive),
None => RangeVal {
start,
end: i64::MAX,
},
}
}
#[must_use]
pub const fn start(&self) -> i64 {
self.start
}
#[must_use]
pub const fn end(&self) -> i64 {
self.end
}
#[must_use]
pub const fn len(&self) -> i128 {
self.end as i128 - self.start as i128
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.end == self.start
}
#[must_use]
pub fn get(&self, index: i64) -> Option<i64> {
if index < 0 || i128::from(index) >= self.len() {
return None;
}
i64::try_from(self.start as i128 + i128::from(index)).ok()
}
}
unsafe fn range_trace(_payload: *mut u8, _tracer: &mut dyn Tracer) {
}
unsafe fn range_drop(_payload: *mut u8) {
}
unsafe fn range_format(payload: *const u8, out: &mut FormatSink<'_>) {
let r = unsafe { &*(payload as *const RangeVal) };
let _ = write!(out, "{}..{}", r.start, r.end);
}
unsafe fn range_equals(a: *const u8, b: *const u8) -> bool {
let ra = unsafe { &*(a as *const RangeVal) };
let rb = unsafe { &*(b as *const RangeVal) };
ra == rb
}
unsafe fn range_hash(payload: *const u8, hasher: &mut dyn DynamicHasher) {
let r = unsafe { &*(payload as *const RangeVal) };
hasher.write_bytes(&r.start.to_le_bytes());
hasher.write_bytes(&r.end.to_le_bytes());
}
unsafe fn range_compare(a: *const u8, b: *const u8) -> std::cmp::Ordering {
let ra = unsafe { &*(a as *const RangeVal) };
let rb = unsafe { &*(b as *const RangeVal) };
(ra.start, ra.end).cmp(&(rb.start, rb.end))
}
pub static RANGE: TypeDescriptor = TypeDescriptor::builtin::<RangeVal>(
BuiltinTypeId::Range,
"Range",
range_trace,
range_drop,
range_format,
Some(range_equals),
Some(range_hash),
Some(range_compare),
);
pub static RANGE_PAYLOAD: Payload<RangeVal> = Payload::new(&RANGE);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_descending_range_is_empty_and_cannot_be_built_inverted() {
let down = RangeVal::new(5, 0);
assert!(down.is_empty());
assert_eq!(down.len(), 0);
assert_eq!(down.get(0), None);
assert_eq!(down.start(), 5);
assert_eq!(down.end(), 5);
assert_eq!(RangeVal::new(i64::MIN, i64::MAX).len(), u64::MAX as i128);
assert!(RangeVal::new(i64::MAX, i64::MIN).is_empty());
}
#[test]
fn inclusive_and_half_open_differ_by_exactly_one_element() {
let half = RangeVal::new(1, 5);
let incl = RangeVal::new_inclusive(1, 5);
assert_eq!(half.len(), 4);
assert_eq!(incl.len(), 5);
assert_eq!(half.get(3), Some(4));
assert_eq!(half.get(4), None);
assert_eq!(incl.get(4), Some(5));
assert_eq!(incl.get(5), None);
assert_eq!(RangeVal::new_inclusive(1, 4), half);
assert!(RangeVal::new_inclusive(5, 4).is_empty());
assert_eq!(RangeVal::new_inclusive(5, 5).len(), 1);
}
#[test]
fn an_inclusive_range_to_the_last_int_keeps_that_int() {
let r = RangeVal::new_inclusive(i64::MAX - 2, i64::MAX);
assert_eq!(r.end(), i64::MAX);
assert_eq!(r.len(), 2);
assert_eq!(r.get(0), Some(i64::MAX - 2));
assert_eq!(r.get(1), Some(i64::MAX - 1));
assert_eq!(r.get(2), None);
}
#[test]
fn an_out_of_range_index_has_no_element() {
let r = RangeVal::new(-3, 3);
assert_eq!(r.len(), 6);
assert_eq!(r.get(0), Some(-3));
assert_eq!(r.get(5), Some(2));
assert_eq!(r.get(6), None);
assert_eq!(r.get(-1), None);
assert_eq!(r.get(i64::MAX), None);
}
#[test]
fn range_descriptor_reports_its_capabilities() {
assert!(RANGE.is_equatable() && RANGE.is_hashable());
assert_eq!(RANGE.name, "Range");
assert!(RANGE.is_orderable());
}
#[test]
fn range_compare_is_start_then_end_and_agrees_with_equality() {
let cmp = |a: &RangeVal, b: &RangeVal| unsafe {
range_compare((a as *const RangeVal).cast(), (b as *const RangeVal).cast())
};
let (a, b, c) = (
RangeVal::new(1, 4),
RangeVal::new(1, 5),
RangeVal::new(2, 3),
);
assert_eq!(cmp(&a, &b), std::cmp::Ordering::Less);
assert_eq!(cmp(&b, &c), std::cmp::Ordering::Less);
assert_eq!(cmp(&c, &a), std::cmp::Ordering::Greater);
assert_eq!(
cmp(&RangeVal::new_inclusive(1, 4), &b),
std::cmp::Ordering::Equal
);
}
}