Skip to main content

chipi_core/
error.rs

1//! Error types and reporting for parsing and validation.
2
3use std::fmt;
4
5/// Source location information for error reporting.
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct Span {
8    pub file: String,
9    pub line: usize,
10    pub col: usize,
11    pub len: usize,
12}
13
14impl Span {
15    /// Create a new span with file and position information.
16    pub fn new(file: &str, line: usize, col: usize, len: usize) -> Self {
17        Span {
18            file: file.to_string(),
19            line,
20            col,
21            len,
22        }
23    }
24}
25
26/// Kinds of errors that can occur during parsing and validation.
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub enum ErrorKind {
29    // Lexer errors
30    /// Unexpected character in input
31    UnexpectedChar(char),
32
33    // Parser errors
34    /// Unexpected token
35    UnexpectedToken(String),
36    /// Invalid bit pattern syntax
37    InvalidBitPattern(String),
38    /// Expected token missing
39    ExpectedToken(String),
40    /// Invalid bit range specification
41    InvalidRange,
42    /// Invalid decoder width value
43    InvalidWidth(u32),
44    /// Missing decoder block in definition
45    MissingDecoderBlock,
46
47    // Validation errors
48    /// Two instructions have the same name
49    DuplicateInstructionName(String),
50    /// Two type aliases have the same name
51    DuplicateTypeAlias(String),
52    /// Instruction doesn't specify bits for all positions
53    BitCoverageGap {
54        instruction: String,
55        missing_bits: Vec<u32>,
56    },
57    /// Instruction specifies overlapping bits
58    OverlappingBits { instruction: String, bit: u32 },
59    /// Field references an undefined type
60    UnresolvedType(String),
61    /// Two instructions have the same fixed bit pattern
62    PatternConflict { a: String, b: String },
63    /// Fixed bit pattern length doesn't match range width
64    PatternLengthMismatch {
65        instruction: String,
66        expected: u32,
67        got: u32,
68    },
69    /// An import statement is unused
70    UnusedImport(String),
71
72    // Format/map errors
73    /// Invalid format string syntax
74    InvalidFormatString(String),
75    /// Invalid guard condition syntax
76    InvalidGuard(String),
77    /// Format string references undefined field
78    UndefinedFieldInFormat { instruction: String, field: String },
79    /// Guard references undefined field
80    UndefinedFieldInGuard { instruction: String, field: String },
81    /// Map call references undefined map
82    UndefinedMap(String),
83    /// Map call has wrong number of arguments
84    MapArgCountMismatch {
85        map: String,
86        expected: usize,
87        got: usize,
88    },
89    /// Duplicate entry in a map
90    DuplicateMapEntry { map: String },
91    /// Duplicate map name
92    DuplicateMapName(String),
93    /// Non-last format line without a guard condition
94    UnguardedNonLastFormatLine { instruction: String },
95    /// Unknown builtin function name
96    UnknownBuiltinFunction(String),
97
98    // Variable-length instruction errors
99    /// A bit range spans across unit boundaries
100    CrossUnitBoundary {
101        instruction: String,
102        range_start: u32,
103        range_end: u32,
104        width: u32,
105    },
106    /// Instruction requires more units than max_units allows
107    ExceedsMaxUnits {
108        instruction: String,
109        required: u32,
110        max_units: u32,
111    },
112
113    // Sub-decoder errors
114    /// Fragment names differ between instructions in the same sub-decoder
115    InconsistentFragmentNames {
116        subdecoder: String,
117        instruction: String,
118        expected: Vec<String>,
119        got: Vec<String>,
120    },
121    /// Field bit-width exceeds sub-decoder's declared width
122    SubDecoderFieldTooWide {
123        field: String,
124        field_width: u32,
125        subdecoder: String,
126        subdecoder_width: u32,
127    },
128    /// Referenced sub-decoder doesn't exist
129    UndefinedSubDecoder(String),
130    /// Dotted access to a non-existent fragment name
131    UndefinedFragment {
132        subdecoder: String,
133        fragment: String,
134    },
135    /// Circular include detected
136    CircularInclude(String),
137    /// Included file not found
138    IncludeNotFound(String),
139}
140
141/// An error with location and optional help text.
142#[derive(Debug, Clone)]
143pub struct Error {
144    pub kind: ErrorKind,
145    pub span: Span,
146    pub help: Option<String>,
147}
148
149impl Error {
150    /// Create a new error with a kind and span.
151    pub fn new(kind: ErrorKind, span: Span) -> Self {
152        Error {
153            kind,
154            span,
155            help: None,
156        }
157    }
158
159    /// Add a help message to the error.
160    pub fn with_help(mut self, help: impl Into<String>) -> Self {
161        self.help = Some(help.into());
162        self
163    }
164}
165
166impl fmt::Display for Error {
167    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168        let msg = match &self.kind {
169            ErrorKind::UnexpectedChar(c) => format!("unexpected character '{}'", c),
170            ErrorKind::UnexpectedToken(t) => format!("unexpected token '{}'", t),
171            ErrorKind::InvalidBitPattern(p) => format!("invalid bit pattern '{}'", p),
172            ErrorKind::ExpectedToken(t) => format!("expected {}", t),
173            ErrorKind::InvalidRange => "invalid bit range".to_string(),
174            ErrorKind::InvalidWidth(w) => format!("invalid decoder width: {}", w),
175            ErrorKind::MissingDecoderBlock => "missing decoder block".to_string(),
176            ErrorKind::DuplicateInstructionName(n) => {
177                format!("duplicate instruction name '{}'", n)
178            }
179            ErrorKind::DuplicateTypeAlias(n) => format!("duplicate type alias '{}'", n),
180            ErrorKind::BitCoverageGap {
181                instruction,
182                missing_bits,
183            } => {
184                format!(
185                    "instruction '{}' has uncovered bits: {:?}",
186                    instruction, missing_bits
187                )
188            }
189            ErrorKind::OverlappingBits { instruction, bit } => {
190                format!(
191                    "instruction '{}' has overlapping coverage at bit {}",
192                    instruction, bit
193                )
194            }
195            ErrorKind::UnresolvedType(t) => format!("unresolved type '{}'", t),
196            ErrorKind::PatternConflict { a, b } => {
197                format!(
198                    "instructions '{}' and '{}' have conflicting fixed bit patterns",
199                    a, b
200                )
201            }
202            ErrorKind::PatternLengthMismatch {
203                instruction,
204                expected,
205                got,
206            } => {
207                format!(
208                    "instruction '{}': fixed pattern length {} doesn't match range width {}",
209                    instruction, got, expected
210                )
211            }
212            ErrorKind::UnusedImport(path) => format!("unused import '{}'", path),
213            ErrorKind::InvalidFormatString(msg) => format!("invalid format string: {}", msg),
214            ErrorKind::InvalidGuard(msg) => format!("invalid guard condition: {}", msg),
215            ErrorKind::UndefinedFieldInFormat { instruction, field } => {
216                format!(
217                    "format string in '{}' references undefined field '{}'",
218                    instruction, field
219                )
220            }
221            ErrorKind::UndefinedFieldInGuard { instruction, field } => {
222                format!(
223                    "guard in '{}' references undefined field '{}'",
224                    instruction, field
225                )
226            }
227            ErrorKind::UndefinedMap(name) => format!("undefined map '{}'", name),
228            ErrorKind::MapArgCountMismatch { map, expected, got } => {
229                format!(
230                    "map '{}' expects {} arguments but got {}",
231                    map, expected, got
232                )
233            }
234            ErrorKind::DuplicateMapEntry { map } => {
235                format!("duplicate entry in map '{}'", map)
236            }
237            ErrorKind::DuplicateMapName(name) => format!("duplicate map name '{}'", name),
238            ErrorKind::UnguardedNonLastFormatLine { instruction } => {
239                format!(
240                    "non-last format line in '{}' must have a guard condition",
241                    instruction
242                )
243            }
244            ErrorKind::UnknownBuiltinFunction(name) => {
245                format!("unknown builtin function '{}'", name)
246            }
247            ErrorKind::CrossUnitBoundary {
248                instruction,
249                range_start,
250                range_end,
251                width,
252            } => {
253                format!(
254                    "instruction '{}': bit range [{}:{}] spans across unit boundary (width={})",
255                    instruction, range_start, range_end, width
256                )
257            }
258            ErrorKind::ExceedsMaxUnits {
259                instruction,
260                required,
261                max_units,
262            } => {
263                format!(
264                    "instruction '{}' requires {} units but decoder max_units is {}",
265                    instruction, required, max_units
266                )
267            }
268            ErrorKind::InconsistentFragmentNames {
269                subdecoder,
270                instruction,
271                expected,
272                got,
273            } => {
274                format!(
275                    "sub-decoder '{}': instruction '{}' has fragments {:?} but expected {:?}",
276                    subdecoder, instruction, got, expected
277                )
278            }
279            ErrorKind::SubDecoderFieldTooWide {
280                field,
281                field_width,
282                subdecoder,
283                subdecoder_width,
284            } => {
285                format!(
286                    "field '{}' is {} bits wide but sub-decoder '{}' is only {} bits",
287                    field, field_width, subdecoder, subdecoder_width
288                )
289            }
290            ErrorKind::UndefinedSubDecoder(name) => {
291                format!("undefined sub-decoder '{}'", name)
292            }
293            ErrorKind::UndefinedFragment {
294                subdecoder,
295                fragment,
296            } => {
297                format!(
298                    "sub-decoder '{}' has no fragment named '{}'",
299                    subdecoder, fragment
300                )
301            }
302            ErrorKind::CircularInclude(path) => {
303                format!("circular include detected: '{}'", path)
304            }
305            ErrorKind::IncludeNotFound(path) => {
306                format!("included file not found: '{}'", path)
307            }
308        };
309
310        write!(f, "error: {}", msg)?;
311        write!(f, "\n --> {}:{}", self.span.file, self.span.line)?;
312
313        if let Some(help) = &self.help {
314            write!(f, "\n = help: {}", help)?;
315        }
316
317        Ok(())
318    }
319}
320
321impl std::error::Error for Error {}
322
323/// Multiple errors collected from parsing or validation.
324#[derive(Debug)]
325pub struct Errors(pub Vec<Error>);
326
327impl fmt::Display for Errors {
328    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
329        for (i, err) in self.0.iter().enumerate() {
330            if i > 0 {
331                writeln!(f)?;
332            }
333            write!(f, "{}", err)?;
334        }
335        Ok(())
336    }
337}
338
339impl std::error::Error for Errors {}