1use crate::Outcome;
4use probl_engine::RuntimeError;
5use probl_syntax::{SourceFile, Span};
6use std::fmt;
7use std::ops::Range;
8use std::sync::Arc;
9
10#[non_exhaustive]
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
13pub enum ErrorKind {
14 Compile,
17 Usage,
20 Language,
22 Unsupported,
24 Limit,
26 Internal,
28}
29
30#[non_exhaustive]
32#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
33pub enum Severity {
34 Error,
35 Warning,
36}
37
38#[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 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 pub fn line_column(&self) -> (usize, usize) {
82 self.file.line_col(self.inner.span.lo)
83 }
84
85 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#[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 pub fn diagnostics(&self) -> &[Diagnostic] {
123 &self.diagnostics
124 }
125
126 pub fn render(&self, color: bool) -> String {
128 self.diagnostics.iter().map(|d| d.render(color)).collect()
129 }
130
131 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 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 {}