use std::fmt;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Span {
pub start: usize,
pub end: usize,
}
impl Span {
pub const fn new(start: usize, end: usize) -> Self {
Self { start, end }
}
pub const fn synthetic() -> Self {
Self {
start: usize::MAX,
end: usize::MAX,
}
}
pub const fn is_synthetic(&self) -> bool {
self.start == usize::MAX && self.end == usize::MAX
}
pub fn merge(self, other: Span) -> Span {
if self.is_synthetic() {
return other;
}
if other.is_synthetic() {
return self;
}
Span {
start: self.start.min(other.start),
end: self.end.max(other.end),
}
}
pub fn line_col(src: &str, byte_offset: usize) -> (usize, usize) {
let mut line = 1usize;
let mut col = 1usize;
for (i, ch) in src.char_indices() {
if i >= byte_offset {
return (line, col);
}
if ch == '\n' {
line += 1;
col = 1;
} else {
col += 1;
}
}
(line, col)
}
}
impl fmt::Display for Span {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_synthetic() {
f.write_str("<synthetic>")
} else {
write!(f, "{}..{}", self.start, self.end)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn merge_real_spans() {
let a = Span::new(5, 10);
let b = Span::new(8, 20);
assert_eq!(a.merge(b), Span::new(5, 20));
assert_eq!(b.merge(a), Span::new(5, 20));
}
#[test]
fn merge_with_synthetic_preserves_real() {
let a = Span::new(5, 10);
assert_eq!(a.merge(Span::synthetic()), a);
assert_eq!(Span::synthetic().merge(a), a);
}
#[test]
fn merge_two_synthetics_is_synthetic() {
let s = Span::synthetic();
assert!(s.merge(s).is_synthetic());
}
#[test]
fn line_col_counts_newlines() {
let src = "abc\nde\nfghi";
assert_eq!(Span::line_col(src, 0), (1, 1));
assert_eq!(Span::line_col(src, 2), (1, 3));
assert_eq!(Span::line_col(src, 4), (2, 1));
assert_eq!(Span::line_col(src, 7), (3, 1));
assert_eq!(Span::line_col(src, 10), (3, 4));
}
#[test]
fn display_synthetic() {
assert_eq!(Span::synthetic().to_string(), "<synthetic>");
}
#[test]
fn display_real() {
assert_eq!(Span::new(4, 9).to_string(), "4..9");
}
}