extern crate alloc;
use alloc::vec::Vec;
use core::fmt;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default)]
pub struct Position {
pub line: usize,
pub col: usize,
}
impl Position {
#[must_use]
pub const fn start() -> Self {
Self { line: 1, col: 1 }
}
#[must_use]
pub fn from_src_end(src: &str) -> Self {
let mut line = 1;
let mut col = 1;
for &b in src.as_bytes() {
if b == b'\n' {
line += 1;
col = 1;
} else {
col += 1;
}
}
Self { line, col }
}
}
impl fmt::Display for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self { line, col } = self;
write!(f, "{line}:{col}")
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub struct Span {
pub start: Position,
pub end: Position,
pub start_offset: usize,
pub end_offset: usize,
}
impl Span {
#[must_use]
pub const fn synthetic() -> Self {
Self {
start: Position { line: 0, col: 0 },
end: Position { line: 0, col: 0 },
start_offset: 0,
end_offset: 0,
}
}
#[must_use]
pub const fn is_synthetic(&self) -> bool {
self.start.line == 0
}
#[must_use]
pub fn slice<'a>(&self, source: &'a str) -> &'a str {
&source[self.start_offset..self.end_offset]
}
#[must_use]
pub fn between(start: &Span, end: &Span) -> Self {
Self {
start: start.start,
end: end.end,
start_offset: start.start_offset,
end_offset: end.end_offset,
}
}
}
impl fmt::Display for Span {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self { start, end, .. } = self;
if start == end {
write!(f, "{start}")
} else {
write!(f, "{start}-{end}")
}
}
}
#[derive(Clone, Debug)]
pub struct LineIndex {
line_starts: Vec<usize>,
}
impl LineIndex {
#[must_use]
pub fn new(source: &str) -> Self {
let mut line_starts = vec![0];
for (i, b) in source.bytes().enumerate() {
if b == b'\n' {
line_starts.push(i + 1);
}
}
Self { line_starts }
}
#[must_use]
pub fn position(&self, offset: usize) -> Position {
let line = self.line_starts.partition_point(|&start| start <= offset);
let line_start = self.line_starts[line - 1];
Position {
line,
col: offset - line_start + 1,
}
}
#[must_use]
pub fn line_starts(&self) -> &[usize] {
&self.line_starts
}
}
#[derive(Clone, Debug)]
pub struct LineIndexCursor<'a> {
line_starts: &'a [usize],
line: usize,
line_start: usize,
}
impl<'a> LineIndexCursor<'a> {
#[must_use]
pub fn new(line_index: &'a LineIndex) -> Self {
Self {
line_starts: &line_index.line_starts,
line: 1,
line_start: 0,
}
}
#[must_use]
pub fn position(&mut self, offset: usize) -> Position {
while self.line < self.line_starts.len() {
let next_line_start = self.line_starts[self.line];
if offset < next_line_start {
break;
}
self.line += 1;
self.line_start = next_line_start;
}
Position {
line: self.line,
col: offset - self.line_start + 1,
}
}
}
#[cfg(test)]
mod tests {
extern crate alloc;
use alloc::string::ToString;
use super::*;
#[test]
fn test_position_display() {
let pos = Position { line: 3, col: 15 };
assert_eq!(pos.to_string(), "3:15");
}
#[test]
fn test_span_display() {
let span = Span {
start: Position { line: 1, col: 1 },
end: Position { line: 1, col: 1 },
start_offset: 0,
end_offset: 0,
};
assert_eq!(span.to_string(), "1:1");
let span = Span {
start: Position { line: 1, col: 1 },
end: Position { line: 2, col: 5 },
start_offset: 0,
end_offset: 10,
};
assert_eq!(span.to_string(), "1:1-2:5");
}
#[test]
fn test_synthetic_span() {
let span = Span::synthetic();
assert!(span.is_synthetic());
let real_span = Span {
start: Position { line: 1, col: 1 },
end: Position { line: 1, col: 5 },
start_offset: 0,
end_offset: 4,
};
assert!(!real_span.is_synthetic());
}
#[test]
fn test_span_slice() {
let source = "hello world";
let span = Span {
start: Position { line: 1, col: 1 },
end: Position { line: 1, col: 5 },
start_offset: 0,
end_offset: 5,
};
assert_eq!(span.slice(source), "hello");
}
#[test]
fn test_position_from_src_end() {
assert_eq!(
Position::from_src_end("hello"),
Position { line: 1, col: 6 }
);
assert_eq!(
Position::from_src_end("hello\nworld"),
Position { line: 2, col: 6 }
);
assert_eq!(
Position::from_src_end("a\nb\nc"),
Position { line: 3, col: 2 }
);
}
#[test]
fn test_line_index_single_line() {
let idx = LineIndex::new("hello");
assert_eq!(idx.position(0), Position { line: 1, col: 1 });
assert_eq!(idx.position(4), Position { line: 1, col: 5 });
assert_eq!(idx.position(5), Position { line: 1, col: 6 });
}
#[test]
fn test_line_index_multiline() {
let idx = LineIndex::new("foo\nbar\nbaz");
assert_eq!(idx.position(0), Position { line: 1, col: 1 });
assert_eq!(idx.position(2), Position { line: 1, col: 3 });
assert_eq!(idx.position(3), Position { line: 1, col: 4 }); assert_eq!(idx.position(4), Position { line: 2, col: 1 });
assert_eq!(idx.position(6), Position { line: 2, col: 3 });
assert_eq!(idx.position(8), Position { line: 3, col: 1 });
assert_eq!(idx.position(10), Position { line: 3, col: 3 });
}
#[test]
fn test_line_index_empty() {
let idx = LineIndex::new("");
assert_eq!(idx.position(0), Position { line: 1, col: 1 });
}
#[test]
fn test_line_index_trailing_newline() {
let idx = LineIndex::new("foo\n");
assert_eq!(idx.position(0), Position { line: 1, col: 1 });
assert_eq!(idx.position(3), Position { line: 1, col: 4 }); assert_eq!(idx.position(4), Position { line: 2, col: 1 }); }
}