1use crate::ast::Span;
2use ariadne::{Config, Label, Report, ReportKind, Source};
3use std::{fmt, rc::Rc};
4
5#[cfg(not(target_arch = "wasm32"))]
7use ariadne::Color;
8
9#[derive(Debug)]
11pub struct RuntimeError {
12 pub message: String,
13 pub span: Option<Span>,
14 pub source: Option<Rc<str>>,
15}
16
17impl RuntimeError {
18 pub fn new(message: String) -> Self {
20 Self {
21 message,
22 span: None,
23 source: None,
24 }
25 }
26
27 pub fn with_span(message: String, span: Span, source: Rc<str>) -> Self {
29 Self {
30 message,
31 span: Some(span),
32 source: Some(source),
33 }
34 }
35
36 pub fn with_call_site(self, span: Span, source: Rc<str>) -> Self {
38 if self.span.is_some() {
41 self
42 } else {
43 Self {
44 message: self.message,
45 span: Some(span),
46 source: Some(source),
47 }
48 }
49 }
50}
51
52impl fmt::Display for RuntimeError {
53 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
54 if let (Some(span), Some(source)) = (&self.span, &self.source) {
56 let mut output = Vec::new();
57
58 #[cfg(target_arch = "wasm32")]
61 let config = Config::default().with_color(false);
62
63 #[cfg(not(target_arch = "wasm32"))]
64 let config = Config::default();
65
66 #[cfg(target_arch = "wasm32")]
68 let label = Label::new(span.start_byte..span.end_byte).with_message(&self.message);
69
70 #[cfg(not(target_arch = "wasm32"))]
71 let label = Label::new(span.start_byte..span.end_byte)
72 .with_message(&self.message)
73 .with_color(Color::Red);
74
75 Report::build(ReportKind::Error, (), span.start_byte)
76 .with_message(&self.message)
77 .with_label(label)
78 .with_config(config)
79 .finish()
80 .write(Source::from(&**source), &mut output)
81 .map_err(|_| fmt::Error)?;
82
83 let output_str = String::from_utf8(output).map_err(|_| fmt::Error)?;
84 write!(f, "{}", output_str)
85 } else {
86 write!(f, "[evaluation error] {}", self.message)
88 }
89 }
90}
91
92impl std::error::Error for RuntimeError {}
93
94impl From<anyhow::Error> for RuntimeError {
96 fn from(err: anyhow::Error) -> Self {
97 RuntimeError::new(err.to_string())
98 }
99}
100
101impl From<&str> for RuntimeError {
103 fn from(s: &str) -> Self {
104 RuntimeError::new(s.to_string())
105 }
106}
107
108impl From<String> for RuntimeError {
110 fn from(s: String) -> Self {
111 RuntimeError::new(s)
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn test_runtime_error_with_span() {
121 let source = "output x = undefined_variable + 5".to_string();
122
123 let span = Span::new(11, 29, 1, 12); let error = RuntimeError::with_span(
127 "unknown identifier: undefined_variable".to_string(),
128 span,
129 source.into(),
130 );
131
132 let error_msg = format!("{}", error);
133
134 assert!(error_msg.contains("undefined_variable"));
136 println!("\n=== DEMO: Improved Error Message ===");
137 println!("{}", error);
138 println!("=====================================\n");
139 }
140
141 #[test]
142 fn test_runtime_error_without_span() {
143 let error = RuntimeError::new("simple error".to_string());
144 let error_msg = format!("{}", error);
145 assert_eq!(error_msg, "[evaluation error] simple error");
146 }
147}