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)
69 .with_message(&self.message);
70
71 #[cfg(not(target_arch = "wasm32"))]
72 let label = Label::new(span.start_byte..span.end_byte)
73 .with_message(&self.message)
74 .with_color(Color::Red);
75
76 Report::build(ReportKind::Error, (), span.start_byte)
77 .with_message(&self.message)
78 .with_label(label)
79 .with_config(config)
80 .finish()
81 .write(Source::from(&**source), &mut output)
82 .map_err(|_| fmt::Error)?;
83
84 let output_str = String::from_utf8(output).map_err(|_| fmt::Error)?;
85 write!(f, "{}", output_str)
86 } else {
87 write!(f, "[evaluation error] {}", self.message)
89 }
90 }
91}
92
93impl std::error::Error for RuntimeError {}
94
95impl From<anyhow::Error> for RuntimeError {
97 fn from(err: anyhow::Error) -> Self {
98 RuntimeError::new(err.to_string())
99 }
100}
101
102impl From<&str> for RuntimeError {
104 fn from(s: &str) -> Self {
105 RuntimeError::new(s.to_string())
106 }
107}
108
109impl From<String> for RuntimeError {
111 fn from(s: String) -> Self {
112 RuntimeError::new(s)
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn test_runtime_error_with_span() {
122 let source = "output x = undefined_variable + 5".to_string();
123
124 let span = Span::new(11, 29, 1, 12); let error = RuntimeError::with_span(
128 "unknown identifier: undefined_variable".to_string(),
129 span,
130 source.into(),
131 );
132
133 let error_msg = format!("{}", error);
134
135 assert!(error_msg.contains("undefined_variable"));
137 println!("\n=== DEMO: Improved Error Message ===");
138 println!("{}", error);
139 println!("=====================================\n");
140 }
141
142 #[test]
143 fn test_runtime_error_without_span() {
144 let error = RuntimeError::new("simple error".to_string());
145 let error_msg = format!("{}", error);
146 assert_eq!(error_msg, "[evaluation error] simple error");
147 }
148}