#[repr(C)]
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ByteRange {
pub tag: u32,
pub start: u32,
pub end: u32,
}
impl ByteRange {
#[must_use]
pub const fn new(tag: u32, start: u32, end: u32) -> Self {
assert!(
end >= start,
"ByteRange::new requires end >= start. Fix: pass half-open byte ranges as [start, end)."
);
Self { tag, start, end }
}
#[must_use]
pub const fn len(&self) -> u32 {
self.end - self.start
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.end == self.start
}
#[must_use]
pub const fn contains(&self, other: &ByteRange) -> bool {
self.start <= other.start && other.end <= self.end
}
#[must_use]
pub const fn ends_before(&self, other: &ByteRange) -> bool {
self.end <= other.start
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Match {
pub pattern_id: u32,
pub start: u32,
pub end: u32,
}
impl Match {
#[must_use]
pub const fn new(pattern_id: u32, start: u32, end: u32) -> Self {
Self {
pattern_id,
start,
end,
}
}
}
impl From<Match> for ByteRange {
fn from(value: Match) -> Self {
ByteRange::new(value.pattern_id, value.start, value.end)
}
}
impl From<ByteRange> for Match {
fn from(value: ByteRange) -> Self {
Match::new(value.tag, value.start, value.end)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
#[test]
fn construction() {
let m = Match::new(1, 10, 20);
assert_eq!(m.pattern_id, 1);
assert_eq!(m.start, 10);
assert_eq!(m.end, 20);
}
#[test]
fn ordering() {
let a = Match::new(0, 5, 10);
let b = Match::new(0, 15, 20);
let c = Match::new(1, 0, 5);
let mut v = [c, a, b];
v.sort();
assert_eq!(v[0].start, 5);
assert_eq!(v[1].start, 15);
assert_eq!(v[2].pattern_id, 1);
}
#[test]
fn clone_and_eq() {
let a = Match::new(1, 0, 100);
let b = a;
assert_eq!(a, b);
}
#[test]
fn hash_consistency() {
let mut set = HashSet::new();
let m = Match::new(1, 0, 10);
set.insert(m);
assert!(set.contains(&Match::new(1, 0, 10)));
assert!(!set.contains(&Match::new(2, 0, 10)));
}
#[test]
fn byte_range_bridge_preserves_fields() {
let range = ByteRange::new(7, 11, 22);
let matched: Match = range.into();
assert_eq!(matched.pattern_id, 7);
assert_eq!(matched.start, 11);
assert_eq!(matched.end, 22);
let roundtrip: ByteRange = matched.into();
assert_eq!(roundtrip, range);
}
#[test]
#[should_panic(expected = "ByteRange::new requires end >= start")]
fn byte_range_rejects_reversed_ranges() {
let _ = ByteRange::new(1, 10, 9);
}
}