Skip to main content

libperl_macrogen/
error.rs

1use crate::source::{FileRegistry, SourceLocation};
2use crate::token::TokenKind;
3use std::fmt;
4use std::path::{Path, PathBuf};
5
6/// エラー表示用のロケーション(ファイル名解決付き)
7pub 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/// レキサーエラー
20#[derive(Debug)]
21pub enum LexError {
22    /// 閉じられていないブロックコメント
23    UnterminatedComment,
24    /// 閉じられていない文字列リテラル
25    UnterminatedString,
26    /// 閉じられていない文字リテラル
27    UnterminatedChar,
28    /// 空の文字リテラル
29    EmptyCharLit,
30    /// 不正な文字
31    InvalidChar(char),
32    /// 不正なエスケープシーケンス
33    InvalidEscape(char),
34    /// 不正な数値リテラル
35    InvalidNumber(String),
36    /// 不正なサフィックス
37    InvalidSuffix(String),
38    /// 未知の識別子(ReadOnly モードで intern 済みでない識別子を検出)
39    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/// プリプロセッサエラー
59#[derive(Debug)]
60pub enum PPError {
61    /// 不正なディレクティブ
62    InvalidDirective(String),
63    /// マクロの再定義
64    MacroRedefinition(String),
65    /// インクルードファイルが見つからない
66    IncludeNotFound(PathBuf),
67    /// 対応する#ifがない#endif
68    UnmatchedEndif,
69    /// 対応する#endifがない
70    MissingEndif,
71    /// 不正なマクロ引数
72    InvalidMacroArgs(String),
73    /// ファイル読み込みエラー
74    IoError(PathBuf, String),
75    /// #if の条件式エラー
76    InvalidCondition(String),
77    /// 不正な#演算子の使用
78    InvalidStringize,
79    /// 不正な##演算子の使用
80    InvalidTokenPaste,
81    /// 再帰的マクロ展開の検出
82    RecursiveMacro(String),
83    /// 対応する#elseがない
84    UnmatchedElse,
85    /// #elifが#elseの後に出現
86    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/// パースエラー(Phase 3 で拡張)
110#[derive(Debug)]
111pub enum ParseError {
112    /// 予期しないトークン
113    UnexpectedToken { expected: String, found: TokenKind },
114    /// 予期しないファイル終端
115    UnexpectedEof,
116    /// 宣言エラー
117    InvalidDeclaration(String),
118    /// 型エラー
119    InvalidType(String),
120    /// assert マクロの引数数が不正
121    InvalidAssertArgs { macro_name: String, arg_count: usize },
122    /// assert マクロがオブジェクトマクロとして呼ばれた
123    AssertNotFunctionMacro { macro_name: String },
124    /// 入れ子の assert はサポートされない
125    NestedAssertNotSupported,
126    /// MacroEnd が見つからない
127    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/// 統合エラー型
156#[derive(Debug)]
157pub enum CompileError {
158    /// レキサーエラー
159    Lex { loc: SourceLocation, kind: LexError },
160    /// プリプロセッサエラー
161    Preprocess { loc: SourceLocation, kind: PPError },
162    /// パースエラー
163    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    /// エラーが発生した位置を取得
186    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    /// ファイル名を解決してエラーメッセージをフォーマット
195    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    /// FileRegistry を使い、ファイルパスとソース該当行を付加した
213    /// `EnrichedCompileError` に変換する。
214    ///
215    /// 表示時に `file:line:col: kind: msg` に加えて該当行と caret を出すため、
216    /// `CompileError` を上位エラー型へ変換する境界で呼び出すことを想定。
217    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
229/// 該当行 1 行をファイルから読み出す。失敗時は None を返す。
230fn 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/// CompileError + ファイルパス・該当行で強化されたエラー。
243///
244/// `Display` 実装は `path:line:col: kind: msg` の後に該当行と caret を付加した
245/// gcc 風のフォーマットを出す。下流(build.rs 等)は `{}` で文字列化するだけで
246/// 該当行が見えるようになる。
247#[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    /// 内側の CompileError を取得
256    pub fn inner(&self) -> &CompileError {
257        &self.error
258    }
259
260    /// エラー発生位置
261    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
301/// Result型エイリアス
302pub 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}