zelen/
error.rs

1//! Error types and reporting for MiniZinc parser
2
3use std::fmt;
4use crate::ast::Span;
5
6pub type Result<T> = std::result::Result<T, Error>;
7
8/// Parser and compiler errors
9#[derive(Debug, Clone, PartialEq)]
10pub struct Error {
11    pub kind: ErrorKind,
12    pub span: Span,
13    pub source: Option<String>,
14}
15
16#[derive(Debug, Clone, PartialEq)]
17#[non_exhaustive]
18pub enum ErrorKind {
19    // Lexer errors
20    UnexpectedChar(char),
21    UnterminatedString,
22    InvalidNumber(String),
23    
24    // Parser errors
25    UnexpectedToken {
26        expected: String,
27        found: String,
28    },
29    UnexpectedEof,
30    InvalidExpression(String),
31    InvalidTypeInst(String),
32    
33    // Semantic errors
34    UnsupportedFeature {
35        feature: String,
36        phase: String,
37        workaround: Option<String>,
38    },
39    TypeError {
40        expected: String,
41        found: String,
42    },
43    DuplicateDeclaration(String),
44    UndefinedVariable(String),
45    
46    // Array-related errors
47    ArraySizeMismatch {
48        declared: usize,
49        provided: usize,
50    },
51    Array2DSizeMismatch {
52        declared_rows: usize,
53        declared_cols: usize,
54        provided_rows: usize,
55        provided_cols: usize,
56    },
57    Array3DSizeMismatch {
58        declared_d1: usize,
59        declared_d2: usize,
60        declared_d3: usize,
61        provided_d1: usize,
62        provided_d2: usize,
63        provided_d3: usize,
64    },
65    Array2DValueCountMismatch {
66        expected: usize,
67        provided: usize,
68    },
69    Array3DValueCountMismatch {
70        expected: usize,
71        provided: usize,
72    },
73    Array2DInvalidContext,
74    Array3DInvalidContext,
75    Array2DValuesMustBeLiteral,
76    Array3DValuesMustBeLiteral,
77    
78    // General
79    Message(String),
80}
81
82impl Error {
83    pub fn new(kind: ErrorKind, span: Span) -> Self {
84        Self {
85            kind,
86            span,
87            source: None,
88        }
89    }
90    
91    pub fn with_source(mut self, source: String) -> Self {
92        self.source = Some(source);
93        self
94    }
95    
96    pub fn unexpected_token(expected: &str, found: &str, span: Span) -> Self {
97        Self::new(
98            ErrorKind::UnexpectedToken {
99                expected: expected.to_string(),
100                found: found.to_string(),
101            },
102            span,
103        )
104    }
105    
106    pub fn unexpected_eof(span: Span) -> Self {
107        Self::new(ErrorKind::UnexpectedEof, span)
108    }
109    
110    pub fn unsupported_feature(feature: &str, phase: &str, span: Span) -> Self {
111        Self::new(
112            ErrorKind::UnsupportedFeature {
113                feature: feature.to_string(),
114                phase: phase.to_string(),
115                workaround: None,
116            },
117            span,
118        )
119    }
120    
121    pub fn type_error(expected: &str, found: &str, span: Span) -> Self {
122        Self::new(
123            ErrorKind::TypeError {
124                expected: expected.to_string(),
125                found: found.to_string(),
126            },
127            span,
128        )
129    }
130    
131    pub fn array_size_mismatch(declared: usize, provided: usize, span: Span) -> Self {
132        Self::new(
133            ErrorKind::ArraySizeMismatch { declared, provided },
134            span,
135        )
136    }
137    
138    pub fn array2d_size_mismatch(declared_rows: usize, declared_cols: usize, 
139                                  provided_rows: usize, provided_cols: usize, span: Span) -> Self {
140        Self::new(
141            ErrorKind::Array2DSizeMismatch { 
142                declared_rows, declared_cols, provided_rows, provided_cols 
143            },
144            span,
145        )
146    }
147    
148    pub fn array3d_size_mismatch(declared_d1: usize, declared_d2: usize, declared_d3: usize,
149                                  provided_d1: usize, provided_d2: usize, provided_d3: usize, span: Span) -> Self {
150        Self::new(
151            ErrorKind::Array3DSizeMismatch {
152                declared_d1, declared_d2, declared_d3, provided_d1, provided_d2, provided_d3
153            },
154            span,
155        )
156    }
157    
158    pub fn array2d_value_count_mismatch(expected: usize, provided: usize, span: Span) -> Self {
159        Self::new(
160            ErrorKind::Array2DValueCountMismatch { expected, provided },
161            span,
162        )
163    }
164    
165    pub fn array3d_value_count_mismatch(expected: usize, provided: usize, span: Span) -> Self {
166        Self::new(
167            ErrorKind::Array3DValueCountMismatch { expected, provided },
168            span,
169        )
170    }
171    
172    pub fn array2d_invalid_context(span: Span) -> Self {
173        Self::new(ErrorKind::Array2DInvalidContext, span)
174    }
175    
176    pub fn array3d_invalid_context(span: Span) -> Self {
177        Self::new(ErrorKind::Array3DInvalidContext, span)
178    }
179    
180    pub fn array2d_values_must_be_literal(span: Span) -> Self {
181        Self::new(ErrorKind::Array2DValuesMustBeLiteral, span)
182    }
183    
184    pub fn array3d_values_must_be_literal(span: Span) -> Self {
185        Self::new(ErrorKind::Array3DValuesMustBeLiteral, span)
186    }
187    
188    pub fn message(msg: &str, span: Span) -> Self {
189        Self::new(ErrorKind::Message(msg.to_string()), span)
190    }
191    
192    pub fn with_workaround(mut self, workaround: &str) -> Self {
193        if let ErrorKind::UnsupportedFeature { workaround: w, .. } = &mut self.kind {
194            *w = Some(workaround.to_string());
195        }
196        self
197    }
198    
199    /// Get the line and column of the error in the source
200    pub fn location(&self) -> (usize, usize) {
201        if let Some(source) = &self.source {
202            let mut line = 1;
203            let mut col = 1;
204            let pos = if self.span.start >= source.len() {
205                // For EOF errors, point to the last character
206                source.len().saturating_sub(1)
207            } else {
208                self.span.start
209            };
210            
211            for (i, c) in source.chars().enumerate() {
212                if i >= pos {
213                    break;
214                }
215                if c == '\n' {
216                    line += 1;
217                    col = 1;
218                } else {
219                    col += 1;
220                }
221            }
222            (line, col)
223        } else {
224            (0, 0)
225        }
226    }
227    
228    /// Get the line of source code where the error occurred
229    pub fn source_line(&self) -> Option<(String, usize)> {
230        self.source.as_ref().map(|source| {
231            let lines: Vec<&str> = source.lines().collect();
232            let (line_num, col) = self.location();
233            let line = if line_num > 0 && line_num <= lines.len() {
234                lines[line_num - 1].to_string()
235            } else {
236                String::new()
237            };
238            
239            // For EOF errors at position beyond line length, point to end of line
240            let adjusted_col = if col > line.len() {
241                line.len()
242            } else {
243                col
244            };
245            
246            (line, adjusted_col)
247        })
248    }
249}
250
251impl fmt::Display for Error {
252    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
253        let (line, col) = self.location();
254        
255        write!(f, "Error")?;
256        if line > 0 {
257            write!(f, " at line {}, column {}", line, col)?;
258        }
259        write!(f, ": ")?;
260        
261        match &self.kind {
262            ErrorKind::UnexpectedChar(c) => {
263                write!(f, "Unexpected character '{}'", c)
264            }
265            ErrorKind::UnterminatedString => {
266                write!(f, "Unterminated string literal")
267            }
268            ErrorKind::InvalidNumber(s) => {
269                write!(f, "Invalid number: {}", s)
270            }
271            ErrorKind::UnexpectedToken { expected, found } => {
272                write!(f, "Expected {}, found {}", expected, found)
273            }
274            ErrorKind::UnexpectedEof => {
275                write!(f, "Unexpected end of file")
276            }
277            ErrorKind::InvalidExpression(msg) => {
278                write!(f, "Invalid expression: {}", msg)
279            }
280            ErrorKind::InvalidTypeInst(msg) => {
281                write!(f, "Invalid type-inst: {}", msg)
282            }
283            ErrorKind::UnsupportedFeature { feature, phase, workaround } => {
284                write!(f, "Unsupported feature: {}", feature)?;
285                write!(f, " (will be supported in {})", phase)?;
286                if let Some(w) = workaround {
287                    write!(f, "\nWorkaround: {}", w)?;
288                }
289                Ok(())
290            }
291            ErrorKind::TypeError { expected, found } => {
292                write!(f, "Type error: expected {}, found {}", expected, found)
293            }
294            ErrorKind::DuplicateDeclaration(name) => {
295                write!(f, "Duplicate declaration of '{}'", name)
296            }
297            ErrorKind::UndefinedVariable(name) => {
298                write!(f, "Undefined variable '{}'", name)
299            }
300            ErrorKind::ArraySizeMismatch { declared, provided } => {
301                write!(f, "Array size mismatch: declared {}, but got {}", declared, provided)
302            }
303            ErrorKind::Array2DSizeMismatch { declared_rows, declared_cols, provided_rows, provided_cols } => {
304                write!(f, "array2d size mismatch: declared {}x{}, but provided {}x{}", 
305                    declared_rows, declared_cols, provided_rows, provided_cols)
306            }
307            ErrorKind::Array3DSizeMismatch { declared_d1, declared_d2, declared_d3, provided_d1, provided_d2, provided_d3 } => {
308                write!(f, "array3d size mismatch: declared {}x{}x{}, but provided {}x{}x{}", 
309                    declared_d1, declared_d2, declared_d3, provided_d1, provided_d2, provided_d3)
310            }
311            ErrorKind::Array2DValueCountMismatch { expected, provided } => {
312                write!(f, "array2d value count mismatch: expected {}, got {}", expected, provided)
313            }
314            ErrorKind::Array3DValueCountMismatch { expected, provided } => {
315                write!(f, "array3d value count mismatch: expected {}, got {}", expected, provided)
316            }
317            ErrorKind::Array2DInvalidContext => {
318                write!(f, "array2d() can only be used for 2D arrays")
319            }
320            ErrorKind::Array3DInvalidContext => {
321                write!(f, "array3d() can only be used for 3D arrays")
322            }
323            ErrorKind::Array2DValuesMustBeLiteral => {
324                write!(f, "array2d() values must be an array literal [...]")
325            }
326            ErrorKind::Array3DValuesMustBeLiteral => {
327                write!(f, "array3d() values must be an array literal [...]")
328            }
329            ErrorKind::Message(msg) => {
330                write!(f, "{}", msg)
331            }
332        }?;
333        
334        if let Some((source_line, col)) = self.source_line() {
335            write!(f, "\n  {}", source_line)?;
336            if col > 0 {
337                write!(f, "\n  {}{}", " ".repeat(col - 1), "^")?;
338            } else if source_line.is_empty() && matches!(self.kind, ErrorKind::UnexpectedEof) {
339                // For EOF on empty line, show caret at start
340                write!(f, "\n  ^")?;
341            }
342        }
343        
344        Ok(())
345    }
346}
347
348impl std::error::Error for Error {}
349
350impl From<String> for Error {
351    fn from(msg: String) -> Self {
352        Self::new(ErrorKind::Message(msg), Span::dummy())
353    }
354}
355
356impl From<&str> for Error {
357    fn from(msg: &str) -> Self {
358        Self::new(ErrorKind::Message(msg.to_string()), Span::dummy())
359    }
360}