1use crate::{error::RoanError, span::TextSpan};
2use colored::Colorize;
3use log::Level;
4use std::io::{BufWriter, Stderr, Write};
5
6#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct Diagnostic {
10 pub title: String,
12 pub text: Option<String>,
14 pub level: Level,
16 pub location: Option<TextSpan>,
18 pub hint: Option<String>,
20 pub content: Option<String>,
22}
23
24impl Diagnostic {
25 pub fn log_pretty(&self, buff: &mut BufWriter<Stderr>) {
53 writeln!(
54 buff,
55 "{}{}{}",
56 self.level.to_string().to_lowercase().bright_red(),
57 ": ".dimmed(),
58 self.title
59 )
60 .expect("Error writing level");
61
62 if let Some(location) = &self.location {
63 if let Some(content) = &self.content {
64 let line_number = location.start.line;
65 let line = content
66 .lines()
67 .nth((line_number - 1) as usize)
68 .unwrap_or("");
69 let column = location.start.column;
70 let line_content = line.trim_end();
71 let decoration =
72 "^".repeat(location.end.column as usize - location.start.column as usize);
73
74 writeln!(buff, "{} {}:{}", "--->".cyan(), line_number, column)
75 .expect("Error writing line number");
76
77 if line_number > 1 {
78 let line_before = format!("{} |", line_number - 1);
79 writeln!(buff, "{}", line_before.cyan()).expect("Error writing line number");
80 }
81
82 let line_current = format!("{} |", line_number);
83 write!(buff, "{}", line_current.cyan()).expect("Error writing line number");
84 writeln!(buff, " {}", line_content).expect("Error writing content");
85
86 let padding_left =
87 " ".repeat((column + 6 + line_number.to_string().len() as u32) as usize);
88 writeln!(buff, "{}{}", padding_left, decoration.bright_red())
89 .expect("Error writing decoration");
90
91 if line_number > 1 {
92 let line_after = format!("{} |", line_number + 1);
93 writeln!(buff, "{}", line_after.cyan()).expect("Error writing line number");
94 }
95 }
96 }
97
98 if let Some(text) = &self.text {
99 writeln!(buff, "{}", text).expect("Error writing text");
100 }
101
102 self.print_hint(buff);
103 }
104
105 pub fn print_hint(&self, buff: &mut BufWriter<Stderr>) {
111 if let Some(hint) = &self.hint {
112 writeln!(buff, "{}{}", "Hint: ".bright_cyan(), hint.bright_cyan())
113 .expect("Error writing hint");
114 }
115 }
116}
117
118pub fn print_diagnostic(err: anyhow::Error, content: Option<String>) {
135 let pulse_error = err.downcast_ref::<RoanError>();
136
137 if let Some(err) = pulse_error {
138 let err_str = err.to_string();
139
140 if let RoanError::Throw(content, frames) = err {
141 let mut buff = BufWriter::new(std::io::stderr());
142 write!(buff, "{}{}", "error".bright_red(), ": ".dimmed()).expect("Error writing level");
143 writeln!(buff, "{}", content).expect("Error writing text");
144 for frame in frames {
145 writeln!(buff, "{:?}", frame).expect("Error writing text");
146 }
147 return;
148 }
149
150 let diagnostic = match err {
151 RoanError::Io(_) => Diagnostic {
152 title: "IO error".to_string(),
153 text: Some(err_str),
154 level: Level::Error,
155 location: None,
156 hint: None,
157 content: None,
158 },
159 RoanError::RestParameterNotLast(span)
160 | RoanError::RestParameterNotLastPosition(span)
161 | RoanError::MultipleRestParameters(span)
162 | RoanError::SelfParameterCannotBeRest(span)
163 | RoanError::SelfParameterNotFirst(span)
164 | RoanError::MultipleSelfParameters(span)
165 | RoanError::StaticContext(span)
166 | RoanError::StaticMemberAccess(span)
167 | RoanError::StaticMemberAssignment(span) => Diagnostic {
168 title: err_str,
169 text: None,
170 level: Level::Error,
171 location: Some(span.clone()),
172 hint: None,
173 content,
174 },
175 RoanError::InvalidToken(_, span)
176 | RoanError::SemanticError(_, span)
177 | RoanError::UnexpectedToken(_, span)
178 | RoanError::InvalidEscapeSequence(_, span)
179 | RoanError::NonBooleanCondition(_, span)
180 | RoanError::StructNotFoundError(_, span)
181 | RoanError::TraitNotFoundError(_, span) => Diagnostic {
182 title: err_str,
183 text: None,
184 level: Level::Error,
185 location: Some(span.clone()),
186 hint: None,
187 content,
188 },
189 RoanError::TraitMethodNotImplemented(name, methods, span) => Diagnostic {
190 title: format!(
191 "Trait {name} doesn't implement these methods: {}",
192 methods.join(", ")
193 ),
194 text: None,
195 level: Level::Error,
196 location: Some(span.clone()),
197 hint: Some("Method not implemented".to_string()),
198 content,
199 },
200 RoanError::StructAlreadyImplementsTrait(_, _, span) => Diagnostic {
201 title: err_str,
202 text: None,
203 level: Level::Error,
204 location: Some(span.clone()),
205 hint: Some("Struct already implements this trait".to_string()),
206 content,
207 },
208 RoanError::ExpectedToken(expected, hint, span) => Diagnostic {
209 title: format!("Expected {}", expected),
210 text: None,
211 level: Level::Error,
212 location: Some(span.clone()),
213 hint: Some(hint.clone()),
214 content,
215 },
216 RoanError::FailedToImportModule(_, _, span) => Diagnostic {
217 title: err_str,
218 text: None,
219 level: Level::Error,
220 location: Some(span.clone()),
221 hint: None,
222 content,
223 },
224 RoanError::InvalidType(_, _, span) => Diagnostic {
225 title: err_str,
226 text: None,
227 level: Level::Error,
228 location: Some(span.clone()),
229 hint: None,
230 content,
231 },
232 RoanError::ResolverError(_) => Diagnostic {
233 title: err_str,
234 text: None,
235 level: Level::Error,
236 location: None,
237 hint: None,
238 content: None,
239 },
240 RoanError::ModuleError(_) => Diagnostic {
241 title: err_str,
242 text: None,
243 level: Level::Error,
244 location: None,
245 hint: None,
246 content: None,
247 },
248 | RoanError::UndefinedFunctionError(_, span)
249 | RoanError::VariableNotFoundError(_, span)
250 | RoanError::ImportError(_, span)
251 | RoanError::PropertyNotFoundError(_, span)
252 | RoanError::TypeMismatch(_, span)
253 | RoanError::InvalidAssignment(_, span)
254 | RoanError::MissingParameter(_, span)
255 | RoanError::InvalidUnaryOperation(_, span) => Diagnostic {
256 title: err_str,
257 text: None,
258 level: Level::Error,
259 location: Some(span.clone()),
260 hint: None,
261 content,
262 },
263 RoanError::InvalidBreakOrContinue(span) => Diagnostic {
264 title: err_str,
265 text: None,
266 level: Level::Error,
267 location: Some(span.clone()),
268 hint: Some(
269 "Break and continue statements can only be used inside loops".to_string(),
270 ),
271 content,
272 },
273 RoanError::LoopBreak(span) | RoanError::LoopContinue(span) => Diagnostic {
274 title: err_str,
275 text: None,
276 level: Level::Error,
277 location: Some(span.clone()),
278 hint: Some(
279 "Break and continue statements can only be used inside loops".to_string(),
280 ),
281 content,
282 },
283 RoanError::InvalidSpread(span) => Diagnostic {
284 title: err_str,
285 text: None,
286 level: Level::Error,
287 location: Some(span.clone()),
288 hint: Some(
289 "Spread operator can only be used in function calls or vectors".to_string(),
290 ),
291 content,
292 },
293 RoanError::InvalidPropertyAccess(span) => Diagnostic {
294 title: err_str,
295 text: None,
296 level: Level::Error,
297 location: Some(span.clone()),
298 hint: Some("Only string literals or call expressions are allowed".to_string()),
299 content,
300 },
301 RoanError::IndexOutOfBounds(_, _, span) => Diagnostic {
302 title: err_str,
303 text: None,
304 level: Level::Error,
305 location: Some(span.clone()),
306 hint: None,
307 content,
308 },
309 _ => {
310 log::error!("{:?}", err);
311 return;
312 }
313 };
314
315 let mut buff = BufWriter::new(std::io::stderr());
316 diagnostic.log_pretty(&mut buff);
317 } else {
318 log::error!("{:?}", err);
319 }
320}