use std::fmt::Display;
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub struct LoopRange(u32, Option<u32>);
fn add32(x: u32, y: u32) -> u32 {
x.checked_add(y).expect("Arithmetic overflow (add u32)")
}
fn mul32(x: u32, y: u32) -> u32 {
x.checked_mul(y).expect("Arithmetic overflow (mul 32)")
}
impl Display for LoopRange {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
LoopRange(0, Some(1)) => write!(f, "?"),
LoopRange(0, None) => write!(f, "*"),
LoopRange(1, None) => write!(f, "+"),
LoopRange(i, Some(j)) => {
if i == j {
write!(f, "{i}")
} else {
write!(f, "[{i}..{j}]")
}
}
LoopRange(i, None) => write!(f, "[{i}..inf)"),
}
}
}
impl LoopRange {
pub fn finite(i: u32, j: u32) -> LoopRange {
debug_assert!(i <= j);
LoopRange(i, Some(j))
}
pub fn infinite(i: u32) -> LoopRange {
LoopRange(i, None)
}
pub fn opt() -> LoopRange {
LoopRange::finite(0, 1)
}
pub fn star() -> LoopRange {
LoopRange::infinite(0)
}
pub fn plus() -> LoopRange {
LoopRange::infinite(1)
}
pub fn point(k: u32) -> LoopRange {
LoopRange::finite(k, k)
}
pub fn is_finite(&self) -> bool {
matches!(self, LoopRange(_, Some(_)))
}
pub fn is_infinite(&self) -> bool {
matches!(self, LoopRange(_, None))
}
pub fn is_point(&self) -> bool {
match self {
LoopRange(i, Some(j)) => *i == *j,
_ => false,
}
}
pub fn is_zero(&self) -> bool {
matches!(self, LoopRange(0, Some(0)))
}
pub fn is_one(&self) -> bool {
matches!(self, LoopRange(1, Some(1)))
}
pub fn is_all(&self) -> bool {
matches!(self, LoopRange(0, None))
}
pub fn start(&self) -> u32 {
self.0
}
fn end(&self) -> u32 {
debug_assert!(self.is_finite());
self.1.unwrap()
}
pub fn contains(&self, i: u32) -> bool {
match self {
LoopRange(j, Some(k)) => *j <= i && i <= *k,
LoopRange(j, None) => *j <= i,
}
}
pub fn includes(&self, other: &LoopRange) -> bool {
match (&self, other) {
(LoopRange(i1, None), LoopRange(i2, _)) => i1 <= i2,
(LoopRange(i1, Some(j1)), LoopRange(i2, Some(j2))) => i1 <= i2 && j2 <= j1,
_ => false,
}
}
pub fn add(&self, other: &LoopRange) -> LoopRange {
let i = add32(self.start(), other.start());
if self.is_infinite() || other.is_infinite() {
LoopRange::infinite(i)
} else {
let j = add32(self.end(), other.end());
LoopRange::finite(i, j)
}
}
pub fn add_point(&self, x: u32) -> LoopRange {
self.add(&Self::point(x))
}
pub fn scale(&self, k: u32) -> LoopRange {
if k == 0 {
LoopRange::point(0)
} else if self.is_infinite() {
let i = mul32(self.start(), k);
LoopRange::infinite(i)
} else {
let i = mul32(self.start(), k);
let j = mul32(self.end(), k);
LoopRange::finite(i, j)
}
}
pub fn mul(&self, other: &LoopRange) -> LoopRange {
if self.is_zero() || other.is_zero() {
LoopRange::point(0)
} else if self.is_infinite() || other.is_infinite() {
let i = mul32(self.start(), other.start());
LoopRange::infinite(i)
} else {
let i = mul32(self.start(), other.start());
let j = mul32(self.end(), other.end());
LoopRange::finite(i, j)
}
}
pub fn right_mul_is_exact(&self, other: &LoopRange) -> bool {
other.is_point()
|| if self.is_infinite() {
other.start() > 0 || self.start() <= 1
} else {
mul32(other.start(), self.end() - self.start()) >= self.start().saturating_sub(1)
}
}
pub fn shift(&self) -> LoopRange {
match self {
LoopRange(0, None) => LoopRange::infinite(0),
LoopRange(0, Some(0)) => LoopRange::point(0),
LoopRange(0, Some(j)) => LoopRange::finite(0, *j - 1),
LoopRange(i, None) => LoopRange::infinite(*i - 1),
LoopRange(i, Some(j)) => LoopRange::finite(*i - 1, *j - 1),
}
}
}
#[allow(clippy::uninlined_format_args)]
#[cfg(test)]
mod test {
use super::LoopRange;
fn make_examples() -> Vec<LoopRange> {
vec![
LoopRange::finite(2, 3),
LoopRange::finite(4, 9),
LoopRange::finite(1, 3),
LoopRange::infinite(3),
LoopRange::infinite(20),
LoopRange::point(0),
LoopRange::point(1),
LoopRange::point(2),
LoopRange::point(5),
LoopRange::opt(),
LoopRange::star(),
LoopRange::plus(),
]
}
#[test]
fn basic() {
println!("Test examples");
for rng in make_examples() {
println!(
"range {}: finite: {}, infinite: {}, point: {}",
rng,
rng.is_finite(),
rng.is_infinite(),
rng.is_point()
);
}
println!()
}
#[test]
fn test_shift() {
println!("Shift tests");
for rng in make_examples() {
println!("shift({}) = {}", rng, rng.shift());
}
println!()
}
#[test]
fn test_add() {
println!("Add tests");
let v = make_examples();
for r in &v {
for s in &v {
println!("add({}, {}) = {}", r, s, r.add(s))
}
}
println!()
}
#[test]
fn test_scale() {
println!("Scaling tests");
for r in make_examples() {
for k in 0..5 {
let s = r.scale(k);
println!("scale({}, {}) = {}", r, k, &s);
match k {
0 => assert!(s.is_zero()),
1 => assert_eq!(s, r),
_ => assert_eq!(s.is_finite(), r.is_finite()),
}
}
}
println!();
}
#[test]
fn test_mul() {
println!("Product tests");
let v = make_examples();
for r in &v {
for s in &v {
let product = r.mul(s);
let exact = r.right_mul_is_exact(s);
println!("mul({}, {}) = {} (exact: {})", r, s, product, exact);
if r.is_zero() || s.is_zero() {
assert!(product.is_zero());
}
if product.is_point() {
assert!(exact);
}
}
}
println!()
}
#[test]
fn test_mul_exact() {
let v = [
LoopRange::point(2),
LoopRange::finite(2, 3),
LoopRange::infinite(2),
];
for r in &v {
assert!(r.right_mul_is_exact(&LoopRange::point(3)));
}
let opt = LoopRange::opt();
let star = LoopRange::star();
let plus = LoopRange::plus();
for r in &v {
assert!(!r.right_mul_is_exact(&opt));
assert!(!r.right_mul_is_exact(&star));
}
assert!(!v[0].right_mul_is_exact(&plus));
assert!(v[1].right_mul_is_exact(&plus));
assert!(v[2].right_mul_is_exact(&plus));
assert!(plus.right_mul_is_exact(&opt));
assert_eq!(plus.mul(&opt), star);
assert!(star.right_mul_is_exact(&opt));
assert_eq!(star.mul(&opt), star);
assert!(opt.right_mul_is_exact(&opt));
assert_eq!(opt.mul(&opt), opt);
}
}