1use std::io;
2
3use thiserror::Error;
4
5use crate::lexer::{LexerError, Token};
6
7#[derive(Error, Debug, Clone, PartialEq, Eq)]
10pub enum LpParseError {
11 #[error("Invalid constraint syntax at position {position}: {context}")]
13 ConstraintSyntax { position: usize, context: String },
14
15 #[error("Invalid objective syntax at position {position}: {context}")]
17 ObjectiveSyntax { position: usize, context: String },
18
19 #[error("Unknown variable type '{var_type}' for variable '{variable}'")]
21 UnknownVariableType { variable: String, var_type: String },
22
23 #[error("Undefined variable '{variable}' referenced in {context}")]
25 UndefinedVariable { variable: String, context: String },
26
27 #[error("Duplicate {component_type} '{name}' defined")]
29 DuplicateDefinition { component_type: String, name: String },
30
31 #[error("Invalid number format '{value}' at position {position}")]
33 InvalidNumber { value: String, position: usize },
34
35 #[error("Missing required section: {section}")]
37 MissingSection { section: String },
38
39 #[error("Invalid bounds for variable '{variable}': {details}")]
41 InvalidBounds { variable: String, details: String },
42
43 #[error("Invalid SOS constraint '{constraint}': {details}")]
45 InvalidSosConstraint { constraint: String, details: String },
46
47 #[error("Validation error: {message}")]
49 ValidationError { message: String },
50
51 #[error("Parse error at position {position}: {message}")]
53 ParseError { position: usize, message: String },
54
55 #[error("File I/O error: {message}")]
57 IoError { message: String },
58
59 #[error("Internal parser error: {message}")]
61 InternalError { message: String },
62}
63
64impl LpParseError {
65 pub fn constraint_syntax(position: usize, context: impl Into<String>) -> Self {
67 Self::ConstraintSyntax { position, context: context.into() }
68 }
69
70 pub fn objective_syntax(position: usize, context: impl Into<String>) -> Self {
72 Self::ObjectiveSyntax { position, context: context.into() }
73 }
74
75 pub fn unknown_variable_type(variable: impl Into<String>, var_type: impl Into<String>) -> Self {
77 Self::UnknownVariableType { variable: variable.into(), var_type: var_type.into() }
78 }
79
80 pub fn undefined_variable(variable: impl Into<String>, context: impl Into<String>) -> Self {
82 Self::UndefinedVariable { variable: variable.into(), context: context.into() }
83 }
84
85 pub fn duplicate_definition(component_type: impl Into<String>, name: impl Into<String>) -> Self {
87 Self::DuplicateDefinition { component_type: component_type.into(), name: name.into() }
88 }
89
90 pub fn invalid_number(value: impl Into<String>, position: usize) -> Self {
92 Self::InvalidNumber { value: value.into(), position }
93 }
94
95 pub fn missing_section(section: impl Into<String>) -> Self {
97 Self::MissingSection { section: section.into() }
98 }
99
100 pub fn invalid_bounds(variable: impl Into<String>, details: impl Into<String>) -> Self {
102 Self::InvalidBounds { variable: variable.into(), details: details.into() }
103 }
104
105 pub fn invalid_sos_constraint(constraint: impl Into<String>, details: impl Into<String>) -> Self {
107 Self::InvalidSosConstraint { constraint: constraint.into(), details: details.into() }
108 }
109
110 pub fn validation_error(message: impl Into<String>) -> Self {
112 Self::ValidationError { message: message.into() }
113 }
114
115 pub fn parse_error(position: usize, message: impl Into<String>) -> Self {
117 Self::ParseError { position, message: message.into() }
118 }
119
120 pub fn io_error(message: impl Into<String>) -> Self {
122 Self::IoError { message: message.into() }
123 }
124
125 pub fn internal_error(message: impl Into<String>) -> Self {
127 Self::InternalError { message: message.into() }
128 }
129}
130
131impl<'input> From<lalrpop_util::ParseError<usize, Token<'input>, LexerError>> for LpParseError {
133 fn from(err: lalrpop_util::ParseError<usize, Token<'input>, LexerError>) -> Self {
134 match err {
135 lalrpop_util::ParseError::InvalidToken { location } => Self::parse_error(location, "Invalid token"),
136 lalrpop_util::ParseError::UnrecognizedEof { location, expected } => {
137 let expected_str = if expected.is_empty() { String::new() } else { format!(", expected one of: {}", expected.join(", ")) };
138 Self::parse_error(location, format!("Unexpected end of input{expected_str}"))
139 }
140 lalrpop_util::ParseError::UnrecognizedToken { token: (start, tok, _), expected } => {
141 let expected_str = if expected.is_empty() { String::new() } else { format!(", expected one of: {}", expected.join(", ")) };
142 Self::parse_error(start, format!("Unexpected token {tok:?}{expected_str}"))
143 }
144 lalrpop_util::ParseError::ExtraToken { token: (start, tok, _) } => Self::parse_error(start, format!("Extra token {tok:?}")),
145 lalrpop_util::ParseError::User { error } => Self::parse_error(0, format!("Lexer error: {error:?}")),
146 }
147 }
148}
149
150impl From<io::Error> for LpParseError {
152 fn from(err: io::Error) -> Self {
153 Self::io_error(err.to_string())
154 }
155}
156
157impl From<Box<dyn std::error::Error + 'static>> for LpParseError {
159 fn from(err: Box<dyn std::error::Error + 'static>) -> Self {
160 Self::io_error(err.to_string())
161 }
162}
163
164pub type LpResult<T> = Result<T, LpParseError>;
166
167pub trait ErrorContext<T> {
169 fn with_position(self, position: usize) -> LpResult<T>;
175
176 fn with_context(self, context: &str) -> LpResult<T>;
182}
183
184impl<T> ErrorContext<T> for Result<T, LpParseError> {
185 fn with_position(self, position: usize) -> Self {
186 self.map_err(|mut err| {
187 if let LpParseError::ParseError { position: pos, .. } = &mut err {
188 *pos = position;
189 }
190 err
191 })
192 }
193
194 fn with_context(self, context: &str) -> Self {
195 self.map_err(|err| match err {
196 LpParseError::ParseError { position, message } => LpParseError::parse_error(position, format!("{context}: {message}")),
197 other => other,
198 })
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205
206 #[test]
207 fn test_error_creation() {
208 let err = LpParseError::constraint_syntax(42, "missing operator");
209 assert_eq!(err.to_string(), "Invalid constraint syntax at position 42: missing operator");
210 }
211
212 #[test]
213 fn test_error_context() {
214 let result: LpResult<()> = Err(LpParseError::parse_error(10, "test error"));
215 let with_context = result.with_context("parsing constraint");
216
217 assert!(with_context.is_err());
218 assert!(with_context.unwrap_err().to_string().contains("parsing constraint"));
219 }
220
221 #[test]
222 fn test_io_error_conversion() {
223 let io_err = io::Error::new(io::ErrorKind::NotFound, "file not found");
224 let lp_err: LpParseError = io_err.into();
225
226 match lp_err {
227 LpParseError::IoError { message } => assert!(message.contains("file not found")),
228 _ => panic!("Expected IoError"),
229 }
230 }
231}