1use crate::source::{FileRegistry, SourceLocation};
2use crate::token::TokenKind;
3use std::fmt;
4use std::path::{Path, PathBuf};
5
6pub struct DisplayLocation<'a> {
8 pub loc: &'a SourceLocation,
9 pub files: &'a FileRegistry,
10}
11
12impl<'a> fmt::Display for DisplayLocation<'a> {
13 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
14 let path = self.files.get_path(self.loc.file_id);
15 write!(f, "{}:{}:{}", path.display(), self.loc.line, self.loc.column)
16 }
17}
18
19#[derive(Debug)]
21pub enum LexError {
22 UnterminatedComment,
24 UnterminatedString,
26 UnterminatedChar,
28 EmptyCharLit,
30 InvalidChar(char),
32 InvalidEscape(char),
34 InvalidNumber(String),
36 InvalidSuffix(String),
38 UnknownIdentifier(String),
40}
41
42impl fmt::Display for LexError {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 match self {
45 LexError::UnterminatedComment => write!(f, "unterminated block comment"),
46 LexError::UnterminatedString => write!(f, "unterminated string literal"),
47 LexError::UnterminatedChar => write!(f, "unterminated character literal"),
48 LexError::EmptyCharLit => write!(f, "empty character literal"),
49 LexError::InvalidChar(c) => write!(f, "invalid character: {:?}", c),
50 LexError::InvalidEscape(c) => write!(f, "invalid escape sequence: \\{}", c),
51 LexError::InvalidNumber(s) => write!(f, "invalid number: {}", s),
52 LexError::InvalidSuffix(s) => write!(f, "invalid suffix: {}", s),
53 LexError::UnknownIdentifier(s) => write!(f, "unknown identifier: {}", s),
54 }
55 }
56}
57
58#[derive(Debug)]
60pub enum PPError {
61 InvalidDirective(String),
63 MacroRedefinition(String),
65 IncludeNotFound(PathBuf),
67 UnmatchedEndif,
69 MissingEndif,
71 InvalidMacroArgs(String),
73 IoError(PathBuf, String),
75 InvalidCondition(String),
77 InvalidStringize,
79 InvalidTokenPaste,
81 RecursiveMacro(String),
83 UnmatchedElse,
85 ElifAfterElse,
87}
88
89impl fmt::Display for PPError {
90 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91 match self {
92 PPError::InvalidDirective(s) => write!(f, "invalid directive: #{}", s),
93 PPError::MacroRedefinition(s) => write!(f, "macro redefinition: {}", s),
94 PPError::IncludeNotFound(p) => write!(f, "include file not found: {}", p.display()),
95 PPError::UnmatchedEndif => write!(f, "#endif without matching #if"),
96 PPError::MissingEndif => write!(f, "missing #endif"),
97 PPError::InvalidMacroArgs(s) => write!(f, "invalid macro arguments: {}", s),
98 PPError::IoError(p, e) => write!(f, "I/O error reading {}: {}", p.display(), e),
99 PPError::InvalidCondition(s) => write!(f, "invalid preprocessor condition: {}", s),
100 PPError::InvalidStringize => write!(f, "'#' is not followed by a macro parameter"),
101 PPError::InvalidTokenPaste => write!(f, "'##' cannot appear at boundary of macro expansion"),
102 PPError::RecursiveMacro(s) => write!(f, "recursive macro expansion: {}", s),
103 PPError::UnmatchedElse => write!(f, "#else without matching #if"),
104 PPError::ElifAfterElse => write!(f, "#elif after #else"),
105 }
106 }
107}
108
109#[derive(Debug)]
111pub enum ParseError {
112 UnexpectedToken { expected: String, found: TokenKind },
114 UnexpectedEof,
116 InvalidDeclaration(String),
118 InvalidType(String),
120 InvalidAssertArgs { macro_name: String, arg_count: usize },
122 AssertNotFunctionMacro { macro_name: String },
124 NestedAssertNotSupported,
126 MacroEndNotFound,
128}
129
130impl fmt::Display for ParseError {
131 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132 match self {
133 ParseError::UnexpectedToken { expected, found } => {
134 write!(f, "expected {}, found {:?}", expected, found)
135 }
136 ParseError::UnexpectedEof => write!(f, "unexpected end of file"),
137 ParseError::InvalidDeclaration(s) => write!(f, "invalid declaration: {}", s),
138 ParseError::InvalidType(s) => write!(f, "invalid type: {}", s),
139 ParseError::InvalidAssertArgs { macro_name, arg_count } => {
140 write!(f, "assert macro '{}' expects 1 argument, got {}", macro_name, arg_count)
141 }
142 ParseError::AssertNotFunctionMacro { macro_name } => {
143 write!(f, "assert macro '{}' must be called as function macro", macro_name)
144 }
145 ParseError::NestedAssertNotSupported => {
146 write!(f, "nested assert macros are not supported")
147 }
148 ParseError::MacroEndNotFound => {
149 write!(f, "matching MacroEnd not found")
150 }
151 }
152 }
153}
154
155#[derive(Debug)]
157pub enum CompileError {
158 Lex { loc: SourceLocation, kind: LexError },
160 Preprocess { loc: SourceLocation, kind: PPError },
162 Parse { loc: SourceLocation, kind: ParseError },
164}
165
166impl fmt::Display for CompileError {
167 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168 match self {
169 CompileError::Lex { loc, kind } => {
170 write!(f, "{}:{}:{}: lexer error: {}", loc.file_id.as_u32(), loc.line, loc.column, kind)
171 }
172 CompileError::Preprocess { loc, kind } => {
173 write!(f, "{}:{}:{}: preprocessor error: {}", loc.file_id.as_u32(), loc.line, loc.column, kind)
174 }
175 CompileError::Parse { loc, kind } => {
176 write!(f, "{}:{}:{}: parse error: {}", loc.file_id.as_u32(), loc.line, loc.column, kind)
177 }
178 }
179 }
180}
181
182impl std::error::Error for CompileError {}
183
184impl CompileError {
185 pub fn loc(&self) -> &SourceLocation {
187 match self {
188 CompileError::Lex { loc, .. } => loc,
189 CompileError::Preprocess { loc, .. } => loc,
190 CompileError::Parse { loc, .. } => loc,
191 }
192 }
193
194 pub fn format_with_files(&self, files: &FileRegistry) -> String {
196 match self {
197 CompileError::Lex { loc, kind } => {
198 let disp = DisplayLocation { loc, files };
199 format!("{}: lexer error: {}", disp, kind)
200 }
201 CompileError::Preprocess { loc, kind } => {
202 let disp = DisplayLocation { loc, files };
203 format!("{}: preprocessor error: {}", disp, kind)
204 }
205 CompileError::Parse { loc, kind } => {
206 let disp = DisplayLocation { loc, files };
207 format!("{}: parse error: {}", disp, kind)
208 }
209 }
210 }
211
212 pub fn with_files(self, files: &FileRegistry) -> EnrichedCompileError {
218 let loc = self.loc().clone();
219 let file_path = files.try_get_path(loc.file_id).map(|p| p.to_path_buf());
220 let source_line = file_path.as_deref().and_then(|p| read_source_line(p, loc.line));
221 EnrichedCompileError {
222 error: self,
223 file_path,
224 source_line,
225 }
226 }
227}
228
229fn read_source_line(path: &Path, line: u32) -> Option<String> {
231 use std::io::{BufRead, BufReader};
232 if line == 0 {
233 return None;
234 }
235 let f = std::fs::File::open(path).ok()?;
236 let reader = BufReader::new(f);
237 let mut iter = reader.lines();
238 let target = (line as usize).saturating_sub(1);
239 iter.nth(target).and_then(|r| r.ok())
240}
241
242#[derive(Debug)]
248pub struct EnrichedCompileError {
249 pub error: CompileError,
250 pub file_path: Option<PathBuf>,
251 pub source_line: Option<String>,
252}
253
254impl EnrichedCompileError {
255 pub fn inner(&self) -> &CompileError {
257 &self.error
258 }
259
260 pub fn loc(&self) -> &SourceLocation {
262 self.error.loc()
263 }
264}
265
266impl fmt::Display for EnrichedCompileError {
267 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
268 let loc = self.error.loc();
269 let (kind_label, kind_msg) = match &self.error {
270 CompileError::Lex { kind, .. } => ("lexer error", format!("{}", kind)),
271 CompileError::Preprocess { kind, .. } => ("preprocessor error", format!("{}", kind)),
272 CompileError::Parse { kind, .. } => ("parse error", format!("{}", kind)),
273 };
274 match &self.file_path {
275 Some(p) => write!(
276 f,
277 "{}:{}:{}: {}: {}",
278 p.display(), loc.line, loc.column, kind_label, kind_msg
279 )?,
280 None => write!(
281 f,
282 "<file_id={}>:{}:{}: {}: {}",
283 loc.file_id.as_u32(), loc.line, loc.column, kind_label, kind_msg
284 )?,
285 }
286 if let Some(line_text) = &self.source_line {
287 write!(f, "\n {:>4} | {}", loc.line, line_text)?;
288 let col = (loc.column as usize).saturating_sub(1);
289 write!(f, "\n | {}^", " ".repeat(col))?;
290 }
291 Ok(())
292 }
293}
294
295impl std::error::Error for EnrichedCompileError {
296 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
297 Some(&self.error)
298 }
299}
300
301pub type Result<T> = std::result::Result<T, CompileError>;
303
304#[cfg(test)]
305mod tests {
306 use super::*;
307 use crate::source::FileId;
308
309 #[test]
310 fn test_lex_error_display() {
311 let err = LexError::UnterminatedComment;
312 assert_eq!(format!("{}", err), "unterminated block comment");
313 }
314
315 #[test]
316 fn test_compile_error_display() {
317 let loc = SourceLocation::new(FileId::default(), 10, 5);
318 let err = CompileError::Lex {
319 loc,
320 kind: LexError::InvalidChar('$'),
321 };
322 assert!(format!("{}", err).contains("invalid character"));
323 }
324}