1use std::fmt;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7pub struct Location {
8 pub line: usize,
10
11 pub column: usize,
13}
14
15impl Location {
16 pub fn new(line: usize, column: usize) -> Self {
18 Self { line, column }
19 }
20
21 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#[derive(Debug, Clone, PartialEq)]
44#[allow(dead_code)]
45pub struct ParseError {
46 pub location: Location,
48
49 pub message: String,
51
52 pub errors: Vec<ParseError>,
55}
56
57impl ParseError {
58 pub fn new(location: Location, message: String) -> Self {
60 Self {
61 location,
62 message,
63 errors: Vec::new(),
64 }
65 }
66
67 #[allow(dead_code)]
72 pub fn with_error(mut self, error: ParseError) -> Self {
73 self.errors.push(error);
74 self
75 }
76
77 #[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 #[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#[derive(Debug, Clone, PartialEq)]
113pub enum SerializeError {
114 InvalidStructure { reason: String },
116
117 InvalidValue { value_type: String, reason: String },
119
120 InvalidIdentifier { identifier: String, reason: String },
122
123 ValidationFailed { gram: String, reason: String },
125
126 IoError { message: String },
128}
129
130impl SerializeError {
131 pub fn invalid_structure(reason: impl Into<String>) -> Self {
133 Self::InvalidStructure {
134 reason: reason.into(),
135 }
136 }
137
138 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 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 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); 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}