use std::cmp::Ordering;
use crate::diagnostic::Diagnostic;
use super::{Point, Position, Ranged, Span};
pub struct Locator<'a> {
source: &'a str,
line_spans: Vec<Span>,
}
impl<'a> Locator<'a> {
pub fn new(source: &'a str) -> Self {
let mut line_spans = Vec::new();
let mut start = 0;
for (end, _) in source.match_indices('\n') {
line_spans.push(Span::new(start, end));
start = end + 1;
}
if start <= source.len() {
line_spans.push(Span::new(start, source.len()))
}
Self { source, line_spans }
}
fn point(&self, offset: usize) -> Point {
if self.line_spans.is_empty() {
return Point::default();
}
let cmp = |span: &Span| {
if offset < span.start {
Ordering::Greater
} else if offset > span.end {
Ordering::Less
} else {
Ordering::Equal
}
};
let i = match self.line_spans.binary_search_by(cmp) {
Ok(i) => i,
Err(i) => i
.saturating_sub(1)
.min(self.line_spans.len().saturating_sub(1)),
};
Point::new(
i + 1,
offset.saturating_sub(self.line_spans[i].start) + 1,
offset,
)
}
pub fn locate(&self, diagnostic: &Diagnostic) -> Diagnostic<Position> {
diagnostic.clone().locate(self)
}
pub fn locate_all(&self, diagnostics: &[Diagnostic]) -> Vec<Diagnostic<Position>> {
diagnostics.iter().map(|d| self.locate(d)).collect()
}
pub fn position<R: Ranged<usize>>(&self, ranged: &R) -> Position {
let range = ranged.range();
Position::new(self.point(range.start), self.point(range.end))
}
pub(crate) fn line(&self, line: usize) -> &'a str {
&self.source[self.line_spans[line.saturating_sub(1)].range()]
}
pub(crate) fn lines(&self) -> usize {
self.line_spans.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::span::SpanIterator;
#[test]
fn test_position() {
let s = "foo
bar
quux";
let locator = Locator::new(s);
let spans: Vec<Span> = SpanIterator::new(s).collect();
assert_eq!(
locator.position(&spans[0]),
Position::new(Point::new(1, 1, 0), Point::new(1, 4, 3)),
);
assert_eq!(
locator.position(&spans[1]),
Position::new(Point::new(3, 1, 5), Point::new(3, 4, 8)),
);
assert_eq!(
locator.position(&spans[2]),
Position::new(Point::new(5, 1, 10), Point::new(5, 5, 14)),
);
}
#[test]
fn test_line() {
let s = "foo bar baz\nfoobar foobaz";
let locator = Locator::new(s);
assert_eq!(locator.line(2), "foobar foobaz");
}
}