use crate::Allocator;
use crate::models::nlcst::Node;
use crate::models::position::{Point, Position, UnistPoint, UnistPosition};
#[derive(Clone, Debug)]
pub struct Location<'a> {
source: &'a str,
starts: Vec<usize>,
units: Vec<u32>,
bytes: Vec<u32>,
}
const INSIDE: u32 = u32::MAX;
impl<'a> Location<'a> {
pub fn new(source: &'a str) -> Self {
let bytes = source.as_bytes();
let mut starts = vec![0];
for (index, byte) in bytes.iter().enumerate() {
match byte {
b'\n' => starts.push(index + 1),
b'\r' if bytes.get(index + 1) != Some(&b'\n') => starts.push(index + 1),
_ => {}
}
}
let mut location = Self {
source,
starts,
units: Vec::new(),
bytes: Vec::new(),
};
if !source.is_ascii() {
let mut units = Vec::with_capacity(source.len() + 1);
let mut offsets = Vec::with_capacity(source.len() + 1);
let mut unit = 0;
for (index, character) in source.char_indices() {
units.resize(index + character.len_utf8(), unit);
offsets.push(index as u32);
if character.len_utf16() == 2 {
offsets.push(INSIDE);
}
unit += character.len_utf16() as u32;
}
units.push(unit);
offsets.push(source.len() as u32);
location.units = units;
location.bytes = offsets;
}
location
}
pub fn source(&self) -> &'a str {
self.source
}
pub fn len(&self) -> usize {
self.to_unit(self.source.len())
}
pub fn is_empty(&self) -> bool {
self.source.is_empty()
}
pub fn to_unit(&self, byte: usize) -> usize {
let byte = byte.min(self.source.len());
match self.units.get(byte) {
Some(unit) => *unit as usize,
None => byte,
}
}
pub fn to_byte(&self, unit: usize) -> Option<usize> {
if self.units.is_empty() {
return (unit <= self.source.len()).then_some(unit);
}
match self.bytes.get(unit) {
Some(&INSIDE) | None => None,
Some(byte) => Some(*byte as usize),
}
}
pub fn to_unist_point(&self, point: Point) -> UnistPoint {
let offset = point.offset().min(self.source.len());
let line = self.line(offset);
let extra = usize::from(point.virtual_spaces() > 0) + (point.offset() - offset);
let unit = self.to_unit(offset);
UnistPoint {
line,
column: unit - self.to_unit(self.starts[line - 1]) + 1 + extra,
offset: Some(unit + extra),
}
}
pub fn to_point(&self, unit: usize) -> Option<UnistPoint> {
if unit > self.len() {
return None;
}
let byte = match self.to_byte(unit) {
Some(byte) => byte,
None => self.to_byte(unit.checked_sub(1)?)?,
};
let line = self.line(byte);
Some(UnistPoint {
line,
column: unit - self.to_unit(self.starts[line - 1]) + 1,
offset: Some(unit),
})
}
pub fn to_offset(&self, line: usize, column: usize) -> Option<usize> {
let start = self.to_unit(*self.starts.get(line.checked_sub(1)?)?);
let next = match self.starts.get(line) {
Some(next) => self.to_unit(*next),
None => self.len() + 1,
};
let offset = (start + column).checked_sub(1)?;
(offset < next).then_some(offset)
}
pub fn to_position<'b>(
&self,
start: usize,
end: usize,
alloc: &'b Allocator,
) -> Option<Position<'b>> {
match (self.to_byte(start), self.to_byte(end)) {
(Some(start), Some(end)) => {
Some(Position::span(Point::new(start, 0), Point::new(end, 0)))
}
_ => {
let start = self.to_point(start)?;
let end = self.to_point(end)?;
Some(Position::Unist(alloc.alloc(UnistPosition { start, end })))
}
}
}
pub fn place_nlcst<'b>(
&self,
nodes: &mut [Node<'b>],
start: Option<usize>,
alloc: &'b Allocator,
) -> Option<usize> {
let mut from = start;
for node in nodes {
let to = match node {
Node::Root(parent)
| Node::Paragraph(parent)
| Node::Sentence(parent)
| Node::Word(parent) => {
let to = self.place_nlcst(&mut parent.children, from, alloc);
parent.position = self.place(from, to, alloc);
to
}
Node::Text(literal)
| Node::Symbol(literal)
| Node::Punctuation(literal)
| Node::WhiteSpace(literal)
| Node::Source(literal) => {
let to = from.map(|from| from + count_units(literal.value));
literal.position = self.place(from, to, alloc);
to
}
Node::Unknown(unknown) => {
unknown.position = None;
from
}
};
from = to;
}
from
}
pub fn join<'b>(
&self,
start: &Position<'b>,
end: &Position<'b>,
alloc: &'b Allocator,
) -> Position<'b> {
match (start, end) {
(Position::Span { start, .. }, Position::Span { end, .. }) => {
Position::span(*start, *end)
}
_ => Position::Unist(alloc.alloc(UnistPosition {
start: self.to_unist_position(start).start,
end: self.to_unist_position(end).end,
})),
}
}
fn to_unist_position(&self, position: &Position<'_>) -> UnistPosition {
match position {
Position::Span { start, end } => UnistPosition {
start: self.to_unist_point(*start),
end: self.to_unist_point(*end),
},
Position::Unist(position) => (*position).clone(),
}
}
fn place<'b>(
&self,
from: Option<usize>,
to: Option<usize>,
alloc: &'b Allocator,
) -> Option<Position<'b>> {
self.to_position(from?, to?, alloc)
}
fn line(&self, byte: usize) -> usize {
self.starts.partition_point(|start| *start <= byte)
}
}
fn count_units(value: &str) -> usize {
if value.is_ascii() {
value.len()
} else {
value.chars().map(char::len_utf16).sum()
}
}
#[cfg(test)]
#[path = "location_unit.rs"]
mod unit;