gram_codec/parser/
error.rs1use super::types::Location;
4use thiserror::Error;
5
6#[derive(Debug, Error)]
8pub enum ParseError {
9 #[error("Syntax error at {location}: expected {expected}, found '{found}'")]
11 SyntaxError {
12 location: Location,
13 expected: String,
14 found: String,
15 context: Vec<String>,
16 },
17
18 #[error("Unexpected input at {location}: {snippet}")]
20 UnexpectedInput { location: Location, snippet: String },
21
22 #[error("Invalid {kind} at {location}: {reason}")]
24 InvalidValue {
25 location: Location,
26 kind: String,
27 reason: String,
28 },
29
30 #[error("Unmatched {delimiter} at {location}")]
32 UnmatchedDelimiter { location: Location, delimiter: char },
33
34 #[error("Internal parser error: {message}")]
36 Internal { message: String },
37}
38
39impl ParseError {
40 pub fn from_nom_error(input: &str, err: nom::Err<nom::error::VerboseError<&str>>) -> Self {
42 match err {
43 nom::Err::Error(e) | nom::Err::Failure(e) => {
44 let (error_input, kind) = e
45 .errors
46 .first()
47 .map(|(i, k)| (*i, k))
48 .unwrap_or((input, &nom::error::VerboseErrorKind::Context("unknown")));
49
50 let offset = input.len() - error_input.len();
51 let location = Location::from_offset(input, offset);
52
53 let found = error_input.chars().take(20).collect::<String>();
54 let expected = format!("{:?}", kind);
55
56 ParseError::SyntaxError {
57 location,
58 expected,
59 found,
60 context: Vec::new(),
61 }
62 }
63 nom::Err::Incomplete(_) => ParseError::Internal {
64 message: "Unexpected incomplete parse (streaming not supported)".to_string(),
65 },
66 }
67 }
68
69 pub fn location(&self) -> Option<Location> {
71 match self {
72 ParseError::SyntaxError { location, .. }
73 | ParseError::UnexpectedInput { location, .. }
74 | ParseError::InvalidValue { location, .. }
75 | ParseError::UnmatchedDelimiter { location, .. } => Some(*location),
76 ParseError::Internal { .. } => None,
77 }
78 }
79
80 pub fn with_context(mut self, context: String) -> Self {
82 if let ParseError::SyntaxError {
83 context: ref mut ctx,
84 ..
85 } = self
86 {
87 ctx.push(context);
88 }
89 self
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 #[test]
98 fn test_error_location() {
99 let err = ParseError::SyntaxError {
100 location: Location::new(2, 5, 10),
101 expected: "identifier".to_string(),
102 found: "123".to_string(),
103 context: vec![],
104 };
105
106 assert_eq!(err.location().unwrap().line, 2);
107 assert_eq!(err.location().unwrap().column, 5);
108 }
109
110 #[test]
111 fn test_error_with_context() {
112 let err = ParseError::SyntaxError {
113 location: Location::new(1, 1, 0),
114 expected: "value".to_string(),
115 found: "x".to_string(),
116 context: vec![],
117 };
118
119 let err = err.with_context("in record".to_string());
120
121 match err {
122 ParseError::SyntaxError { context, .. } => {
123 assert_eq!(context.len(), 1);
124 assert_eq!(context[0], "in record");
125 }
126 _ => panic!("Expected SyntaxError"),
127 }
128 }
129}