use std::ops;
use crate::location::{Offset, line_column};
#[derive(Debug)]
pub struct Index {
line_offsets: Vec<Offset>,
}
impl Index {
pub fn new() -> Self {
Self {
line_offsets: vec![0.into()],
}
}
#[inline]
pub fn count(&self) -> usize {
debug_assert!(!self.line_offsets.is_empty());
self.line_offsets.len() - 1
}
#[inline]
pub fn end(&self) -> Option<Offset> {
self.line_offsets.last().copied()
}
#[inline]
pub fn into_offsets(self) -> Vec<Offset> {
self.line_offsets
}
}
impl Index {
pub fn query(&self) -> Query<'_> {
Query::from(&self.line_offsets[..])
}
pub fn get_line_offset_mut(&mut self, line_no: usize) -> Option<&mut Offset> {
self.line_offsets.get_mut(line_no)
}
pub fn add_next_line(&mut self, offset: Offset) {
self.line_offsets.push(offset);
}
pub fn clear(&mut self) {
self.line_offsets.clear();
self.add_next_line(0.into());
}
}
#[derive(Debug)]
pub struct Query<'index> {
begin: usize,
slice: &'index [Offset],
}
impl<'index> Query<'index> {
fn new(begin: usize, slice: &'index [Offset]) -> Self {
Self { begin, slice }
}
fn from(slice: &'index [Offset]) -> Self {
Self { begin: 0, slice }
}
pub fn range(&self, range: ops::Range<usize>) -> Self {
assert!(range.start <= range.end);
assert!(range.end <= self.count());
let range = range.start..range.end + 1;
Self::new(range.start, &self.slice[range])
}
pub fn range_from(&self, range_from: ops::RangeFrom<usize>) -> Self {
assert!(range_from.start <= self.count());
Self::new(range_from.start, &self.slice[range_from])
}
pub fn count(&self) -> usize {
debug_assert!(!self.slice.is_empty());
self.slice.len() - 1
}
}
impl Query<'_> {
#[inline]
pub fn line_offset(&self, line_no: usize) -> Option<Offset> {
if line_no < self.begin {
return None;
}
let line_no = line_no - self.begin;
self.slice.get(line_no).copied()
}
pub fn line_span(&self, line_no: usize) -> Option<ops::Range<Offset>> {
let start = self.line_offset(line_no)?;
let end = self.line_offset(line_no + 1)?;
Some(start..end)
}
#[inline]
pub fn beginning(&self) -> Option<Offset> {
self.line_offset(0)
}
#[inline]
pub fn ending(&self) -> Option<Offset> {
self.slice.last().copied()
}
pub fn contains(&self, offset: Offset) -> bool {
let Some(begin) = self.beginning() else {
return false;
};
let Some(end) = self.ending() else {
return false;
};
offset >= begin && offset < end
}
#[inline]
pub fn locate_line(&self, offset: Offset) -> Option<usize> {
binary_search_between(&self.slice, offset).map(|n| self.begin + n)
}
pub fn locate(&self, offset: Offset) -> Option<line_column::ZeroBased> {
let line = self.locate_line(offset)?;
let line_offset = self.line_offset(line).unwrap();
let col = offset - line_offset;
Some((line, col.raw()).into())
}
pub fn encode(&self, location: line_column::ZeroBased) -> Option<Offset> {
let (line, col) = location.raw();
let range = self.line_span(line)?;
let offset = range.start + col;
range.contains(&offset).then_some(offset)
}
}
fn binary_search_between<A: Ord + Copy>(xs: &[A], x: A) -> Option<usize> {
if xs.len() <= 1 {
return None;
}
if x == xs[0] {
return Some(0);
}
if x < xs[0] {
return None;
}
let mut start = 0;
let mut end = xs.len() - 1;
while start < end {
if start == end - 1 && xs[start] <= x && x < xs[end] {
return Some(start);
}
let mid = start + ((end - start) >> 1);
let y = xs[mid];
if x == y {
return Some(mid);
}
if x < y {
end = mid;
continue;
}
if start == mid {
return None;
}
start = mid;
}
None
}
#[cfg(test)]
mod test {
use super::*;
use quickcheck_macros::quickcheck;
fn linear_search_between<A: Ord + Copy>(xs: &[A], x: A) -> Option<usize> {
if xs.len() <= 1 {
return None;
}
for i in 0..xs.len() - 1 {
if xs[i] <= x && x < xs[i + 1] {
return Some(i);
}
}
None
}
#[quickcheck]
fn prop_binary_search_between(mut xs: Vec<i64>, x: i64) -> bool {
xs.sort();
xs.dedup();
if xs.len() < 2 {
return true;
}
let res0 = linear_search_between(&xs, x);
let res1 = binary_search_between(&xs, x);
res0 == res1
}
#[test]
fn test_binary_search() {
let xs = [2, 4, 6];
let i = binary_search_between(&xs, 3);
assert_eq!(i, Some(0));
let i = binary_search_between(&xs, 4);
assert_eq!(i, Some(1));
let i = binary_search_between(&xs, 1);
assert_eq!(i, None);
let i = binary_search_between(&xs, 7);
assert_eq!(i, None);
let i = binary_search_between(&xs, 6);
assert_eq!(i, None);
}
}