use std::sync::Arc;
type Pos = usize;
#[derive(Clone, Debug)]
pub struct SourceInfo {
source: Source,
linelens: LineLens,
}
impl SourceInfo {
pub fn unknown() -> SourceInfo {
SourceInfo {
source: Source::Unknown,
linelens: LineLens::from(""),
}
}
pub fn source(&self) -> &Source {
&self.source
}
pub fn from_file(filepath: &std::path::Path, contents: &str) -> SourceInfo {
SourceInfo {
source: Source::File(Arc::new(filepath.to_owned())),
linelens: LineLens::from(contents),
}
}
pub fn from_string(contents: &str) -> SourceInfo {
SourceInfo {
source: Source::String(Arc::new(contents.to_owned())),
linelens: LineLens::from(contents),
}
}
pub fn start(&self, item: &dyn HasSpan) -> LineCol {
self.linelens.linecol(item.span().start)
}
pub fn end(&self, item: &dyn HasSpan) -> LineCol {
self.linelens.linecol(item.span().end)
}
pub fn linecol_from(&self, pos: usize) -> LineCol {
self.linelens.linecol(pos)
}
}
#[derive(Clone, Debug)]
pub enum Source {
File(Arc<std::path::PathBuf>),
String(Arc<String>),
Unknown,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct LineCol(usize, usize);
impl LineCol {
pub fn from(line: usize, col: usize) -> LineCol {
assert!(line > 0);
assert!(col > 0);
LineCol(line - 1, col - 1)
}
pub fn line(&self) -> usize {
self.0 + 1
}
pub fn col(&self) -> usize {
self.1 + 1
}
}
impl std::fmt::Display for LineCol {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(f, "{}:{}", self.line(), self.col())
}
}
#[derive(Clone)]
pub struct Span {
start: Pos,
end: Pos,
source_info: SourceInfo,
}
impl std::fmt::Debug for Span {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match &self.source_info.source {
Source::File(path) => write!(f, "[{}-{}:{:?}]", self.start(), self.end(), path),
Source::String(s) => write!(f, "[{}-{}:{:?}]", self.start(), self.end(), String::from_utf8_lossy(&s.as_bytes()[self.start..self.end])),
Source::Unknown => write!(f, "[{}-{}]", self.start(), self.end()),
}
}
}
impl std::fmt::Display for Span {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match &self.source_info.source {
Source::File(_path) => write!(f, "[{}-{}]", self.start(), self.end()),
Source::String(_s) => write!(f, "[{}-{}]", self.start(), self.end()),
Source::Unknown => write!(f, "[{}-{}]", self.start(), self.end()),
}
}
}
impl Span {
pub fn unknown() -> Span {
Span {
start: 0,
end: 0,
source_info: SourceInfo::unknown(),
}
}
pub fn from(source_info: &SourceInfo, start: usize, end: usize) -> Span {
Span {
start,
end,
source_info: source_info.clone(),
}
}
pub fn start(&self) -> LineCol {
self.source_info.linelens.linecol(self.start)
}
pub fn end(&self) -> LineCol {
self.source_info.linelens.linecol(self.end)
}
pub fn source(&self) -> &str {
if let Source::String(source) = &self.source_info.source {
&source[self.start..self.end]
} else {
""
}
}
pub fn contains(&self, linecol: &LineCol) -> bool {
&self.start() <= linecol && linecol <= &self.end()
}
}
pub trait HasSpan {
fn span(&self) -> Span;
}
#[derive(Clone, Debug)]
struct LineLens(Vec<usize>);
impl LineLens {
fn from(text: &str) -> LineLens {
let mut lens = vec![];
for line in text.split("\n") {
lens.push(line.len() + 1);
}
LineLens(lens)
}
fn linecol(&self, pos: Pos) -> LineCol {
let mut line = 0;
let mut col = pos;
for line_len in &self.0 {
if col >= *line_len {
col -= *line_len;
line += 1;
} else {
break
}
}
LineCol(line, col)
}
}
#[test]
fn linelens() {
let text = "Hello,
world!
How are you?";
let linelens = LineLens::from(text);
assert_eq!(linelens.linecol(0).to_string(), "1:1".to_string());
assert_eq!(linelens.linecol(5).to_string(), "1:6".to_string());
assert_eq!(linelens.linecol(6).to_string(), "1:7".to_string());
assert_eq!(linelens.linecol(7).to_string(), "2:1".to_string());
assert_eq!(linelens.linecol(7).to_string(), "2:1".to_string());
assert_eq!(linelens.linecol(12).to_string(), "2:6".to_string());
assert_eq!(linelens.linecol(13).to_string(), "2:7".to_string());
assert_eq!(linelens.linecol(14).to_string(), "3:1".to_string());
}