slang_error/
compiler_error.rs

1use crate::error_codes::ErrorCode;
2use colored::Colorize;
3
4/// Represents a compiler error with a message, line number, column number, position, and token length
5#[derive(Debug)]
6pub struct CompilerError {
7    /// The structured error code for this error
8    pub error_code: ErrorCode,
9    /// The error message
10    pub message: String,
11    /// The line number where the error occurred
12    pub line: usize,
13    /// The column number where the error occurred
14    pub column: usize,
15    /// The byte offset position of the error in the source code
16    pub position: usize,
17    /// The length of the token causing the error, if applicable
18    pub token_length: Option<usize>,
19}
20
21impl CompilerError {
22    /// Creates a new CompilerError with the given error code, message, line number, column number, position, and token length
23    ///
24    /// ### Arguments
25    /// * `error_code` - The structured error code for this error
26    /// * `message` - The error message
27    /// * `line` - The line number where the error occurred
28    /// * `column` - The column number where the error occurred
29    /// * `position` - The byte offset position of the error
30    /// * `token_length` - The length of the token, if applicable
31    ///
32    /// ### Returns
33    /// A new CompilerError object
34    ///
35    /// ### Example
36    /// ```
37    /// use slang_error::{CompilerError, ErrorCode};
38    ///
39    /// let error = CompilerError::new(ErrorCode::ExpectedSemicolon, "Syntax error".to_string(), 10, 5, 0, Some(1));
40    /// ```
41    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    /// Format an error message with line information and source code snippet
60    ///
61    /// This creates a nicely formatted error message similar to Rust's compiler errors,
62    /// with line numbers, source code context, and arrows pointing to the error location.
63    ///
64    /// ### Arguments
65    /// * `line_info` - LineInfo object for the source code context
66    ///
67    /// ### Returns
68    /// A formatted error message string
69    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", // TODO: replace if actual filename is available
95            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
124/// A type alias for a result that can either be a value of type T or a list of compiler errors
125pub type CompileResult<T> = Result<T, Vec<CompilerError>>;
126
127/// Reports a list of compiler errors to stderr
128///
129/// ### Arguments
130/// * `errors` - A slice of CompilerError to report
131/// * `source` - The source code string, used for generating line information
132pub 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    /// Number of tokens on each line (run-length encoded)
179    /// (line_number, tokens_on_line)
180    pub per_line: Vec<(u16, u16)>,
181    /// Reference to the original source code
182    source: &'a str,
183    /// The starting position of each line in the source code
184    pub line_starts: Vec<usize>,
185}
186
187impl LineInfo<'_> {
188    /// Creates a new LineInfo object
189    ///
190    /// ### Arguments
191    /// * `source` - The source code string
192    ///
193    /// ### Returns
194    /// A new LineInfo object with the line starts calculated
195    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    /// Get the line and column number for a token position
212    ///
213    /// ### Arguments
214    /// * `pos` - The position of the token in the source code
215    ///
216    /// ### Returns
217    /// A tuple containing the line number and column number
218    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    /// Get the text for a specific line
230    ///
231    /// ### Arguments
232    /// * `line` - The line number to retrieve
233    ///
234    /// ### Returns
235    /// The text of the line, or None if the line number is invalid
236    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}