use std::{
io::Result as IOResult,
fmt::{ Display, Debug, Formatter, Result as FMTResult, },
fs::read_to_string,
cell::{ UnsafeCell, },
path::{ Path, PathBuf, },
};
use crate::{
util::{ make_key_type, },
collections::{ SlotMap, },
};
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
#[allow(missing_docs)]
pub struct SourceLocation {
pub index: usize,
pub line: u32,
pub column: u32,
}
impl SourceLocation {
pub const ZERO: Self = Self { index: 0, line: 0, column: 0 };
pub const fn to_region (self, source: Option<SourceKey>) -> SourceRegion {
SourceRegion {
source,
start: self,
end: self,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
#[allow(missing_docs)]
pub struct SourceRegion {
pub source: Option<SourceKey>,
pub start: SourceLocation,
pub end: SourceLocation,
}
impl SourceRegion {
pub const ANONYMOUS: Self = SourceLocation::ZERO.to_region(None);
pub fn merge (start: Self, end: Self) -> Self {
if start.source == end.source {
Self {
source: start.source,
start: start.start,
end: end.end,
}
} else {
Self::ANONYMOUS
}
}
pub fn clip_to_start (&self) -> Self {
Self {
source: self.source,
start: self.start,
end: self.start,
}
}
pub fn clip_to_end (&self) -> Self {
Self {
source: self.source,
start: self.end,
end: self.end,
}
}
}
impl Debug for SourceLocation {
fn fmt (&self, f: &mut Formatter) -> FMTResult {
write!(f, "{}:{}", self.line + 1, self.column + 1)
}
}
impl Display for SourceLocation {
fn fmt (&self, f: &mut Formatter) -> FMTResult {
Debug::fmt(self, f)
}
}
impl Debug for SourceRegion {
fn fmt (&self, f: &mut Formatter) -> FMTResult {
if let Some(source) = self.source {
let source = SOURCE_MANAGER.get(source).expect("Internal error: Could not get Source for SourceRegion Display");
write!(f, "{}:{}", source.path.display(), self.start)?;
if self.end != self.start
&& !(self.end.line == self.start.line && self.end.column == self.start.column + 1) {
write!(f, " to {}", self.end)?;
}
} else {
write!(f, "AnonymousSource")?;
}
Ok(())
}
}
impl Display for SourceRegion {
fn fmt (&self, f: &mut Formatter) -> FMTResult {
Debug::fmt(self, f)
}
}
pub struct Source {
pub path: PathBuf,
pub content: Vec<char>,
}
impl Source {
pub fn load<P: AsRef<Path>> (path: P) -> IOResult<Source> {
let content = read_to_string(&path)?;
Ok(Source {
path: path.as_ref().to_path_buf(),
content: content.chars().collect()
})
}
pub fn chars (&self) -> &[char] {
self.content.as_slice()
}
pub fn line_and_column_to_index (&self, line: u32, column: u32) -> Option<usize> {
let mut index = 0;
let mut ol = 0u32;
let mut oc = 0u32;
for ch in self.chars().iter() {
if ol == line && oc == column { return Some(index) }
if *ch == '\n' {
ol += 1;
oc = 0;
} else {
oc += 1;
}
index += 1;
}
if ol == line && oc == column { Some(index) }
else { None }
}
}
make_key_type! {
pub struct SourceKey;
}
pub struct SourceManager (UnsafeCell<Option<SlotMap<SourceKey, Source>>>);
unsafe impl Send for SourceManager { }
unsafe impl Sync for SourceManager { }
pub static SOURCE_MANAGER: SourceManager = SourceManager(UnsafeCell::new(None));
impl SourceManager {
#[allow(clippy::mut_from_ref)]
unsafe fn inner (&self) -> &mut Option<SlotMap<SourceKey, Source>> {
&mut *self.0.get()
}
#[allow(clippy::mut_from_ref)]
fn map (&self) -> &mut SlotMap<SourceKey, Source> {
let inner = unsafe { self.inner() };
inner.as_mut().expect("Internal error: SourceManager not initialized")
}
pub fn init (&self) {
let inner = unsafe { self.inner() };
assert!(inner.is_none(), "Internal error: SourceManager double initialized");
inner.replace(SlotMap::default());
}
pub fn load<P: AsRef<Path>> (&self, path: P) -> IOResult<SourceKey> {
for src in self.map().values() {
if src.path == path.as_ref() {
return Err(std::io::Error::from(std::io::ErrorKind::AlreadyExists))
}
}
Ok(self.map().insert(Source::load(path)?))
}
pub fn get (&self, key: SourceKey) -> Option<&Source> {
self.map().get(key)
}
}