Skip to main content

lp_parser_rs/
error.rs

1use thiserror::Error;
2
3use crate::lexer::{LexerError, Token};
4
5/// This error type provides detailed context about parsing failures,
6/// including location information and specific error conditions.
7#[derive(Error, Debug, Clone, PartialEq, Eq)]
8pub enum LpParseError {
9    /// Invalid numerical value or format
10    #[error("Invalid number format '{value}' at position {position}")]
11    InvalidNumber {
12        /// The text that failed to parse as a number.
13        value: String,
14        /// Byte or line position of the value in the input.
15        position: usize,
16    },
17
18    /// Missing required section in LP file
19    #[error("Missing required section: {section}")]
20    MissingSection {
21        /// Name of the section that was expected but not found.
22        section: String,
23    },
24
25    /// Invalid bound specification
26    #[error("Invalid bounds for variable '{variable}': {details}")]
27    InvalidBounds {
28        /// Name of the variable with the invalid bound.
29        variable: String,
30        /// Description of what makes the bound invalid.
31        details: String,
32    },
33
34    /// Validation error for logical consistency
35    #[error("Validation error: {message}")]
36    ValidationError {
37        /// Description of the consistency violation.
38        message: String,
39    },
40
41    /// Generic parsing error with context
42    #[error("Parse error at position {position}: {message}")]
43    ParseError {
44        /// Byte or line position where parsing failed.
45        position: usize,
46        /// Description of the failure.
47        message: String,
48    },
49
50    /// File I/O related errors
51    #[error("File I/O error: {message}")]
52    IoError {
53        /// The underlying I/O error, rendered as text to keep the type `Clone`.
54        message: String,
55    },
56}
57
58impl LpParseError {
59    /// Create a new invalid number error
60    pub fn invalid_number(value: impl Into<String>, position: usize) -> Self {
61        Self::InvalidNumber { value: value.into(), position }
62    }
63
64    /// Create a new missing section error
65    pub fn missing_section(section: impl Into<String>) -> Self {
66        Self::MissingSection { section: section.into() }
67    }
68
69    /// Create a new invalid bounds error
70    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    /// Create a new validation error
75    pub fn validation_error(message: impl Into<String>) -> Self {
76        Self::ValidationError { message: message.into() }
77    }
78
79    /// Create a new parse error
80    pub fn parse_error(position: usize, message: impl Into<String>) -> Self {
81        Self::ParseError { position, message: message.into() }
82    }
83
84    /// Create a new I/O error
85    pub fn io_error(message: impl Into<String>) -> Self {
86        Self::IoError { message: message.into() }
87    }
88}
89
90/// Convert from LALRPOP parsing errors to our custom error type.
91impl<'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
112/// Result type alias for LP parsing operations
113pub 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}