1use crate::*;
2use std::sync::Arc;
3use std::any::Any;
4
5type Rows = usize;
9type Cols = usize;
10
11#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
12#[derive(Clone, Debug, PartialEq, Eq)]
13pub struct CompilerSourceRange {
14 pub file: &'static str,
15 pub line: u32,
16}
17
18impl CompilerSourceRange {
19 #[track_caller]
20 pub fn here() -> Self {
21 let loc = std::panic::Location::caller();
22 Self {
23 file: loc.file(),
24 line: loc.line(),
25 }
26 }
27}
28
29#[macro_export]
30macro_rules! compiler_loc {
31 () => {
32 $crate::CompilerSourceRange {
33 file: file!(),
34 line: line!(),
35 }
36 };
37}
38
39trait ErrorKindCallbacks: Send + Sync {
40 fn name(&self, data: &dyn Any) -> String;
41 fn message(&self, data: &dyn Any) -> String;
42}
43
44struct CallbacksImpl<K> {
45 _marker: std::marker::PhantomData<K>,
47}
48
49impl<K> CallbacksImpl<K> {
50 fn new() -> Self {
51 Self { _marker: std::marker::PhantomData }
52 }
53}
54
55impl<K> ErrorKindCallbacks for CallbacksImpl<K>
56where
57 K: MechErrorKind + 'static,
58{
59 fn name(&self, data: &dyn Any) -> String {
60 let k = data.downcast_ref::<K>().expect("wrong kind type in vtable");
62 k.name().to_string()
63 }
64
65 fn message(&self, data: &dyn Any) -> String {
66 let k = data.downcast_ref::<K>().expect("wrong kind type in vtable");
67 k.message()
68 }
69}
70
71pub trait MechErrorKind: std::fmt::Debug + Send + Sync + Clone {
72 fn name(&self) -> &str;
73 fn message(&self) -> String;
74}
75
76#[derive(Clone)]
79pub struct MechError {
80 kind_data: Arc<dyn Any + Send + Sync>,
81 kind_callbacks: Arc<dyn ErrorKindCallbacks>, pub program_range: Option<SourceRange>,
83 pub annotations: Vec<SourceRange>,
84 pub tokens: Vec<Token>,
85 pub compiler_location: Option<CompilerSourceRange>,
86 pub source: Option<Box<MechError>>, pub message: Option<String>,
88}
89
90impl std::fmt::Debug for MechError {
91 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 f.debug_struct("MechError")
93 .field("kind name", &self.kind_name())
94 .field("kind message", &self.kind_message())
95 .field("message", &self.message)
96 .field("program_range", &self.program_range)
97 .field("annotations", &self.annotations)
98 .field("tokens", &self.tokens)
99 .field("compiler_location", &self.compiler_location)
100 .field("source", &self.source)
101 .finish()
102 }
103}
104
105impl MechError {
106 pub fn new<K: MechErrorKind + 'static>(
107 kind: K,
108 message: Option<String>
109 ) -> Self {
110 MechError {
111 kind_data: Arc::new(kind),
112 kind_callbacks: Arc::new(CallbacksImpl::<K>::new()),
113 program_range: None,
114 annotations: Vec::new(),
115 tokens: Vec::new(),
116 compiler_location: None,
117 source: None,
118 message,
119 }
120 }
121
122 pub fn kind_as<K: MechErrorKind + 'static>(&self) -> Option<&K> {
124 self.kind_data.downcast_ref::<K>()
125 }
126
127 pub fn kind_name(&self) -> String {
129 self.kind_callbacks.name(self.kind_data.as_ref())
130 }
131
132 pub fn kind_message(&self) -> String {
134 self.kind_callbacks.message(self.kind_data.as_ref())
135 }
136
137 pub fn display_message(&self) -> String {
139 if let Some(ref m) = self.message { m.clone() } else { self.kind_message() }
140 }
141
142 pub fn kind_downcast_ref<K: 'static>(&self) -> Option<&K> {
144 self.kind_data.downcast_ref::<K>()
145 }
146
147 #[track_caller]
149 pub fn with_compiler_loc(mut self) -> Self {
150 self.compiler_location = Some(CompilerSourceRange::here());
151 self
152 }
153
154 pub fn with_specific_compiler_loc(mut self, loc: CompilerSourceRange) -> Self {
156 self.compiler_location = Some(loc);
157 self
158 }
159
160 pub fn with_annotation(mut self, range: SourceRange) -> Self {
162 self.annotations.push(range);
163 self
164 }
165
166 pub fn with_annotations<I>(mut self, iter: I) -> Self
168 where
169 I: IntoIterator<Item = SourceRange>,
170 {
171 self.annotations.extend(iter);
172 self
173 }
174
175 pub fn with_tokens<I>(mut self, iter: I) -> Self
177 where
178 I: IntoIterator<Item = Token>,
179 {
180 self.tokens.extend(iter);
181 self
182 }
183
184 pub fn with_source(mut self, src: MechError) -> Self {
186 self.source = Some(Box::new(src));
187 self
188 }
189
190 pub fn primary_range(&self) -> Option<SourceRange> {
192 self.program_range.clone()
193 }
194
195 pub fn simple_message(&self) -> String {
197 format!("{}: {}", self.kind_name(), self.kind_message())
198 }
199
200 pub fn full_chain_message(&self) -> String {
202 let mut out = self.simple_message();
203 let mut current = &self.source;
204
205 while let Some(err) = current {
206 out.push_str("\nCaused by: ");
207 out.push_str(&err.simple_message());
208 current = &err.source;
209 }
210
211 out
212 }
213
214 pub fn boxed(self) -> Box<Self> {
215 Box::new(self)
216 }
217
218 #[cfg(feature = "pretty_print")]
219 pub fn to_html(&self) -> String {
220 fn escape_html(input: &str) -> String {
221 input
222 .replace('&', "&")
223 .replace('<', "<")
224 .replace('>', ">")
225 .replace('"', """)
226 .replace('\'', "'")
227 }
228
229 let source_range = self
230 .program_range
231 .clone()
232 .or_else(|| self.tokens.first().map(|token| token.src_range.clone()));
233
234 let token_html = if self.tokens.is_empty() {
235 String::new()
236 } else {
237 let token_list = self
238 .tokens
239 .iter()
240 .map(|token| format!(
241 "<li><span class=\"mech-runtime-error-token\">{}</span> <span class=\"mech-runtime-error-token-range\">[{}:{}, {}:{})</span></li>",
242 escape_html(&token.to_string()),
243 token.src_range.start.row,
244 token.src_range.start.col,
245 token.src_range.end.row,
246 token.src_range.end.col
247 ))
248 .collect::<Vec<String>>()
249 .join("");
250 format!(
251 "<div class=\"mech-runtime-error-section\"><div class=\"mech-runtime-error-section-title\">Source tokens</div><ul class=\"mech-runtime-error-list\">{}</ul></div>",
252 token_list
253 )
254 };
255
256 let mut causes = vec![];
257 let mut current = &self.source;
258 while let Some(err) = current {
259 causes.push(format!(
260 "<li>{}</li>",
261 escape_html(&format!("{}: {}", err.kind_name(), err.display_message()))
262 ));
263 current = &err.source;
264 }
265 let causes_html = if causes.is_empty() {
266 String::new()
267 } else {
268 format!(
269 "<div class=\"mech-runtime-error-section\"><div class=\"mech-runtime-error-section-title\">Caused by</div><ul class=\"mech-runtime-error-list\">{}</ul></div>",
270 causes.join("")
271 )
272 };
273
274 let compiler_location_html = match &self.compiler_location {
275 Some(loc) => format!(
276 "<div class=\"mech-runtime-error-meta-row\"><span class=\"mech-runtime-error-meta-label\">Compiler location</span><span class=\"mech-runtime-error-meta-value\">{}:{}</span></div>",
277 escape_html(loc.file),
278 loc.line
279 ),
280 None => String::new(),
281 };
282
283 format!(
284 "<div class=\"mech-runtime-error\">
285 <div class=\"mech-runtime-error-header\">
286 <span class=\"mech-runtime-error-icon\" aria-hidden=\"true\"></span>
287 <div>
288 <div class=\"mech-runtime-error-title\">{}</div>
289 <div class=\"mech-runtime-error-message\">{}</div>
290 </div>
291 </div>
292 <div class=\"mech-runtime-error-meta\">{}{}</div>
293 {}
294 </div>",
295 escape_html(&self.kind_name()),
296 escape_html(&self.display_message()),
297 token_html,
298 compiler_location_html,
299 causes_html
300 )
301 }
302}
303
304#[derive(Debug, Clone)]
305pub struct UndefinedKindError {
306 pub kind_id: u64,
307}
308impl MechErrorKind for UndefinedKindError {
309 fn name(&self) -> &str {
310 "UndefinedKind"
311 }
312 fn message(&self) -> String {
313 format!("Kind `{}` is not defined.", self.kind_id)
314 }
315}
316
317impl From<std::io::Error> for MechError {
318 fn from(err: std::io::Error) -> Self {
319 MechError::new(
320 IoErrorWrapper { msg: err.to_string() },
321 None
322 )
323 .with_compiler_loc()
324 }
325}
326
327#[derive(Debug, Clone)]
328pub struct DimensionMismatch {
329 pub dims: Vec<usize>,
330}
331impl MechErrorKind for DimensionMismatch {
332 fn name(&self) -> &str { "DimensionMismatch" }
333 fn message(&self) -> String { format!("Matrix dimension mismatch: {:?}", self.dims) }
334}
335
336#[derive(Debug, Clone)]
337pub struct GenericError {
338 pub msg: String,
339}
340impl MechErrorKind for GenericError {
341 fn name(&self) -> &str { "GenericError" }
342
343 fn message(&self) -> String {
344 format!("Error: {}", self.msg)
345 }
346}
347
348#[derive(Debug, Clone)]
349pub struct FeatureNotEnabledError;
350impl MechErrorKind for FeatureNotEnabledError {
351 fn name(&self) -> &str { "FeatureNotEnabled" }
352
353 fn message(&self) -> String {
354 format!("Feature not enabled")
355 }
356}
357
358#[derive(Debug, Clone)]
359pub struct NotExecutableError {}
360impl MechErrorKind for NotExecutableError {
361 fn name(&self) -> &str { "NotExecutable" }
362
363 fn message(&self) -> String {
364 format!("Not executable")
365 }
366}
367
368#[derive(Debug, Clone)]
369pub struct IoErrorWrapper {
370 pub msg: String,
371}
372impl MechErrorKind for IoErrorWrapper {
373 fn name(&self) -> &str { "IoError" }
374
375 fn message(&self) -> String {
376 format!("IO error: {}", self.msg)
377 }
378}