1use thiserror::Error;
2
3use crate::lexer::{LexerError, Token};
4
5#[derive(Error, Debug, Clone, PartialEq, Eq)]
8pub enum LpParseError {
9 #[error("Invalid number format '{value}' at position {position}")]
11 InvalidNumber {
12 value: String,
14 position: usize,
16 },
17
18 #[error("Missing required section: {section}")]
20 MissingSection {
21 section: String,
23 },
24
25 #[error("Invalid bounds for variable '{variable}': {details}")]
27 InvalidBounds {
28 variable: String,
30 details: String,
32 },
33
34 #[error("Validation error: {message}")]
36 ValidationError {
37 message: String,
39 },
40
41 #[error("Parse error at position {position}: {message}")]
43 ParseError {
44 position: usize,
46 message: String,
48 },
49
50 #[error("File I/O error: {message}")]
52 IoError {
53 message: String,
55 },
56}
57
58impl LpParseError {
59 pub fn invalid_number(value: impl Into<String>, position: usize) -> Self {
61 Self::InvalidNumber { value: value.into(), position }
62 }
63
64 pub fn missing_section(section: impl Into<String>) -> Self {
66 Self::MissingSection { section: section.into() }
67 }
68
69 pub fn invalid_bounds(variable: impl Into<String>, details: impl Into<String>) -> Self {
71 Self::InvalidBounds { variable: variable.into(), details: details.into() }
72 }
73
74 pub fn validation_error(message: impl Into<String>) -> Self {
76 Self::ValidationError { message: message.into() }
77 }
78
79 pub fn parse_error(position: usize, message: impl Into<String>) -> Self {
81 Self::ParseError { position, message: message.into() }
82 }
83
84 pub fn io_error(message: impl Into<String>) -> Self {
86 Self::IoError { message: message.into() }
87 }
88}
89
90impl<'input> From<lalrpop_util::ParseError<usize, Token<'input>, LexerError>> for LpParseError {
92 fn from(err: lalrpop_util::ParseError<usize, Token<'input>, LexerError>) -> Self {
93 match err {
94 lalrpop_util::ParseError::InvalidToken { location } => Self::parse_error(location, "Invalid token"),
95 lalrpop_util::ParseError::UnrecognizedEof { location, expected } => {
96 let expected_str = if expected.is_empty() { String::new() } else { format!(", expected one of: {}", expected.join(", ")) };
97 Self::parse_error(location, format!("Unexpected end of input{expected_str}"))
98 }
99 lalrpop_util::ParseError::UnrecognizedToken { token: (start, tok, _), expected } => {
100 let expected_str = if expected.is_empty() { String::new() } else { format!(", expected one of: {}", expected.join(", ")) };
101 Self::parse_error(start, format!("Unexpected token {tok:?}{expected_str}"))
102 }
103 lalrpop_util::ParseError::ExtraToken { token: (start, tok, _) } => Self::parse_error(start, format!("Extra token {tok:?}")),
104 lalrpop_util::ParseError::User { error } => {
105 let message = error.message.clone().unwrap_or_else(|| "Lexer error".to_string());
106 Self::parse_error(error.position, message)
107 }
108 }
109 }
110}
111
112pub type LpResult<T> = Result<T, LpParseError>;
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn test_error_creation() {
121 let err = LpParseError::invalid_bounds("x", "lower exceeds upper");
122 assert_eq!(err.to_string(), "Invalid bounds for variable 'x': lower exceeds upper");
123 }
124}