use core::cmp::Ordering;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
pub struct FileLoc {
pub line: u32,
pub col: u32,
}
impl FileLoc {
#[inline]
pub const fn new(line: u32, col: u32) -> Self {
Self { line, col }
}
}
impl PartialOrd for FileLoc {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for FileLoc {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
match self.line.cmp(&other.line) {
Ordering::Equal => self.col.cmp(&other.col),
ord => ord,
}
}
}
impl From<(u32, u32)> for FileLoc {
#[inline]
fn from((line, col): (u32, u32)) -> Self {
Self::new(line, col)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
pub struct FileLocRange {
pub start: FileLoc,
pub end: FileLoc,
}
impl FileLocRange {
#[inline]
pub const fn try_new(start: FileLoc, end: FileLoc) -> Option<Self> {
let ord = if start.line < end.line {
true
} else if start.line == end.line {
start.col <= end.col
} else {
false
};
if ord { Some(Self { start, end }) } else { None }
}
#[inline]
pub fn new(start: FileLoc, end: FileLoc) -> Self {
match Self::try_new(start, end) {
Some(r) => r,
None => Self { start, end: start },
}
}
#[inline]
pub const fn empty_at(start: FileLoc) -> Self {
Self { start, end: start }
}
#[inline]
pub fn contains_loc(&self, loc: FileLoc) -> bool {
self.start <= loc && loc < self.end
}
#[inline]
pub fn contains_range(&self, other: FileLocRange) -> bool {
self.start <= other.start && other.end <= self.end
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn file_loc_orderings() {
let a = FileLoc::new(1, 4);
let b = FileLoc::new(1, 5);
let c = FileLoc::new(2, 0);
assert!(a < b);
assert!(b < c);
assert!(a <= a);
assert!(a == FileLoc::new(1, 4));
assert!(b > a && c > b);
}
#[test]
fn range_contains_loc_is_half_open() {
let r = FileLocRange::new(FileLoc::new(1, 0), FileLoc::new(1, 5));
assert!(r.contains_loc(FileLoc::new(1, 0)));
assert!(r.contains_loc(FileLoc::new(1, 4)));
assert!(!r.contains_loc(FileLoc::new(1, 5)));
assert!(!r.contains_loc(FileLoc::new(0, 9)));
}
#[test]
fn range_contains_range() {
let outer = FileLocRange::new(FileLoc::new(1, 0), FileLoc::new(3, 0));
let inner = FileLocRange::new(FileLoc::new(1, 2), FileLoc::new(2, 8));
assert!(outer.contains_range(inner));
assert!(outer.contains_range(outer));
let crossing = FileLocRange::new(FileLoc::new(2, 0), FileLoc::new(4, 0));
assert!(!outer.contains_range(crossing));
}
#[test]
fn try_new_rejects_inverted_range() {
let s = FileLoc::new(2, 0);
let e = FileLoc::new(1, 0);
assert!(FileLocRange::try_new(s, e).is_none());
assert_eq!(FileLocRange::try_new(s, s), Some(FileLocRange::empty_at(s)));
}
}