1use crate::span::Span;
5use crate::error::KoreError;
6
7pub struct Diagnostics<'a> {
9 source: &'a str,
10 filename: &'a str,
11}
12
13impl<'a> Diagnostics<'a> {
14 pub fn new(source: &'a str, filename: &'a str) -> Self {
15 Self { source, filename }
16 }
17
18 pub fn format_error(&self, error: &KoreError) -> String {
20 match error {
21 KoreError::Lexer { message, span } => self.format_with_context("Lexer Error", message, *span),
22 KoreError::Parser { message, span } => self.format_with_context("Parse Error", message, *span),
23 KoreError::Type { message, span } => self.format_with_context("Type Error", message, *span),
24 KoreError::Effect { message, span } => self.format_with_context("Effect Error", message, *span),
25 KoreError::Borrow { message, span } => self.format_with_context("Borrow Error", message, *span),
26 KoreError::Codegen { message, span } => self.format_with_context("Codegen Error", message, *span),
27 KoreError::Runtime { message } => format!(
28 "\n\x1b[1;31merror\x1b[0m: {}\n",
29 message
30 ),
31 KoreError::Io(e) => format!(
32 "\n\x1b[1;31merror\x1b[0m: IO error: {}\n",
33 e
34 ),
35 }
36 }
37
38 fn format_with_context(&self, error_type: &str, message: &str, span: Span) -> String {
39 let (line_num, col, line_content) = self.get_line_info(span);
40
41 let mut output = String::new();
42
43 output.push_str(&format!(
45 "\n\x1b[1;31merror[{}]\x1b[0m: {}\n",
46 error_type, message
47 ));
48
49 output.push_str(&format!(
51 " \x1b[1;34m-->\x1b[0m {}:{}:{}\n",
52 self.filename, line_num, col
53 ));
54
55 output.push_str(" \x1b[1;34m|\x1b[0m\n");
57
58 output.push_str(&format!(
60 "\x1b[1;34m{:>3} |\x1b[0m {}\n",
61 line_num, line_content
62 ));
63
64 let pointer_offset = col.saturating_sub(1);
66 let content_len = line_content.len();
67 let remaining_len = content_len.saturating_sub(pointer_offset);
68 let span_len = span.end.saturating_sub(span.start);
69 let pointer_len = span_len.min(remaining_len).max(1);
70
71 output.push_str(&format!(
72 " \x1b[1;34m|\x1b[0m {}\x1b[1;31m{}\x1b[0m\n",
73 " ".repeat(pointer_offset),
74 "^".repeat(pointer_len)
75 ));
76
77 output.push_str(" \x1b[1;34m|\x1b[0m\n");
79
80 output
81 }
82
83 fn get_line_info(&self, span: Span) -> (usize, usize, &str) {
85 let mut line_num = 1;
86 let mut line_start = 0;
87
88 let start = span.start.min(self.source.len());
90
91 for (i, c) in self.source.char_indices() {
92 if i >= start {
93 break;
94 }
95 if c == '\n' {
96 line_num += 1;
97 line_start = i + 1;
98 }
99 }
100
101 let col = start.saturating_sub(line_start) + 1;
102
103 let line_end = if start < self.source.len() {
105 self.source[start..]
106 .find('\n')
107 .map(|i| start + i)
108 .unwrap_or(self.source.len())
109 } else {
110 self.source.len()
111 };
112
113 let line_start = line_start.min(self.source.len());
114 let line_content = &self.source[line_start..line_end];
115
116 (line_num, col, line_content)
117 }
118}
119
120pub fn format_simple_error(error: &KoreError) -> String {
122 match error {
123 KoreError::Runtime { message } => format!("Runtime Error: {}", message),
124 _ => format!("{}", error),
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn test_line_info() {
134 let source = "let x = 5\nlet y = x + 1\nprint(y)";
135 let diag = Diagnostics::new(source, "test.kr");
136
137 let (line, col, content) = diag.get_line_info(Span::new(0, 3));
139 assert_eq!(line, 1);
140 assert_eq!(col, 1);
141 assert_eq!(content, "let x = 5");
142
143 let (line, col, content) = diag.get_line_info(Span::new(14, 15));
145 assert_eq!(line, 2);
146 assert_eq!(content, "let y = x + 1");
147 }
148}
149