mimium_lang/utils/
error.rs1use std::{
2 collections::HashMap,
3 ops::Range,
4 path::PathBuf,
5 sync::{LazyLock, Mutex},
6};
7
8use ariadne::{ColorGenerator, Label, Report, ReportKind, Source};
9
10use crate::interner::{Symbol, ToSymbol};
11
12use super::metadata::Location;
13
14pub trait ReportableError: std::error::Error {
16 fn get_message(&self) -> String {
18 self.to_string()
19 }
20 fn get_labels(&self) -> Vec<(Location, String)>;
23}
24
25impl PartialEq for dyn ReportableError + '_ {
27 fn eq(&self, other: &Self) -> bool {
28 self.get_labels() == other.get_labels()
29 }
30}
31
32#[derive(Debug, Clone)]
33pub struct SimpleError {
34 pub message: String,
35 pub span: Location,
36}
37impl std::fmt::Display for SimpleError {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 write!(f, "{}", self.message)
40 }
41}
42impl std::error::Error for SimpleError {}
43impl ReportableError for SimpleError {
44 fn get_labels(&self) -> Vec<(Location, String)> {
45 vec![(self.span.clone(), self.message.clone())]
46 }
47}
48
49#[derive(Debug, Clone)]
50pub struct RichError {
51 pub message: String,
52 pub labels: Vec<(Location, String)>,
53}
54impl std::fmt::Display for RichError {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 write!(f, "{}", self.message)
57 }
58}
59impl std::error::Error for RichError {}
60impl ReportableError for RichError {
61 fn get_message(&self) -> String {
62 self.message.clone()
63 }
64 fn get_labels(&self) -> Vec<(Location, String)> {
65 self.labels.clone()
66 }
67}
68impl From<Box<dyn ReportableError + '_>> for RichError {
69 fn from(e: Box<dyn ReportableError + '_>) -> Self {
70 Self {
71 message: e.get_message(),
72 labels: e.get_labels(),
73 }
74 }
75}
76
77struct FileCache {
78 pub storage: HashMap<PathBuf, ariadne::Source<String>>,
79}
80
81impl ariadne::Cache<PathBuf> for FileCache {
82 type Storage = String;
83
84 fn fetch(&mut self, id: &PathBuf) -> Result<&Source<Self::Storage>, impl std::fmt::Debug> {
85 self.storage
86 .get(id)
87 .ok_or_else(|| format!("File not found: {}", id.display()))
88 }
89
90 fn display<'a>(&self, id: &'a PathBuf) -> Option<impl std::fmt::Display + 'a> {
91 Some(id.display())
92 }
93}
94
95static FILE_BUCKET: LazyLock<Mutex<FileCache>> = LazyLock::new(|| {
96 Mutex::new(FileCache {
97 storage: HashMap::new(),
98 })
99});
100
101pub fn report(src: &str, path: PathBuf, errs: &[Box<dyn ReportableError + '_>]) {
102 let mut colors = ColorGenerator::new();
103 for e in errs {
104 let rawlabels = e.get_labels();
106 let labels = rawlabels.iter().map(|(loc, message)| {
107 let span = (path.clone(), loc.span.clone());
108 Label::new(span)
109 .with_message(message)
110 .with_color(colors.next())
111 });
112 let span = (path.clone(), rawlabels[0].0.span.clone());
113 let builder = Report::build(ReportKind::Error, span)
114 .with_message(e.get_message())
115 .with_labels(labels)
116 .finish();
117 if let Ok(mut cache) = FILE_BUCKET.lock() {
118 let mut cache: &mut FileCache = &mut cache;
119 cache
120 .storage
121 .insert(path.clone(), Source::from(src.to_string()));
122 builder.eprint(&mut cache).unwrap();
123 }
124 }
125}
126
127pub fn dump_to_string(errs: &[Box<dyn ReportableError>]) -> String {
128 let mut res = String::new();
129 for e in errs {
130 res += e.get_message().as_str();
131 }
132 res
133}