Skip to main content

gram_codec/
error.rs

1//! Error types for gram codec operations
2
3use std::fmt;
4
5/// Location in source code (1-indexed line and column)
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7pub struct Location {
8    /// Line number (1-indexed)
9    pub line: usize,
10
11    /// Column number (1-indexed)
12    pub column: usize,
13}
14
15impl Location {
16    /// Create a new location
17    pub fn new(line: usize, column: usize) -> Self {
18        Self { line, column }
19    }
20
21    // TODO: tree-sitter methods removed during nom parser migration
22    // Location tracking is now handled by parser::Location
23
24    /// Location at start of file
25    pub fn start() -> Self {
26        Self { line: 1, column: 1 }
27    }
28}
29
30impl fmt::Display for Location {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        write!(f, "{}:{}", self.line, self.column)
33    }
34}
35
36impl Default for Location {
37    fn default() -> Self {
38        Self::start()
39    }
40}
41
42/// Error during gram notation parsing
43#[derive(Debug, Clone, PartialEq)]
44#[allow(dead_code)]
45pub struct ParseError {
46    /// Location where error occurred
47    pub location: Location,
48
49    /// Primary error message
50    pub message: String,
51
52    /// Additional errors (for error recovery)
53    /// When error recovery is enabled, this contains all errors found
54    pub errors: Vec<ParseError>,
55}
56
57impl ParseError {
58    /// Create a new parse error at a specific location
59    pub fn new(location: Location, message: String) -> Self {
60        Self {
61            location,
62            message,
63            errors: Vec::new(),
64        }
65    }
66
67    // TODO: tree-sitter methods removed during nom parser migration
68    // Error construction is now handled by parser::ParseError
69
70    /// Add additional error (for error recovery)
71    #[allow(dead_code)]
72    pub fn with_error(mut self, error: ParseError) -> Self {
73        self.errors.push(error);
74        self
75    }
76
77    /// Create error for invalid pattern structure
78    #[allow(dead_code)]
79    pub fn invalid_structure(location: Location, details: String) -> Self {
80        Self::new(location, format!("Invalid pattern structure: {}", details))
81    }
82
83    /// Total number of errors (including nested)
84    #[allow(dead_code)]
85    pub fn error_count(&self) -> usize {
86        1 + self.errors.len()
87    }
88}
89
90impl fmt::Display for ParseError {
91    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92        write!(
93            f,
94            "Parse error at {}:{}: {}",
95            self.location.line, self.location.column, self.message
96        )?;
97
98        if !self.errors.is_empty() {
99            write!(f, "\nAdditional errors:")?;
100            for error in &self.errors {
101                write!(f, "\n  - {}", error)?;
102            }
103        }
104
105        Ok(())
106    }
107}
108
109impl std::error::Error for ParseError {}
110
111/// Error during pattern serialization to gram notation
112#[derive(Debug, Clone, PartialEq)]
113pub enum SerializeError {
114    /// Pattern structure cannot be represented in gram notation
115    InvalidStructure { reason: String },
116
117    /// Invalid value that cannot be serialized
118    InvalidValue { value_type: String, reason: String },
119
120    /// Invalid identifier (contains disallowed characters)
121    InvalidIdentifier { identifier: String, reason: String },
122
123    /// Serialized output failed validation
124    ValidationFailed { gram: String, reason: String },
125
126    /// I/O error during serialization (e.g., writing to file)
127    IoError { message: String },
128}
129
130impl SerializeError {
131    /// Create error for invalid pattern structure
132    pub fn invalid_structure(reason: impl Into<String>) -> Self {
133        Self::InvalidStructure {
134            reason: reason.into(),
135        }
136    }
137
138    /// Create error for invalid value
139    pub fn invalid_value(value_type: impl Into<String>, reason: impl Into<String>) -> Self {
140        Self::InvalidValue {
141            value_type: value_type.into(),
142            reason: reason.into(),
143        }
144    }
145
146    /// Create error for invalid identifier
147    pub fn invalid_identifier(identifier: impl Into<String>, reason: impl Into<String>) -> Self {
148        Self::InvalidIdentifier {
149            identifier: identifier.into(),
150            reason: reason.into(),
151        }
152    }
153
154    /// Create error for validation failure
155    pub fn validation_failed(gram: impl Into<String>, reason: impl Into<String>) -> Self {
156        Self::ValidationFailed {
157            gram: gram.into(),
158            reason: reason.into(),
159        }
160    }
161}
162
163impl fmt::Display for SerializeError {
164    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165        match self {
166            Self::InvalidStructure { reason } => {
167                write!(f, "Cannot serialize pattern structure: {}", reason)
168            }
169            Self::InvalidValue { value_type, reason } => {
170                write!(f, "Cannot serialize {} value: {}", value_type, reason)
171            }
172            Self::InvalidIdentifier { identifier, reason } => {
173                write!(f, "Invalid identifier '{}': {}", identifier, reason)
174            }
175            Self::ValidationFailed { gram, reason } => {
176                write!(
177                    f,
178                    "Serialized gram notation failed validation: {}\n{}",
179                    reason, gram
180                )
181            }
182            Self::IoError { message } => {
183                write!(f, "I/O error: {}", message)
184            }
185        }
186    }
187}
188
189impl std::error::Error for SerializeError {}
190
191impl From<std::io::Error> for SerializeError {
192    fn from(err: std::io::Error) -> Self {
193        Self::IoError {
194            message: err.to_string(),
195        }
196    }
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202
203    #[test]
204    fn test_location_creation() {
205        let loc = Location::new(10, 25);
206        assert_eq!(loc.line, 10);
207        assert_eq!(loc.column, 25);
208    }
209
210    #[test]
211    fn test_location_display() {
212        let loc = Location::new(1, 5);
213        assert_eq!(loc.to_string(), "1:5");
214    }
215
216    #[test]
217    fn test_location_start() {
218        let loc = Location::start();
219        assert_eq!(loc.line, 1);
220        assert_eq!(loc.column, 1);
221    }
222
223    #[test]
224    fn test_parse_error_creation() {
225        let error = ParseError::new(Location::new(1, 5), "Unexpected token".to_string());
226
227        assert_eq!(error.location.line, 1);
228        assert_eq!(error.location.column, 5);
229        assert_eq!(error.message, "Unexpected token");
230        assert_eq!(error.errors.len(), 0);
231    }
232
233    #[test]
234    fn test_parse_error_display() {
235        let error = ParseError::new(Location::new(1, 5), "Unexpected token".to_string());
236
237        assert_eq!(error.to_string(), "Parse error at 1:5: Unexpected token");
238    }
239
240    #[test]
241    fn test_parse_error_with_recovery() {
242        let mut primary = ParseError::new(Location::start(), "Multiple errors".to_string());
243        primary = primary.with_error(ParseError::new(Location::new(1, 1), "Error 1".to_string()));
244        primary = primary.with_error(ParseError::new(Location::new(2, 1), "Error 2".to_string()));
245
246        assert_eq!(primary.error_count(), 3); // 1 primary + 2 additional
247
248        let display = primary.to_string();
249        assert!(display.contains("Multiple errors"));
250        assert!(display.contains("Additional errors"));
251        assert!(display.contains("Error 1"));
252        assert!(display.contains("Error 2"));
253    }
254
255    #[test]
256    fn test_serialize_error_invalid_structure() {
257        let error = SerializeError::invalid_structure("Test reason");
258        let display = error.to_string();
259        assert!(display.contains("Test reason"));
260        assert!(display.contains("Cannot serialize pattern structure"));
261    }
262
263    #[test]
264    fn test_serialize_error_invalid_value() {
265        let error = SerializeError::invalid_value("CustomType", "Not supported");
266        let display = error.to_string();
267        assert!(display.contains("CustomType"));
268        assert!(display.contains("Not supported"));
269    }
270
271    #[test]
272    fn test_serialize_error_invalid_identifier() {
273        let error = SerializeError::invalid_identifier("hello world", "Contains whitespace");
274        let display = error.to_string();
275        assert!(display.contains("hello world"));
276        assert!(display.contains("Contains whitespace"));
277    }
278
279    #[test]
280    fn test_serialize_error_validation_failed() {
281        let error = SerializeError::validation_failed("(unclosed", "gram-lint failed");
282        let display = error.to_string();
283        assert!(display.contains("(unclosed"));
284        assert!(display.contains("gram-lint failed"));
285    }
286
287    #[test]
288    fn test_serialize_error_io_conversion() {
289        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "File not found");
290        let error: SerializeError = io_err.into();
291
292        match error {
293            SerializeError::IoError { message } => {
294                assert!(message.contains("File not found"));
295            }
296            _ => panic!("Expected IoError variant"),
297        }
298    }
299}