mimium_lang/utils/
error.rs

1use 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
14/// A dynamic error type that can hold specific error messages and the location where the error happened.
15pub trait ReportableError: std::error::Error {
16    /// message is used for reporting verbose message for `ariadne``.
17    fn get_message(&self) -> String {
18        self.to_string()
19    }
20    /// Label is used for indicating error with the specific position for `ariadne``.
21    /// One error may have multiple labels, because the reason of the error may be caused by the mismatch of the properties in 2 or more different locations in the source (such as the type mismatch).
22    fn get_labels(&self) -> Vec<(Location, String)>;
23}
24
25/// ReportableError implements `PartialEq`` mostly for testing purpose.
26impl 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 a_span = (src.source(), span);color
105        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}