#![allow(dead_code, clippy::redundant_pub_crate)]
use core::num::NonZeroU64;
#[cfg(kani)]
mod proofs;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub(crate) struct Span {
pub(crate) station: u32,
pub(crate) start: NonZeroU64,
pub(crate) end: NonZeroU64,
}
impl Span {
pub(crate) fn new(station: u32, start: NonZeroU64, end: NonZeroU64) -> Option<Self> {
(start <= end).then_some(Self {
station,
start,
end,
})
}
pub(crate) const fn contains(&self, station: u32, dot: NonZeroU64) -> bool {
station == self.station && self.start.get() <= dot.get() && dot.get() <= self.end.get()
}
pub(crate) const fn abuts(&self, other: &Self) -> bool {
self.station == other.station && self.end.get().saturating_add(1) == other.start.get()
}
pub(crate) fn coalesce(self, other: Self) -> Result<Self, (Self, Self)> {
if self.station != other.station {
return Err((self, other));
}
let mergeable = self.start.get() <= other.end.get().saturating_add(1)
&& other.start.get() <= self.end.get().saturating_add(1);
if !mergeable {
return Err((self, other));
}
let start = self.start.min(other.start);
let end = self.end.max(other.end);
Ok(Self {
station: self.station,
start,
end,
})
}
pub(crate) fn split_at(self, seq: NonZeroU64) -> (Self, Option<Self>) {
if seq <= self.start || seq > self.end {
return (self, None);
}
let head_end = NonZeroU64::new(seq.get() - 1).unwrap_or(self.start);
let head = Self {
station: self.station,
start: self.start,
end: head_end,
};
let tail = Self {
station: self.station,
start: seq,
end: self.end,
};
(head, Some(tail))
}
pub(crate) const fn len(&self) -> u64 {
(self.end.get() - self.start.get()) + 1
}
}
#[cfg(test)]
mod tests {
use super::Span;
use core::num::NonZeroU64;
use proptest::prelude::*;
fn arb_span(station: u32) -> impl Strategy<Value = Span> {
(1u64..=40, 1u64..=40).prop_map(move |(a, b)| {
let (lo, hi) = (a.min(b), a.max(b));
Span {
station,
start: NonZeroU64::new(lo).unwrap(),
end: NonZeroU64::new(hi).unwrap(),
}
})
}
fn arb_dot() -> impl Strategy<Value = NonZeroU64> {
(1u64..=45).prop_map(|d| NonZeroU64::new(d).unwrap())
}
proptest! {
#[test]
fn contains_agrees_with_range(span in arb_span(0), dot in arb_dot()) {
let by_arith = span.start <= dot && dot <= span.end;
prop_assert_eq!(span.contains(0, dot), by_arith);
prop_assert!(!span.contains(1, dot));
}
#[test]
fn len_is_run_cardinality(span in arb_span(0)) {
prop_assert_eq!(span.len(), span.end.get() - span.start.get() + 1);
prop_assert!(span.len() >= 1);
}
#[test]
fn coalesce_is_commutative(a in arb_span(0), b in arb_span(0)) {
match (a.coalesce(b), b.coalesce(a)) {
(Ok(ab), Ok(ba)) => prop_assert_eq!(ab, ba),
(Err((a1, b1)), Err((b2, a2))) => {
prop_assert_eq!(a1, a2);
prop_assert_eq!(b1, b2);
}
_ => prop_assert!(false, "coalesce disagrees on mergeability across argument order"),
}
}
#[test]
fn coalesce_is_associative_where_defined(
a in arb_span(0), b in arb_span(0), c in arb_span(0),
) {
if let (Ok(ab), Ok(bc)) = (a.coalesce(b), b.coalesce(c))
&& let (Ok(left), Ok(right)) = (ab.coalesce(c), a.coalesce(bc))
{
prop_assert_eq!(left, right);
}
}
#[test]
fn split_then_coalesce_is_identity(span in arb_span(0), seq in arb_dot()) {
let (head, tail) = span.split_at(seq);
if let Some(tail) = tail {
prop_assert!(head.abuts(&tail));
prop_assert_eq!(head.coalesce(tail), Ok(span));
} else {
prop_assert_eq!(head, span);
}
}
#[test]
fn abutting_spans_coalesce(a in arb_span(0), b in arb_span(0)) {
if a.abuts(&b) {
let merged = a.coalesce(b).expect("abutting spans merge");
prop_assert_eq!(merged.start, a.start);
prop_assert_eq!(merged.end, b.end);
}
}
}
}