use std::fmt;
use std::ops::Range;
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct FileId(pub u32);
impl fmt::Debug for FileId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "f{}", self.0)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct Span {
pub file: FileId,
pub start: u32,
pub end: u32,
}
impl Span {
pub fn new(file: FileId, start: usize, end: usize) -> Self {
Span { file, start: start as u32, end: end as u32 }
}
pub fn to(self, other: Span) -> Span {
debug_assert_eq!(self.file, other.file);
Span { file: self.file, start: self.start.min(other.start), end: self.end.max(other.end) }
}
pub fn range(self) -> Range<usize> {
self.start as usize..self.end as usize
}
pub fn len(self) -> usize {
(self.end - self.start) as usize
}
pub fn is_empty(self) -> bool {
self.start == self.end
}
}
impl fmt::Debug for Span {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}..{}", self.start, self.end)
}
}
pub struct SourceFile {
pub name: String,
pub text: String,
line_starts: Vec<u32>,
pub(crate) ariadne: ariadne::Source<String>,
}
impl SourceFile {
fn new(name: String, text: String) -> Self {
let mut line_starts = vec![0];
for (i, b) in text.bytes().enumerate() {
if b == b'\n' {
line_starts.push(i as u32 + 1);
}
}
let ariadne = ariadne::Source::from(text.clone());
SourceFile { name, text, line_starts, ariadne }
}
pub fn line_col(&self, offset: u32) -> (usize, usize) {
let line = self.line_starts.partition_point(|&s| s <= offset).max(1) - 1;
let start = self.line_starts[line] as usize;
let col = self.text[start..offset as usize].chars().count();
(line + 1, col + 1)
}
pub fn slice(&self, span: Span) -> &str {
&self.text[span.range()]
}
}
#[derive(Default)]
pub struct SourceMap {
files: Vec<SourceFile>,
}
impl SourceMap {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, name: impl Into<String>, text: impl Into<String>) -> FileId {
let id = FileId(self.files.len() as u32);
self.files.push(SourceFile::new(name.into(), text.into()));
id
}
pub fn load(&mut self, path: &std::path::Path) -> std::io::Result<FileId> {
let text = std::fs::read_to_string(path)?;
Ok(self.add(path.display().to_string(), text))
}
pub fn file(&self, id: FileId) -> &SourceFile {
&self.files[id.0 as usize]
}
pub fn text(&self, id: FileId) -> &str {
&self.file(id).text
}
pub fn slice(&self, span: Span) -> &str {
self.file(span.file).slice(span)
}
pub fn location(&self, span: Span) -> String {
let f = self.file(span.file);
let (l, c) = f.line_col(span.start);
format!("{}:{}:{}", f.name, l, c)
}
pub fn files(&self) -> impl Iterator<Item = (FileId, &SourceFile)> {
self.files.iter().enumerate().map(|(i, f)| (FileId(i as u32), f))
}
}