Skip to main content

probl/
error.rs

1//! What can go wrong: errors, and the diagnostics that describe them.
2
3use crate::Outcome;
4use probl_engine::RuntimeError;
5use probl_syntax::{SourceFile, Span};
6use std::fmt;
7use std::ops::Range;
8use std::sync::Arc;
9
10/// What kind of problem an [`Error`] is.
11#[non_exhaustive]
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
13pub enum ErrorKind {
14    /// The program didn't compile. The diagnostics have every error and
15    /// warning.
16    Compile,
17    /// The host misused the library: it ran a program that reads data
18    /// without its data, or with data loaded for another program.
19    Usage,
20    /// The program did something the language doesn't allow.
21    Language,
22    /// The program uses a feature that isn't built yet.
23    Unsupported,
24    /// The run reached a limit, or was cancelled.
25    Limit,
26    /// A bug in Probl.
27    Internal,
28}
29
30/// How serious a [`Diagnostic`] is.
31#[non_exhaustive]
32#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
33pub enum Severity {
34    Error,
35    Warning,
36}
37
38/// An error or a warning about a place in a program. It keeps the program's
39/// source, so it can render itself.
40#[derive(Clone)]
41pub struct Diagnostic {
42    inner: probl_syntax::Diagnostic,
43    file: Arc<SourceFile>,
44}
45
46impl Diagnostic {
47    pub(crate) fn new(inner: probl_syntax::Diagnostic, file: &Arc<SourceFile>) -> Diagnostic {
48        Diagnostic {
49            inner,
50            file: file.clone(),
51        }
52    }
53
54    pub fn severity(&self) -> Severity {
55        match self.inner.severity {
56            probl_syntax::Severity::Error => Severity::Error,
57            probl_syntax::Severity::Warning => Severity::Warning,
58        }
59    }
60
61    pub fn message(&self) -> &str {
62        &self.inner.message
63    }
64
65    pub fn notes(&self) -> &[String] {
66        &self.inner.notes
67    }
68
69    pub fn help(&self) -> Option<&str> {
70        self.inner.help.as_deref()
71    }
72
73    /// Where it is in the source, in bytes.
74    pub fn span(&self) -> Range<usize> {
75        let len = self.file.text.len();
76        let lo = (self.inner.span.lo as usize).min(len);
77        lo..(self.inner.span.hi as usize).clamp(lo, len)
78    }
79
80    /// The line and column where it starts, both from 1.
81    pub fn line_column(&self) -> (usize, usize) {
82        self.file.line_col(self.inner.span.lo)
83    }
84
85    /// As the command line prints it, in color or not.
86    pub fn render(&self, color: bool) -> String {
87        self.inner.render(&self.file, color)
88    }
89}
90
91impl fmt::Debug for Diagnostic {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        f.debug_struct("Diagnostic")
94            .field("severity", &self.severity())
95            .field("message", &self.message())
96            .field("span", &self.span())
97            .finish()
98    }
99}
100
101impl fmt::Display for Diagnostic {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        f.write_str(self.message())
104    }
105}
106
107/// A problem compiling a program, loading its data or running it.
108#[derive(Clone, Debug)]
109pub struct Error {
110    kind: ErrorKind,
111    diagnostics: Vec<Diagnostic>,
112    partial: Option<Arc<Outcome>>,
113}
114
115impl Error {
116    pub fn kind(&self) -> ErrorKind {
117        self.kind
118    }
119
120    /// When compiling, every diagnostic; otherwise, the one that describes
121    /// the problem.
122    pub fn diagnostics(&self) -> &[Diagnostic] {
123        &self.diagnostics
124    }
125
126    /// As the command line prints it, in color or not.
127    pub fn render(&self, color: bool) -> String {
128        self.diagnostics.iter().map(|d| d.render(color)).collect()
129    }
130
131    /// When a run in partial mode had worlds that failed: what the others
132    /// gave, with its [`failures`](Outcome::failures). The error has a
133    /// diagnostic for each place they failed (the first ten). `None` for
134    /// every other error.
135    pub fn partial(&self) -> Option<&Outcome> {
136        self.partial.as_deref()
137    }
138
139    pub(crate) fn partial_mut(&mut self) -> Option<&mut Outcome> {
140        self.partial.as_mut().and_then(Arc::get_mut)
141    }
142
143    /// A partial result: some worlds failed.
144    pub(crate) fn failed(outcome: Outcome) -> Error {
145        Error {
146            kind: ErrorKind::Language,
147            diagnostics: outcome
148                .failures()
149                .iter()
150                .take(10)
151                .map(|f| f.diagnostic().clone())
152                .collect(),
153            partial: Some(Arc::new(outcome)),
154        }
155    }
156
157    pub(crate) fn compile(diagnostics: Vec<probl_syntax::Diagnostic>, file: &Arc<SourceFile>) -> Error {
158        Error {
159            kind: ErrorKind::Compile,
160            diagnostics: diagnostics.into_iter().map(|d| Diagnostic::new(d, file)).collect(),
161            partial: None,
162        }
163    }
164
165    pub(crate) fn runtime(e: RuntimeError, file: &Arc<SourceFile>) -> Error {
166        let kind = match e.kind {
167            probl_engine::ErrorKind::Language => ErrorKind::Language,
168            probl_engine::ErrorKind::Unsupported => ErrorKind::Unsupported,
169            probl_engine::ErrorKind::Limit => ErrorKind::Limit,
170            probl_engine::ErrorKind::Internal => ErrorKind::Internal,
171        };
172        Error {
173            kind,
174            diagnostics: vec![Diagnostic::new(e.to_diagnostic(), file)],
175            partial: None,
176        }
177    }
178
179    pub(crate) fn usage(span: Span, message: &str, help: &str, file: &Arc<SourceFile>) -> Error {
180        Error {
181            kind: ErrorKind::Usage,
182            diagnostics: vec![Diagnostic::new(
183                probl_syntax::Diagnostic::error(span, message).with_help(help),
184                file,
185            )],
186            partial: None,
187        }
188    }
189}
190
191impl fmt::Display for Error {
192    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193        let first = self
194            .diagnostics
195            .iter()
196            .find(|d| d.severity() == Severity::Error)
197            .or(self.diagnostics.first());
198        match first {
199            Some(d) => f.write_str(d.message()),
200            None => write!(f, "{:?} error", self.kind),
201        }
202    }
203}
204
205impl std::error::Error for Error {}