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    // --- Bindings-specific errors ---
141    /// Generic parse error in a `*.bindings.chipi` file
142    BindingsParse(String),
143    /// Unknown target kind (e.g. `target foo`)
144    UnknownTargetKind(String),
145    /// Unknown strategy keyword (`strategy frob`)
146    InvalidStrategy(String),
147    /// Required field missing in a bindings block
148    MissingBindingsField { block: String, field: String },
149    /// Endianness value not in {big, little}
150    InvalidEndianness(String),
151    /// Multiple `target` blocks present and CLI did not pick one
152    MultipleTargetsAmbiguous(Vec<String>),
153    /// Multiple decoders/dispatches/processors/architectures present and
154    /// CLI did not pick one with `--decoder`
155    MultipleDecodersAmbiguous(Vec<String>),
156    /// Decoder/dispatch/processor/architecture refers to a name that
157    /// isn't defined in any included spec
158    UnknownDecoderInBinding {
159        name: String,
160        suggestion: Option<String>,
161    },
162    /// Handler group references an instruction not present in the resolved
163    /// decoder
164    UnknownInstructionInGroup {
165        instruction: String,
166        suggestion: Option<String>,
167    },
168    /// IDA flow / Binja flow references an instruction not in the decoder
169    UnknownInstructionInFlow {
170        instruction: String,
171        suggestion: Option<String>,
172    },
173    /// `segment_registers` references a name not declared in `registers`
174    SegmentRegisterNotDeclared(String),
175    /// Bindings dispatch missing required `invalid_handler`
176    MissingInvalidHandler(String),
177    /// Bindings include cycle
178    BindingsCircularInclude(String),
179    /// `flat_*` strategy: a raw value matches multiple instructions that
180    /// resolve to different handlers
181    FlatDispatchAmbiguous {
182        raw: u64,
183        matches: Vec<(String, String)>,
184    },
185}
186
187/// An error with location and optional help text.
188#[derive(Debug, Clone)]
189pub struct Error {
190    pub kind: ErrorKind,
191    pub span: Span,
192    pub help: Option<String>,
193}
194
195impl Error {
196    /// Create a new error with a kind and span.
197    pub fn new(kind: ErrorKind, span: Span) -> Self {
198        Error {
199            kind,
200            span,
201            help: None,
202        }
203    }
204
205    /// Add a help message to the error.
206    pub fn with_help(mut self, help: impl Into<String>) -> Self {
207        self.help = Some(help.into());
208        self
209    }
210}
211
212impl fmt::Display for Error {
213    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214        let msg = match &self.kind {
215            ErrorKind::UnexpectedChar(c) => format!("unexpected character '{}'", c),
216            ErrorKind::UnexpectedToken(t) => format!("unexpected token '{}'", t),
217            ErrorKind::InvalidBitPattern(p) => format!("invalid bit pattern '{}'", p),
218            ErrorKind::ExpectedToken(t) => format!("expected {}", t),
219            ErrorKind::InvalidRange => "invalid bit range".to_string(),
220            ErrorKind::InvalidWidth(w) => format!("invalid decoder width: {}", w),
221            ErrorKind::MissingDecoderBlock => "missing decoder block".to_string(),
222            ErrorKind::DuplicateInstructionName(n) => {
223                format!("duplicate instruction name '{}'", n)
224            }
225            ErrorKind::DuplicateTypeAlias(n) => format!("duplicate type alias '{}'", n),
226            ErrorKind::BitCoverageGap {
227                instruction,
228                missing_bits,
229            } => {
230                format!(
231                    "instruction '{}' has uncovered bits: {:?}",
232                    instruction, missing_bits
233                )
234            }
235            ErrorKind::OverlappingBits { instruction, bit } => {
236                format!(
237                    "instruction '{}' has overlapping coverage at bit {}",
238                    instruction, bit
239                )
240            }
241            ErrorKind::UnresolvedType(t) => format!("unresolved type '{}'", t),
242            ErrorKind::PatternConflict { a, b } => {
243                format!(
244                    "instructions '{}' and '{}' have conflicting fixed bit patterns",
245                    a, b
246                )
247            }
248            ErrorKind::PatternLengthMismatch {
249                instruction,
250                expected,
251                got,
252            } => {
253                format!(
254                    "instruction '{}': fixed pattern length {} doesn't match range width {}",
255                    instruction, got, expected
256                )
257            }
258            ErrorKind::UnusedImport(path) => format!("unused import '{}'", path),
259            ErrorKind::InvalidFormatString(msg) => format!("invalid format string: {}", msg),
260            ErrorKind::InvalidGuard(msg) => format!("invalid guard condition: {}", msg),
261            ErrorKind::UndefinedFieldInFormat { instruction, field } => {
262                format!(
263                    "format string in '{}' references undefined field '{}'",
264                    instruction, field
265                )
266            }
267            ErrorKind::UndefinedFieldInGuard { instruction, field } => {
268                format!(
269                    "guard in '{}' references undefined field '{}'",
270                    instruction, field
271                )
272            }
273            ErrorKind::UndefinedMap(name) => format!("undefined map '{}'", name),
274            ErrorKind::MapArgCountMismatch { map, expected, got } => {
275                format!(
276                    "map '{}' expects {} arguments but got {}",
277                    map, expected, got
278                )
279            }
280            ErrorKind::DuplicateMapEntry { map } => {
281                format!("duplicate entry in map '{}'", map)
282            }
283            ErrorKind::DuplicateMapName(name) => format!("duplicate map name '{}'", name),
284            ErrorKind::UnguardedNonLastFormatLine { instruction } => {
285                format!(
286                    "non-last format line in '{}' must have a guard condition",
287                    instruction
288                )
289            }
290            ErrorKind::UnknownBuiltinFunction(name) => {
291                format!("unknown builtin function '{}'", name)
292            }
293            ErrorKind::CrossUnitBoundary {
294                instruction,
295                range_start,
296                range_end,
297                width,
298            } => {
299                format!(
300                    "instruction '{}': bit range [{}:{}] spans across unit boundary (width={})",
301                    instruction, range_start, range_end, width
302                )
303            }
304            ErrorKind::ExceedsMaxUnits {
305                instruction,
306                required,
307                max_units,
308            } => {
309                format!(
310                    "instruction '{}' requires {} units but decoder max_units is {}",
311                    instruction, required, max_units
312                )
313            }
314            ErrorKind::InconsistentFragmentNames {
315                subdecoder,
316                instruction,
317                expected,
318                got,
319            } => {
320                format!(
321                    "sub-decoder '{}': instruction '{}' has fragments {:?} but expected {:?}",
322                    subdecoder, instruction, got, expected
323                )
324            }
325            ErrorKind::SubDecoderFieldTooWide {
326                field,
327                field_width,
328                subdecoder,
329                subdecoder_width,
330            } => {
331                format!(
332                    "field '{}' is {} bits wide but sub-decoder '{}' is only {} bits",
333                    field, field_width, subdecoder, subdecoder_width
334                )
335            }
336            ErrorKind::UndefinedSubDecoder(name) => {
337                format!("undefined sub-decoder '{}'", name)
338            }
339            ErrorKind::UndefinedFragment {
340                subdecoder,
341                fragment,
342            } => {
343                format!(
344                    "sub-decoder '{}' has no fragment named '{}'",
345                    subdecoder, fragment
346                )
347            }
348            ErrorKind::CircularInclude(path) => {
349                format!("circular include detected: '{}'", path)
350            }
351            ErrorKind::IncludeNotFound(path) => {
352                format!("included file not found: '{}'", path)
353            }
354
355            // --- Bindings-specific ---
356            ErrorKind::BindingsParse(msg) => msg.clone(),
357            ErrorKind::UnknownTargetKind(name) => {
358                format!(
359                    "unknown target kind '{}': expected one of rust, cpp, ida, binja",
360                    name
361                )
362            }
363            ErrorKind::InvalidStrategy(name) => {
364                format!(
365                    "unknown dispatch strategy '{}': expected one of fn_ptr_lut, jump_table, flat_lut, flat_match",
366                    name
367                )
368            }
369            ErrorKind::MissingBindingsField { block, field } => {
370                format!("{} is missing required field '{}'", block, field)
371            }
372            ErrorKind::InvalidEndianness(value) => {
373                format!("invalid endianness '{}': expected 'big' or 'little'", value)
374            }
375            ErrorKind::MultipleTargetsAmbiguous(names) => {
376                let mut s = String::from("multiple targets found:");
377                for name in names {
378                    s.push_str("\n   ");
379                    s.push_str(name);
380                }
381                s.push_str("\npass one explicitly with --target");
382                s
383            }
384            ErrorKind::MultipleDecodersAmbiguous(names) => {
385                let mut s = String::from("multiple dispatch targets found:");
386                for name in names {
387                    s.push_str("\n   ");
388                    s.push_str(name);
389                }
390                s.push_str("\npass one explicitly with --decoder");
391                s
392            }
393            ErrorKind::UnknownDecoderInBinding { name, .. } => {
394                format!("unknown decoder '{}' in bindings block", name)
395            }
396            ErrorKind::UnknownInstructionInGroup { instruction, .. } => {
397                format!("unknown instruction '{}' in handler group", instruction)
398            }
399            ErrorKind::UnknownInstructionInFlow { instruction, .. } => {
400                format!("unknown instruction '{}' in flow block", instruction)
401            }
402            ErrorKind::SegmentRegisterNotDeclared(name) => {
403                format!(
404                    "segment_registers entry '{}' is not declared in registers",
405                    name
406                )
407            }
408            ErrorKind::MissingInvalidHandler(decoder) => {
409                format!(
410                    "dispatch '{}' requires an `invalid_handler` directive",
411                    decoder
412                )
413            }
414            ErrorKind::BindingsCircularInclude(path) => {
415                format!("circular bindings include detected: '{}'", path)
416            }
417            ErrorKind::FlatDispatchAmbiguous { raw, matches } => {
418                let mut s = format!(
419                    "flat dispatch cannot resolve raw opcode {:#010x}\n   matched instructions:",
420                    raw
421                );
422                for (instr, handler) in matches {
423                    s.push_str(&format!("\n     {} -> {}", instr, handler));
424                }
425                s.push_str(
426                    "\n   flat dispatch requires each raw value to resolve to exactly one handler.",
427                );
428                s
429            }
430        };
431
432        write!(f, "error: {}", msg)?;
433        write!(f, "\n --> {}:{}", self.span.file, self.span.line)?;
434
435        // Auto-emit `did you mean` from suggestion variants if the user
436        // didn't already attach a help message.
437        let auto_help = if self.help.is_none() {
438            match &self.kind {
439                ErrorKind::UnknownDecoderInBinding {
440                    suggestion: Some(s),
441                    ..
442                }
443                | ErrorKind::UnknownInstructionInGroup {
444                    suggestion: Some(s),
445                    ..
446                }
447                | ErrorKind::UnknownInstructionInFlow {
448                    suggestion: Some(s),
449                    ..
450                } => Some(format!("did you mean \"{}\"?", s)),
451                _ => None,
452            }
453        } else {
454            None
455        };
456
457        if let Some(help) = self.help.as_ref().or(auto_help.as_ref()) {
458            write!(f, "\n = help: {}", help)?;
459        }
460
461        Ok(())
462    }
463}
464
465impl std::error::Error for Error {}
466
467/// Multiple errors collected from parsing or validation.
468#[derive(Debug)]
469pub struct Errors(pub Vec<Error>);
470
471impl fmt::Display for Errors {
472    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
473        for (i, err) in self.0.iter().enumerate() {
474            if i > 0 {
475                writeln!(f)?;
476            }
477            write!(f, "{}", err)?;
478        }
479        Ok(())
480    }
481}
482
483impl std::error::Error for Errors {}