1use std::{ops::Range, sync::Arc};
2
3use crate::{SourceMap, Vm, backend::chunk::SourceLocation, nodes::SourceSpan};
4
5#[derive(Debug, Clone, Copy, PartialEq)]
6pub enum ErrorMessageFormat {
7 Verbose,
8 Compact,
9 Minimal,
10}
11
12#[derive(Debug, Clone, PartialEq)]
13pub struct QangPipelineError(Vec<QangCompilerError>);
14
15impl QangPipelineError {
16 pub fn new(errors: Vec<QangCompilerError>) -> Self {
17 Self(errors)
18 }
19
20 pub fn all(&self) -> &[QangCompilerError] {
21 &self.0
22 }
23
24 pub fn into_errors(self) -> Vec<QangCompilerError> {
25 self.0
26 }
27}
28
29#[derive(Debug, Clone, PartialEq, Copy)]
30pub enum QangErrorKind {
31 Syntax,
32 Analysis,
33 Assembler,
34}
35
36impl std::fmt::Display for QangErrorKind {
37 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38 match self {
39 Self::Syntax => write!(f, "Syntax Error"),
40 Self::Analysis => write!(f, "Analysis Error"),
41 Self::Assembler => write!(f, "Assembler Error"),
42 }
43 }
44}
45
46#[derive(Debug, Clone, PartialEq)]
47pub struct QangCompilerError {
48 pub message: String,
49 pub span: SourceSpan,
50 pub kind: QangErrorKind,
51 pub source_map: Arc<SourceMap>,
52}
53
54impl QangCompilerError {
55 pub fn new_syntax_error(message: String, span: SourceSpan, source_map: Arc<SourceMap>) -> Self {
56 Self {
57 message,
58 span,
59 kind: QangErrorKind::Syntax,
60 source_map,
61 }
62 }
63
64 pub fn new_analysis_error(
65 message: String,
66 span: SourceSpan,
67 source_map: Arc<SourceMap>,
68 ) -> Self {
69 Self {
70 message,
71 span,
72 kind: QangErrorKind::Analysis,
73 source_map,
74 }
75 }
76
77 pub fn new_assembler_error(
78 message: String,
79 span: SourceSpan,
80 source_map: Arc<SourceMap>,
81 ) -> Self {
82 Self {
83 message,
84 span,
85 kind: QangErrorKind::Assembler,
86 source_map,
87 }
88 }
89
90 pub fn into_formatted(self) -> Self {
91 let message = pretty_print_error(&self.source_map, self.kind, &self.message, self.span);
92
93 Self {
94 message,
95 span: self.span,
96 kind: self.kind,
97 source_map: self.source_map,
98 }
99 }
100
101 pub fn into_short_formatted(self) -> Self {
102 let line_num = self.source_map.get_line_number(self.span.start);
103 let col_num = self.source_map.get_column_number(self.span.start);
104
105 let message = format!(
106 "{} at line {}, column {}: {}\n",
107 self.kind, line_num, col_num, self.message
108 );
109
110 Self {
111 message,
112 span: self.span,
113 kind: self.kind,
114 source_map: self.source_map,
115 }
116 }
117}
118
119impl std::fmt::Display for QangCompilerError {
120 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121 write!(f, "{}", self.message)
122 }
123}
124
125impl std::error::Error for QangCompilerError {}
126
127#[derive(Debug, Clone)]
128pub struct NativeFunctionError(pub String);
129
130impl NativeFunctionError {
131 pub fn new(message: &str) -> Self {
132 Self(message.to_string())
133 }
134
135 pub fn into_qang_error(self, loc: SourceLocation) -> QangRuntimeError {
136 QangRuntimeError::new(self.0, loc)
137 }
138}
139
140impl From<&'static str> for NativeFunctionError {
141 fn from(value: &'static str) -> Self {
142 NativeFunctionError::new(value)
143 }
144}
145
146#[derive(Debug, Clone, PartialEq)]
147pub struct Trace {
148 callee: String,
149 module: Option<String>,
150 loc: SourceLocation,
151}
152
153impl Trace {
154 pub fn new(callee: &str, loc: SourceLocation) -> Self {
155 Self {
156 callee: callee.to_string(),
157 module: None,
158 loc,
159 }
160 }
161
162 pub fn new_with_module(callee: &str, module: &str, loc: SourceLocation) -> Self {
163 Self {
164 callee: callee.to_string(),
165 module: Some(module.to_string()),
166 loc,
167 }
168 }
169}
170
171impl std::fmt::Display for Trace {
172 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173 if let Some(module) = &self.module {
174 write!(
175 f,
176 " at {} in {} (line {}, column {})",
177 self.callee, module, self.loc.line, self.loc.col
178 )
179 } else {
180 write!(
181 f,
182 " at {} (line {}, column {})",
183 self.callee, self.loc.line, self.loc.col
184 )
185 }
186 }
187}
188
189#[derive(Debug, Clone, PartialEq)]
190pub struct QangRuntimeError {
191 pub message: String,
192 pub stack_trace: Vec<Trace>,
193}
194
195impl QangRuntimeError {
196 pub fn new(message: String, loc: SourceLocation) -> Self {
197 Self {
198 message: format!(
199 "Runtime Error at line {}, column {}: {}",
200 loc.line, loc.col, message
201 )
202 .to_string(),
203 stack_trace: Vec::new(),
204 }
205 }
206
207 pub fn with_stack_trace(mut self, stack_trace: Vec<Trace>) -> Self {
208 self.stack_trace = stack_trace;
209 self
210 }
211}
212
213impl std::fmt::Display for QangRuntimeError {
214 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215 write!(f, "{}", self.message)?;
216 if !self.stack_trace.is_empty() {
217 writeln!(f)?;
218 writeln!(f, "Stack trace:")?;
219 for trace in &self.stack_trace {
220 writeln!(f, "{}", trace)?;
221 }
222 }
223 Ok(())
224 }
225}
226
227impl std::error::Error for QangRuntimeError {}
228
229#[derive(Debug, Clone, Default)]
230pub struct ErrorReporter {
231 errors: Vec<QangCompilerError>,
232}
233
234impl ErrorReporter {
235 pub fn new() -> Self {
236 Self { errors: Vec::new() }
237 }
238
239 pub fn report_error(&mut self, error: QangCompilerError) {
240 self.errors.push(error);
241 }
242
243 pub fn has_errors(&self) -> bool {
244 !self.errors.is_empty()
245 }
246
247 pub fn errors(&self) -> &[QangCompilerError] {
248 &self.errors
249 }
250
251 pub fn take_errors(&mut self) -> Vec<QangCompilerError> {
252 std::mem::take(&mut self.errors)
253 }
254
255 pub fn merge(&mut self, mut other: Self) {
256 self.errors.extend(other.take_errors());
257 }
258}
259
260pub fn pretty_print_error(
261 source_map: &SourceMap,
262 kind: QangErrorKind,
263 message: &str,
264 span: SourceSpan,
265) -> String {
266 let mut output = String::new();
267
268 let line_num = source_map.get_line_number(span.start);
269 let col_num = source_map.get_column_number(span.start);
270
271 output.push_str(&format!(
273 "{} at line {}, column {}: {}\n",
274 kind, line_num, col_num, message
275 ));
276
277 let line_chars = source_map.get_line(line_num);
279 let line_str: String = line_chars.iter().collect();
280
281 let line_num_str = line_num.to_string();
283 let padding = " ".repeat(line_num_str.len());
284
285 output.push_str(&format!(" {} | {}\n", line_num, line_str));
287
288 let error_pointer = create_error_pointer(source_map, span, &line_str, col_num);
290 output.push_str(&format!(" {} | {}\n", padding, error_pointer));
291
292 output
293}
294
295fn create_error_pointer(
296 source_map: &SourceMap,
297 span: SourceSpan,
298 line_str: &str,
299 col_num: u32,
300) -> String {
301 let mut pointer = String::new();
302
303 for i in 1..col_num {
305 if i <= line_str.len() as u32 {
306 let ch = line_str.chars().nth((i - 1) as usize).unwrap_or(' ');
307 if ch == '\t' {
308 pointer.push('\t');
309 } else {
310 pointer.push(' ');
311 }
312 } else {
313 pointer.push(' ');
314 }
315 }
316
317 let start_col = source_map.get_column_number(span.start) as usize;
319 let end_col = source_map.get_column_number(span.end.saturating_sub(1)) as usize;
320
321 if start_col == end_col || span.start == span.end {
323 pointer.push('^');
324 } else {
325 let span_length = (end_col - start_col + 1).max(1);
327 for i in 0..span_length {
328 if i == 0 {
329 pointer.push('^');
330 } else {
331 pointer.push('~');
332 }
333 }
334 }
335
336 pointer
337}
338
339impl Vm {
340 pub fn get_stack_trace(&self) -> Vec<Trace> {
341 self.get_stack_trace_from_frames(0..self.state.frame_count)
342 }
343
344 fn get_stack_trace_from_frames(&self, frame_id_range: Range<usize>) -> Vec<Trace> {
345 let mut traces = Vec::new();
346 let mut previous_module: Option<String> = None;
347
348 for frame_idx in frame_id_range {
349 let frame = &self.state.frames[frame_idx];
350 let closure = self.alloc.closures.get_closure(frame.closure);
351 let function = self.alloc.get_function(closure.function);
352
353 let name = &clean_canonicalized_path(self.alloc.strings.get(function.name));
356
357 let loc = if frame.ip > 0 {
358 function
359 .chunk
360 .locs
361 .get(frame.ip - 1)
362 .copied()
363 .unwrap_or_default()
364 } else {
365 SourceLocation::default()
366 };
367
368 let current_module = closure.module_context.map(|module_handle| {
370 let module_string_handle = self.state.modules.get_module_id(module_handle);
371 let raw_path = self.alloc.strings.get(module_string_handle);
372 clean_canonicalized_path(raw_path)
373 });
374
375 let should_show_module = match (&previous_module, ¤t_module) {
377 (None, Some(_)) => true, (Some(prev), Some(curr)) => prev != curr, _ => false, };
381
382 if should_show_module {
383 if let Some(module_name) = ¤t_module {
384 traces.push(Trace::new_with_module(name, module_name, loc));
385 } else {
386 traces.push(Trace::new(name, loc));
387 }
388 } else {
389 traces.push(Trace::new(name, loc));
390 }
391
392 previous_module = current_module;
393 }
394
395 traces
396 }
397}
398
399fn clean_canonicalized_path(path: &str) -> String {
400 #[cfg(target_os = "windows")]
402 if let Some(stripped) = path.strip_prefix(r"\\?\") {
403 return stripped.to_string();
404 }
405
406 #[cfg(not(target_os = "windows"))]
408 if let Some(stripped) = path.strip_prefix("./") {
409 return stripped.to_string();
410 }
411
412 path.to_string()
413}
414
415#[cfg(test)]
416mod tests {
417 use super::*;
418 use crate::SourceMap;
419
420 fn error_summary(errors: &[QangCompilerError]) -> String {
421 if errors.is_empty() {
422 "No errors found.".to_string()
423 } else if errors.len() == 1 {
424 "Found 1 error.".to_string()
425 } else {
426 format!("Found {} errors.", errors.len())
427 }
428 }
429
430 #[test]
431 fn test_error_reporter_basic_functionality() {
432 let source = "var x = 5 +\nvar y = 10;";
433 let source_map = SourceMap::from_source(source.to_string());
434 let mut reporter = ErrorReporter::new();
435
436 reporter.report_error(
438 QangCompilerError::new_syntax_error(
439 "Expected expression after '+'".to_string(),
440 SourceSpan::new(11, 11),
441 Arc::new(source_map),
442 )
443 .into_formatted(),
444 );
445
446 assert!(reporter.has_errors());
447 assert_eq!(reporter.errors().len(), 1);
448 }
449
450 #[test]
451 fn test_multiple_errors() {
452 let source = "var x = 5 +\nvar y = 10 *\nvar z;";
453 let source_map = SourceMap::from_source(source.to_string());
454 let source_map = Arc::new(source_map);
455 let mut reporter = ErrorReporter::new();
456
457 reporter.report_error(
459 QangCompilerError::new_syntax_error(
460 "Missing operand after '+'".to_string(),
461 SourceSpan::new(11, 11),
462 source_map.clone(),
463 )
464 .into_formatted(),
465 );
466
467 reporter.report_error(
468 QangCompilerError::new_syntax_error(
469 "Missing operand after '*'".to_string(),
470 SourceSpan::new(23, 23),
471 source_map,
472 )
473 .into_formatted(),
474 );
475
476 assert_eq!(reporter.errors().len(), 2);
477
478 let summary = error_summary(reporter.errors());
479 assert_eq!(summary, "Found 2 errors.");
480 }
481
482 #[test]
483 fn test_error_summary_no_errors() {
484 let reporter = ErrorReporter::new();
485
486 let summary = error_summary(reporter.errors());
487 assert!(summary.contains("No errors found."));
488 }
489
490 #[test]
491 fn test_error_summary_single_errors() {
492 let source = "var x = 5;";
493 let source_map = SourceMap::from_source(source.to_string());
494 let mut reporter = ErrorReporter::new();
495
496 reporter.report_error(
497 QangCompilerError::new_syntax_error(
498 "Syntax error 1".to_string(),
499 SourceSpan::new(0, 3),
500 Arc::new(source_map),
501 )
502 .into_formatted(),
503 );
504
505 let summary = error_summary(reporter.errors());
506 assert!(summary.contains("Found 1 error."));
507 }
508
509 #[test]
510 fn test_error_summary_multiple_errors() {
511 let source = "var x = 5;";
512 let source_map = SourceMap::from_source(source.to_string());
513 let source_map = Arc::new(source_map);
514 let mut reporter = ErrorReporter::new();
515
516 reporter.report_error(
517 QangCompilerError::new_syntax_error(
518 "Syntax error 1".to_string(),
519 SourceSpan::new(0, 3),
520 source_map.clone(),
521 )
522 .into_formatted(),
523 );
524 reporter.report_error(
525 QangCompilerError::new_syntax_error(
526 "Syntax error 2".to_string(),
527 SourceSpan::new(4, 5),
528 source_map,
529 )
530 .into_formatted(),
531 );
532
533 let summary = error_summary(reporter.errors());
534 assert!(summary.contains("Found 2 errors."));
535 }
536}