Skip to main content

mech_core/
error.rs

1use crate::*;
2use std::sync::Arc;
3use std::any::Any;
4
5// Errors
6// ----------------------------------------------------------------------------
7
8type 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  // zero-sized; all behavior encoded in trait impl below
46  _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    // downcast and call name()
61    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//#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
77//#[derive(Clone, Debug, Eq, PartialEq, Hash)]
78#[derive(Clone)]
79pub struct MechError {
80  kind_data: Arc<dyn Any + Send + Sync>,
81  kind_callbacks: Arc<dyn ErrorKindCallbacks>, // object-safe vtable
82  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>>, // for propagation
87  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  /// Get the kind as a specific type, if it matches
123  pub fn kind_as<K: MechErrorKind + 'static>(&self) -> Option<&K> {
124    self.kind_data.downcast_ref::<K>()
125  }
126
127  /// Get the runtime name (delegates to the underlying kind)
128  pub fn kind_name(&self) -> String {
129    self.kind_callbacks.name(self.kind_data.as_ref())
130  }
131
132  /// Get the runtime message (delegates to the underlying kind)
133  pub fn kind_message(&self) -> String {
134    self.kind_callbacks.message(self.kind_data.as_ref())
135  }
136
137  /// Optional helper that returns the message or the explicit `message` override
138  pub fn display_message(&self) -> String {
139    if let Some(ref m) = self.message { m.clone() } else { self.kind_message() }
140  }
141
142  /// If you ever need downcast access to the concrete kind:
143  pub fn kind_downcast_ref<K: 'static>(&self) -> Option<&K> {
144    self.kind_data.downcast_ref::<K>()
145  }
146
147  /// Add a compiler location annotation with the current location
148  #[track_caller]
149  pub fn with_compiler_loc(mut self) -> Self {
150    self.compiler_location = Some(CompilerSourceRange::here());
151    self
152  }
153
154  /// Specify a particular compiler location
155  pub fn with_specific_compiler_loc(mut self, loc: CompilerSourceRange) -> Self {
156    self.compiler_location = Some(loc);
157    self
158  }
159
160  /// Add a source range annotation
161  pub fn with_annotation(mut self, range: SourceRange) -> Self {
162    self.annotations.push(range);
163    self
164  }
165
166  /// Add multiple source range annotations
167  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  /// Add tokens related to this error
176  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  /// Set the source error that caused this one
185  pub fn with_source(mut self, src: MechError) -> Self {
186    self.source = Some(Box::new(src));
187    self
188  }
189
190  /// Get the primary source range associated with this error
191  pub fn primary_range(&self) -> Option<SourceRange> {
192    self.program_range.clone()
193  }
194
195  /// Get a simple message describing the error
196  pub fn simple_message(&self) -> String {
197    format!("{}: {}", self.kind_name(), self.kind_message())
198  }
199
200  /// Get a full chain message including all source errors
201  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('&', "&amp;")
223        .replace('<', "&lt;")
224        .replace('>', "&gt;")
225        .replace('"', "&quot;")
226        .replace('\'', "&#39;")
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}