slang_error/
compiler_error.rs1use crate::error_codes::ErrorCode;
2use colored::Colorize;
3
4#[derive(Debug)]
6pub struct CompilerError {
7 pub error_code: ErrorCode,
9 pub message: String,
11 pub line: usize,
13 pub column: usize,
15 pub position: usize,
17 pub token_length: Option<usize>,
19}
20
21impl CompilerError {
22 pub fn new(
42 error_code: ErrorCode,
43 message: String,
44 line: usize,
45 column: usize,
46 position: usize,
47 token_length: Option<usize>,
48 ) -> Self {
49 Self {
50 error_code,
51 message,
52 line,
53 column,
54 position,
55 token_length,
56 }
57 }
58
59 pub fn format_for_display(&self, line_info: &LineInfo) -> String {
70 let (line, col) = line_info.get_line_col(self.position);
71
72 let current_line_text = line_info
73 .get_line_text(line)
74 .unwrap_or("<line not available>");
75
76 let line_num_str = format!("{}", line);
77
78 let token_display_length = self.token_length.unwrap_or(1).max(1);
79 let error_marker = " ".repeat(col.saturating_sub(1))
80 + &"^".repeat(token_display_length).bold().red().to_string();
81
82 let indent_width = line_num_str.len() + 1;
83 let indent = " ".repeat(indent_width);
84
85 let arrow = "-->".yellow();
86 let pipe = "|".yellow();
87
88 let mut result = format!(
89 "{} {}: {}\n {} {}:{}:{}\n",
90 "error".red().bold(),
91 self.error_code.to_string().bold().red(),
92 self.error_code.description(),
93 arrow,
94 "main", line,
96 col
97 );
98
99 result += &format!("{indent}{}\n", pipe);
100 result += &format!("{} {} {}\n", line_num_str.yellow(), pipe, current_line_text);
101 result += &format!(
102 "{indent}{} {} {}\n",
103 pipe,
104 error_marker,
105 self.message.bold().red()
106 );
107
108 result
109 }
110}
111
112impl std::fmt::Display for CompilerError {
113 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114 write!(f, "{}: {}", self.error_code, self.message)
115 }
116}
117
118impl std::error::Error for CompilerError {
119 fn description(&self) -> &str {
120 &self.message
121 }
122}
123
124pub type CompileResult<T> = Result<T, Vec<CompilerError>>;
126
127pub fn report_errors(errors: &[CompilerError], source: &str) {
133 let line_info = LineInfo::new(source);
134 for error in errors.iter() {
135 eprintln!("{}", error.format_for_display(&line_info));
136 }
137}
138
139pub struct ErrorCollector {
140 errors: Vec<CompilerError>,
141}
142
143impl Default for ErrorCollector {
144 fn default() -> Self {
145 Self::new()
146 }
147}
148
149impl ErrorCollector {
150 pub fn new() -> Self {
151 Self { errors: Vec::new() }
152 }
153
154 pub fn add_error(&mut self, error: CompilerError) {
155 self.errors.push(error);
156 }
157
158 pub fn has_errors(&self) -> bool {
159 !self.errors.is_empty()
160 }
161
162 pub fn report_errors(&self) {
163 for error in self.errors.iter() {
164 eprintln!("{}", error.message);
165 }
166 }
167
168 pub fn clear(&mut self) {
169 self.errors.clear();
170 }
171
172 pub fn take_errors(&mut self) -> Vec<CompilerError> {
173 std::mem::take(&mut self.errors)
174 }
175}
176
177pub struct LineInfo<'a> {
178 pub per_line: Vec<(u16, u16)>,
181 source: &'a str,
183 pub line_starts: Vec<usize>,
185}
186
187impl LineInfo<'_> {
188 pub fn new(source: &str) -> LineInfo {
196 let mut line_starts = vec![0];
197
198 for (i, c) in source.char_indices() {
199 if c == '\n' {
200 line_starts.push(i + 1);
201 }
202 }
203
204 LineInfo {
205 per_line: Vec::new(),
206 source,
207 line_starts,
208 }
209 }
210
211 pub fn get_line_col(&self, pos: usize) -> (usize, usize) {
219 match self.line_starts.binary_search(&pos) {
220 Ok(line) => (line + 1, 1),
221 Err(line) => {
222 let line_idx = line - 1;
223 let col = pos - self.line_starts[line_idx] + 1;
224 (line_idx + 1, col)
225 }
226 }
227 }
228
229 pub fn get_line_text(&self, line: usize) -> Option<&str> {
237 if line == 0 || line > self.line_starts.len() {
238 return None;
239 }
240
241 let start = self.line_starts[line - 1];
242 let end = if line < self.line_starts.len() {
243 self.line_starts[line]
244 } else {
245 self.source.len()
246 };
247
248 let actual_end =
249 if start < end && end > 0 && self.source.as_bytes().get(end - 1) == Some(&b'\n') {
250 end - 1
251 } else {
252 end
253 };
254
255 Some(&self.source[start..actual_end])
256 }
257}