use crate::ast::Span;
use std::fmt;
use std::fs;
use std::io;
use std::ops::Range;
use std::path::Path;
use std::slice;
#[derive(Default, Clone)]
pub struct Source {
name: Box<str>,
source: Box<str>,
path: Option<Box<Path>>,
line_starts: Vec<usize>,
}
impl Source {
pub fn new(name: impl AsRef<str>, source: impl AsRef<str>) -> Self {
Self::with_path(name, source, None::<Box<Path>>)
}
pub fn from_path(path: &Path) -> io::Result<Self> {
let name = path.display().to_string();
let source = fs::read_to_string(&path)?;
Ok(Self::with_path(name, source, Some(path)))
}
pub fn with_path(
name: impl AsRef<str>,
source: impl AsRef<str>,
path: Option<impl AsRef<Path>>,
) -> Self {
let source = source.as_ref();
let line_starts = line_starts(source).collect::<Vec<_>>();
Self {
name: name.as_ref().into(),
source: source.into(),
path: path.map(|p| p.as_ref().into()),
line_starts,
}
}
pub fn line_starts(&self) -> &[usize] {
&self.line_starts
}
pub fn is_empty(&self) -> bool {
self.source.is_empty()
}
pub fn len(&self) -> usize {
self.source.len()
}
pub fn name(&self) -> &str {
&self.name
}
pub fn get<I>(&self, i: I) -> Option<&I::Output>
where
I: slice::SliceIndex<str>,
{
self.source.get(i)
}
pub fn as_str(&self) -> &str {
&self.source
}
pub fn path(&self) -> Option<&Path> {
self.path.as_deref()
}
pub fn position_to_utf16cu_line_char(&self, offset: usize) -> Option<(usize, usize)> {
if offset == 0 {
return Some((0, 0));
}
let line = match self.line_starts.binary_search(&offset) {
Ok(exact) => exact,
Err(0) => return None,
Err(n) => n - 1,
};
let line_start = self.line_starts[line];
let rest = &self.source[line_start..];
let offset = offset - line_start;
let mut line_count = 0;
for (n, c) in rest.char_indices() {
if n == offset {
return Some((line, line_count));
}
if n > offset {
break;
}
line_count += c.encode_utf16(&mut [0u16; 2]).len();
}
Some((line, line_count))
}
pub fn position_to_unicode_line_char(&self, offset: usize) -> (usize, usize) {
if offset == 0 {
return (0, 0);
}
let line = match self.line_starts.binary_search(&offset) {
Ok(exact) => exact,
Err(0) => return (0, 0),
Err(n) => n - 1,
};
let line_start = self.line_starts[line];
let rest = &self.source[line_start..];
let offset = offset - line_start;
let mut line_count = 0;
for (n, _) in rest.char_indices() {
if n == offset {
return (line, line_count);
}
if n > offset {
break;
}
line_count += 1;
}
(line, line_count)
}
pub fn line_index(&self, byte_index: usize) -> usize {
self.line_starts
.binary_search(&byte_index)
.unwrap_or_else(|next_line| next_line.saturating_sub(1))
}
pub fn line_range(&self, line_index: usize) -> Option<Range<usize>> {
let line_start = self.line_start(line_index)?;
let next_line_start = self.line_start(line_index + 1)?;
Some(line_start..next_line_start)
}
pub fn line_count(&self) -> usize {
self.line_starts.len()
}
pub fn line(&self, span: Span) -> Option<(usize, usize, &str)> {
let start = span.start.into_usize();
let (line, line_count) = self.position_to_unicode_line_char(start);
let range = self.line_range(line)?;
let text = self.source.get(range)?;
Some((line, line_count, text))
}
fn line_start(&self, line_index: usize) -> Option<usize> {
use std::cmp::Ordering;
match line_index.cmp(&self.line_starts.len()) {
Ordering::Less => self.line_starts.get(line_index).copied(),
Ordering::Equal => Some(self.source.as_ref().len()),
Ordering::Greater => None,
}
}
}
impl fmt::Debug for Source {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Source")
.field("name", &self.name)
.field("path", &self.path)
.finish()
}
}
fn line_starts(source: &str) -> impl Iterator<Item = usize> + '_ {
std::iter::once(0).chain(source.match_indices('\n').map(|(i, _)| i + 1))
}