use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Span {
pub start: usize,
pub end: usize,
}
impl Span {
pub fn new(start: usize, end: usize) -> Self {
debug_assert!(start <= end, "span start {start} > end {end}");
Self { start, end }
}
pub fn len(&self) -> usize {
self.end - self.start
}
pub fn is_empty(&self) -> bool {
self.start == self.end
}
pub fn contains(&self, offset: usize) -> bool {
self.start <= offset && offset < self.end
}
pub fn join(self, other: Span) -> Span {
Span::new(self.start.min(other.start), self.end.max(other.end))
}
pub fn slice<'a>(&self, src: &'a str) -> &'a str {
&src[self.start..self.end]
}
}
impl fmt::Display for Span {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}..{}", self.start, self.end)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LineCol {
pub line: u32,
pub col: u32,
}
#[derive(Debug, Clone)]
pub struct LineIndex {
line_starts: Vec<usize>,
len: usize,
}
impl LineIndex {
pub fn new(src: &str) -> Self {
let mut line_starts = vec![0];
for (i, b) in src.bytes().enumerate() {
if b == b'\n' {
line_starts.push(i + 1);
}
}
Self {
line_starts,
len: src.len(),
}
}
pub fn line_count(&self) -> usize {
self.line_starts.len()
}
pub fn line_start(&self, line: u32) -> Option<usize> {
self.line_starts.get(line as usize).copied()
}
pub fn line_col(&self, offset: usize) -> LineCol {
let offset = offset.min(self.len);
let line = self.line_starts.partition_point(|&s| s <= offset) - 1;
LineCol {
line: line as u32,
col: (offset - self.line_starts[line]) as u32,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TokenRange {
pub first: usize,
pub end: usize,
}
impl TokenRange {
pub fn len(&self) -> usize {
self.end - self.first
}
pub fn is_empty(&self) -> bool {
self.first == self.end
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn span_basics() {
let s = Span::new(2, 5);
assert_eq!(s.len(), 3);
assert!(!s.is_empty());
assert!(s.contains(2) && s.contains(4) && !s.contains(5));
assert_eq!(s.join(Span::new(7, 9)), Span::new(2, 9));
assert_eq!(s.slice("abcdefgh"), "cde");
assert!(Span::new(3, 3).is_empty());
}
#[test]
fn line_index_lf_and_crlf() {
let idx = LineIndex::new("ab\r\ncd\ne");
assert_eq!(idx.line_count(), 3);
assert_eq!(idx.line_col(0), LineCol { line: 0, col: 0 });
assert_eq!(idx.line_col(2), LineCol { line: 0, col: 2 });
assert_eq!(idx.line_col(4), LineCol { line: 1, col: 0 });
assert_eq!(idx.line_col(7), LineCol { line: 2, col: 0 });
assert_eq!(idx.line_col(99), LineCol { line: 2, col: 1 });
assert_eq!(idx.line_start(1), Some(4));
assert_eq!(idx.line_start(9), None);
}
#[test]
fn line_index_empty_and_trailing_newline() {
let idx = LineIndex::new("");
assert_eq!(idx.line_count(), 1);
assert_eq!(idx.line_col(0), LineCol { line: 0, col: 0 });
let idx = LineIndex::new("x\n");
assert_eq!(idx.line_count(), 2);
assert_eq!(idx.line_col(2), LineCol { line: 1, col: 0 });
}
}