use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
pub struct Position {
pub byte: usize,
pub line: u32,
pub column: u32,
}
impl Position {
pub fn new(byte: usize, line: u32, column: u32) -> Self {
Position { byte, line, column }
}
pub fn start() -> Self {
Position { byte: 0, line: 1, column: 1 }
}
pub fn advance(&mut self, text: &str) {
for ch in text.chars() {
if ch == '\n' {
self.line += 1;
self.column = 1;
} else {
self.column += 1;
}
self.byte += ch.len_utf8();
}
}
pub fn advance_char(&mut self, ch: char) {
if ch == '\n' {
self.line += 1;
self.column = 1;
} else {
self.column += 1;
}
self.byte += ch.len_utf8();
}
}
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, Serialize, Deserialize)]
pub struct Range {
pub start: Position,
pub end: Position,
}
impl Range {
pub fn new(start: Position, end: Position) -> Self {
Range { start, end }
}
pub fn empty(pos: Position) -> Self {
Range { start: pos, end: pos }
}
pub fn contains_byte(&self, byte: usize) -> bool {
self.start.byte <= byte && byte < self.end.byte
}
pub fn contains(&self, pos: Position) -> bool {
self.start.byte <= pos.byte && pos.byte < self.end.byte
}
pub fn overlaps(&self, other: &Range) -> bool {
self.start.byte < other.end.byte && other.start.byte < self.end.byte
}
pub fn len(&self) -> usize {
self.end.byte.saturating_sub(self.start.byte)
}
pub fn is_empty(&self) -> bool {
self.start.byte >= self.end.byte
}
pub fn extend(&mut self, other: &Range) {
if other.start.byte < self.start.byte {
self.start = other.start;
}
if other.end.byte > self.end.byte {
self.end = other.end;
}
}
pub fn span_to(&self, other: &Range) -> Range {
Range {
start: if self.start.byte < other.start.byte { self.start } else { other.start },
end: if self.end.byte > other.end.byte { self.end } else { other.end },
}
}
}
impl fmt::Display for Range {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}-{}", self.start, self.end)
}
}
impl From<crate::SourceLocation> for Range {
fn from(loc: crate::SourceLocation) -> Self {
Range {
start: Position { byte: loc.start, line: 0, column: 0 },
end: Position { byte: loc.end, line: 0, column: 0 },
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_position_advance() {
let mut pos = Position::start();
assert_eq!(pos, Position { byte: 0, line: 1, column: 1 });
pos.advance("hello");
assert_eq!(pos, Position { byte: 5, line: 1, column: 6 });
pos.advance("\n");
assert_eq!(pos, Position { byte: 6, line: 2, column: 1 });
pos.advance("世界"); assert_eq!(pos, Position { byte: 12, line: 2, column: 3 });
}
#[test]
fn test_range_operations() {
let start = Position::new(10, 2, 5);
let end = Position::new(20, 3, 10);
let range = Range::new(start, end);
assert!(range.contains_byte(15));
assert!(!range.contains_byte(25));
assert_eq!(range.len(), 10);
let other = Range::new(Position::new(15, 2, 10), Position::new(25, 4, 5));
assert!(range.overlaps(&other));
let span = range.span_to(&other);
assert_eq!(span.start.byte, 10);
assert_eq!(span.end.byte, 25);
}
}