use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Position {
pub line: usize,
pub column: usize,
}
impl Position {
pub const fn new(line: usize, column: usize) -> Self {
Self { line, column }
}
}
impl fmt::Display for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.line, self.column)
}
}
#[derive(Debug, Clone, Copy, 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 single(pos: usize) -> Self {
Self {
start: pos,
end: pos + 1,
}
}
pub const fn merge(self, other: Span) -> Span {
let start = if self.start < other.start {
self.start
} else {
other.start
};
let end = if self.end > other.end {
self.end
} else {
other.end
};
Span { start, end }
}
pub const fn len(&self) -> usize {
self.end - self.start
}
pub const fn is_empty(&self) -> bool {
self.start == self.end
}
pub fn slice<'a>(&self, source: &'a str) -> &'a str {
&source[self.start..self.end]
}
pub fn to_positions(&self, source: &str) -> (Position, Position) {
let start_pos = byte_offset_to_position(source, self.start);
let end_pos = byte_offset_to_position(source, self.end);
(start_pos, end_pos)
}
}
impl fmt::Display for Span {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}..{}", self.start, self.end)
}
}
fn byte_offset_to_position(source: &str, offset: usize) -> Position {
let mut line = 1;
let mut column = 1;
for (i, ch) in source.char_indices() {
if i >= offset {
break;
}
if ch == '\n' {
line += 1;
column = 1;
} else {
column += 1;
}
}
Position { line, column }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_position_display() {
let pos = Position::new(10, 25);
assert_eq!(pos.to_string(), "10:25");
}
#[test]
fn test_span_merge() {
let span1 = Span::new(5, 10);
let span2 = Span::new(8, 15);
let merged = span1.merge(span2);
assert_eq!(merged, Span::new(5, 15));
}
#[test]
fn test_span_len() {
let span = Span::new(10, 20);
assert_eq!(span.len(), 10);
}
#[test]
fn test_span_slice() {
let source = "hello world";
let span = Span::new(0, 5);
assert_eq!(span.slice(source), "hello");
}
#[test]
fn test_byte_offset_to_position() {
let source = "hello\nworld\nfoo";
assert_eq!(byte_offset_to_position(source, 0), Position::new(1, 1));
assert_eq!(byte_offset_to_position(source, 5), Position::new(1, 6));
assert_eq!(byte_offset_to_position(source, 6), Position::new(2, 1));
assert_eq!(byte_offset_to_position(source, 12), Position::new(3, 1));
}
#[test]
fn test_span_to_positions() {
let source = "hello\nworld";
let span = Span::new(0, 5);
let (start, end) = span.to_positions(source);
assert_eq!(start, Position::new(1, 1));
assert_eq!(end, Position::new(1, 6));
}
}