use colored::Colorize;
use std::{
fmt::{Debug, Display},
ops::{Deref, DerefMut},
path::Path,
sync::{
atomic::{AtomicUsize, Ordering},
Arc,
},
};
use dashmap::DashMap;
use internment::Intern;
use once_cell::sync::Lazy;
use std::path::PathBuf;
pub type CtxResult<T> = Result<T, CtxErr>;
pub trait ToCtxErr {
type Okay;
fn err_ctx(self, loc: Option<CtxLocation>) -> Result<Self::Okay, CtxErr>;
}
impl<T, E: Into<anyhow::Error>> ToCtxErr for Result<T, E> {
type Okay = T;
fn err_ctx(self, loc: Option<CtxLocation>) -> Result<Self::Okay, CtxErr> {
self.map_err(|e| Ctx {
inner: Arc::new(e.into()),
context: loc,
})
}
}
pub type CtxErr = Ctx<anyhow::Error>;
pub trait ToCtx: Sized {
fn with_ctx(self, loc: impl Into<Option<CtxLocation>>) -> Ctx<Self> {
Ctx {
inner: Arc::new(self),
context: loc.into(),
}
}
}
impl<T: Sized> ToCtx for T {}
#[derive(PartialEq, Eq, PartialOrd, Ord)]
pub struct Ctx<T> {
inner: Arc<T>,
context: Option<CtxLocation>,
}
impl<T> Clone for Ctx<T> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
context: self.context,
}
}
}
impl<T: Debug> Debug for Ctx<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.inner.fmt(f)
}
}
impl<T> Ctx<T> {
pub fn ctx(&self) -> Option<CtxLocation> {
self.context
}
}
impl<T> Deref for Ctx<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.inner.deref()
}
}
impl<T: Clone> DerefMut for Ctx<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
Arc::make_mut(&mut self.inner)
}
}
impl<T> From<T> for Ctx<T> {
fn from(val: T) -> Self {
Ctx {
inner: val.into(),
context: None,
}
}
}
impl<T: Display> Ctx<T> {
pub fn pretty_print(&self, source_lookup: impl Fn(ModuleId) -> Option<String>) -> String {
let error_location: String;
let mut detailed_line: Option<String> = None;
if let Some(ctx) = self.ctx() {
if let Some(source_full_string) = source_lookup(ctx.source) {
let mut char_counter = 0;
let mut errloc = ctx.source.to_string();
for (lineno, line) in source_full_string.split('\n').enumerate() {
let line_len = line.len() + 1;
if char_counter + line.len() > ctx.start_offset {
let line_offset = ctx.start_offset - char_counter;
errloc = format!("{}:{}", ctx.source, lineno + 1);
detailed_line = Some(format!("{}\n{}", line, {
let mut toret = String::new();
for _ in 0..line_offset {
toret.push(' ');
}
toret.push_str(&format!("{}", "^".bright_green().bold()));
for _ in
1..(ctx.end_offset - ctx.start_offset).min(line.len() - line_offset)
{
toret.push_str(&format!("{}", "~".bright_green().bold()));
}
toret
}));
break;
}
char_counter += line_len
}
error_location = errloc;
} else {
error_location = ctx.source.to_string();
}
} else {
error_location = "(unknown location)".to_string();
}
let err_str = format!(
"{}: {} {}",
error_location.bold(),
"error:".bold().red(),
self.inner.to_string().bold()
);
if let Some(line) = detailed_line {
let lines = line.lines().collect::<Vec<&str>>().join("\n\t");
format!("{}\n\t{}", err_str, lines)
} else {
err_str
}
}
}
impl<T: Display> Display for Ctx<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(
&self.pretty_print(|mid| std::fs::read_to_string(&mid.to_string()).ok()),
f,
)
}
}
#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug)]
pub struct CtxLocation {
pub source: ModuleId,
pub start_offset: usize,
pub end_offset: usize,
}
#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Hash)]
pub struct ModuleId {
absolute_path: Intern<String>,
}
impl Display for ModuleId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.absolute_path, f)
}
}
impl ModuleId {
pub fn new(path: &Path) -> Self {
let canon = path.to_string_lossy().into_owned();
ModuleId {
absolute_path: Intern::new(canon),
}
}
pub fn from_path(path: &Path) -> Self {
let canon = path.to_string_lossy().into_owned();
ModuleId {
absolute_path: Intern::new(canon),
}
}
pub fn relative(self, frag: &str) -> Self {
let mut path = Path::new(self.absolute_path.as_str()).to_owned();
path.pop();
path.push(frag);
let new = path.to_string_lossy().into_owned();
ModuleId {
absolute_path: Intern::new(new),
}
}
pub fn load_file(self) -> std::io::Result<String> {
std::fs::read_to_string(self.absolute_path.as_str())
}
pub fn uniqid(self) -> usize {
static CACHE: Lazy<DashMap<ModuleId, usize>> = Lazy::new(DashMap::new);
static GCOUNTER: AtomicUsize = AtomicUsize::new(0);
*CACHE
.entry(self)
.or_insert_with(|| GCOUNTER.fetch_add(1, Ordering::Relaxed))
}
}
#[derive(Clone)]
pub struct ProjectRoot(pub PathBuf);
impl ProjectRoot {
pub fn module_from_root(self, path: &Path) -> ModuleId {
ModuleId::new(&self.0.join(path))
}
}