use crate::unist::Point;
use alloc::{vec, vec::Vec};
pub type Stop = (usize, usize);
#[derive(Debug)]
pub struct Location {
indices: Vec<usize>,
}
impl Location {
#[must_use]
pub fn new(bytes: &[u8]) -> Self {
let mut index = 0;
let mut location_index = Self { indices: vec![] };
while index < bytes.len() {
if bytes[index] == b'\r' {
if index + 1 < bytes.len() && bytes[index + 1] == b'\n' {
location_index.indices.push(index + 2);
index += 1;
} else {
location_index.indices.push(index + 1);
}
} else if bytes[index] == b'\n' {
location_index.indices.push(index + 1);
}
index += 1;
}
location_index.indices.push(index + 1);
location_index
}
#[must_use]
pub fn to_point(&self, offset: usize) -> Option<Point> {
let mut index = 0;
if let Some(end) = self.indices.last() {
if offset < *end {
while index < self.indices.len() {
if self.indices[index] > offset {
break;
}
index += 1;
}
let previous = if index > 0 {
self.indices[index - 1]
} else {
0
};
return Some(Point::new(index + 1, offset + 1 - previous, offset));
}
}
None
}
#[must_use]
pub fn relative_to_point(&self, stops: &[Stop], relative: usize) -> Option<Point> {
Location::relative_to_absolute(stops, relative).and_then(|absolute| self.to_point(absolute))
}
#[must_use]
pub fn relative_to_absolute(stops: &[Stop], relative: usize) -> Option<usize> {
let mut index = 0;
while index < stops.len() && stops[index].0 <= relative {
index += 1;
}
if index == 0 {
None
} else {
let (stop_relative, stop_absolute) = &stops[index - 1];
Some(stop_absolute + (relative - stop_relative))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_location_lf() {
let location = Location::new("ab\nc".as_bytes());
assert_eq!(
location.to_point(0), Some(Point::new(1, 1, 0)),
"should support some points (1)"
);
assert_eq!(
location.to_point(1), Some(Point::new(1, 2, 1)),
"should support some points (2)"
);
assert_eq!(
location.to_point(2), Some(Point::new(1, 3, 2)),
"should support some points (3)"
);
assert_eq!(
location.to_point(3), Some(Point::new(2, 1, 3)),
"should support some points (4)"
);
assert_eq!(
location.to_point(4), Some(Point::new(2, 2, 4)),
"should support some points (5)"
);
assert_eq!(
location.to_point(5), None,
"should support some points (6)"
);
}
#[test]
fn test_location_cr() {
let location = Location::new("a\rb".as_bytes());
assert_eq!(
location.to_point(0), Some(Point::new(1, 1, 0)),
"should support some points (1)"
);
assert_eq!(
location.to_point(1), Some(Point::new(1, 2, 1)),
"should support some points (2)"
);
assert_eq!(
location.to_point(2), Some(Point::new(2, 1, 2)),
"should support some points (3)"
);
}
#[test]
fn test_location_cr_lf() {
let location = Location::new("a\r\nb".as_bytes());
assert_eq!(
location.to_point(0), Some(Point::new(1, 1, 0)),
"should support some points (1)"
);
assert_eq!(
location.to_point(1), Some(Point::new(1, 2, 1)),
"should support some points (2)"
);
assert_eq!(
location.to_point(2), Some(Point::new(1, 3, 2)),
"should support some points (3)"
);
assert_eq!(
location.to_point(3), Some(Point::new(2, 1, 3)),
"should support some points (4)"
);
}
#[test]
fn test_empty() {
let location = Location::new("".as_bytes());
assert_eq!(location.to_point(0), Some(Point::new(1, 1, 0)), "to_point");
assert_eq!(
location.relative_to_point(&[], 0),
None,
"relative_to_point"
);
assert_eq!(
Location::relative_to_absolute(&[], 0),
None,
"relative_to_absolute"
);
}
}