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 pub fn with_function_context(self, function_name: &str) -> Self {
53 Self {
54 message: format!("in {}: {}", function_name, self.message),
55 span: self.span,
56 source: self.source,
57 }
58 }
59}
60
61impl fmt::Display for RuntimeError {
62 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
63 if let (Some(span), Some(source)) = (&self.span, &self.source) {
65 let mut output = Vec::new();
66
67 #[cfg(target_arch = "wasm32")]
70 let config = Config::default().with_color(false);
71
72 #[cfg(not(target_arch = "wasm32"))]
73 let config = Config::default();
74
75 #[cfg(target_arch = "wasm32")]
77 let label = Label::new(span.start_byte..span.end_byte).with_message(&self.message);
78
79 #[cfg(not(target_arch = "wasm32"))]
80 let label = Label::new(span.start_byte..span.end_byte)
81 .with_message(&self.message)
82 .with_color(Color::Red);
83
84 Report::build(ReportKind::Error, (), span.start_byte)
85 .with_message(&self.message)
86 .with_label(label)
87 .with_config(config)
88 .finish()
89 .write(Source::from(&**source), &mut output)
90 .map_err(|_| fmt::Error)?;
91
92 let output_str = String::from_utf8(output).map_err(|_| fmt::Error)?;
93 write!(f, "{}", output_str)
94 } else {
95 write!(f, "[evaluation error] {}", self.message)
97 }
98 }
99}
100
101impl std::error::Error for RuntimeError {}
102
103impl From<anyhow::Error> for RuntimeError {
105 fn from(err: anyhow::Error) -> Self {
106 RuntimeError::new(err.to_string())
107 }
108}
109
110impl From<&str> for RuntimeError {
112 fn from(s: &str) -> Self {
113 RuntimeError::new(s.to_string())
114 }
115}
116
117impl From<String> for RuntimeError {
119 fn from(s: String) -> Self {
120 RuntimeError::new(s)
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn test_runtime_error_with_span() {
130 let source = "output x = undefined_variable + 5".to_string();
131
132 let span = Span::new(11, 29, 1, 12); let error = RuntimeError::with_span(
136 "unknown identifier: undefined_variable".to_string(),
137 span,
138 source.into(),
139 );
140
141 let error_msg = format!("{}", error);
142
143 assert!(error_msg.contains("undefined_variable"));
145 println!("\n=== DEMO: Improved Error Message ===");
146 println!("{}", error);
147 println!("=====================================\n");
148 }
149
150 #[test]
151 fn test_runtime_error_without_span() {
152 let error = RuntimeError::new("simple error".to_string());
153 let error_msg = format!("{}", error);
154 assert_eq!(error_msg, "[evaluation error] simple error");
155 }
156}