use std::io;
use thiserror::Error;
use crate::lexer::{LexerError, Token};
#[derive(Error, Debug, Clone, PartialEq, Eq)]
pub enum LpParseError {
#[error("Invalid constraint syntax at position {position}: {context}")]
ConstraintSyntax { position: usize, context: String },
#[error("Invalid objective syntax at position {position}: {context}")]
ObjectiveSyntax { position: usize, context: String },
#[error("Unknown variable type '{var_type}' for variable '{variable}'")]
UnknownVariableType { variable: String, var_type: String },
#[error("Undefined variable '{variable}' referenced in {context}")]
UndefinedVariable { variable: String, context: String },
#[error("Duplicate {component_type} '{name}' defined")]
DuplicateDefinition { component_type: String, name: String },
#[error("Invalid number format '{value}' at position {position}")]
InvalidNumber { value: String, position: usize },
#[error("Missing required section: {section}")]
MissingSection { section: String },
#[error("Invalid bounds for variable '{variable}': {details}")]
InvalidBounds { variable: String, details: String },
#[error("Invalid SOS constraint '{constraint}': {details}")]
InvalidSosConstraint { constraint: String, details: String },
#[error("Validation error: {message}")]
ValidationError { message: String },
#[error("Parse error at position {position}: {message}")]
ParseError { position: usize, message: String },
#[error("File I/O error: {message}")]
IoError { message: String },
#[error("Internal parser error: {message}")]
InternalError { message: String },
}
impl LpParseError {
pub fn constraint_syntax(position: usize, context: impl Into<String>) -> Self {
Self::ConstraintSyntax { position, context: context.into() }
}
pub fn objective_syntax(position: usize, context: impl Into<String>) -> Self {
Self::ObjectiveSyntax { position, context: context.into() }
}
pub fn unknown_variable_type(variable: impl Into<String>, var_type: impl Into<String>) -> Self {
Self::UnknownVariableType { variable: variable.into(), var_type: var_type.into() }
}
pub fn undefined_variable(variable: impl Into<String>, context: impl Into<String>) -> Self {
Self::UndefinedVariable { variable: variable.into(), context: context.into() }
}
pub fn duplicate_definition(component_type: impl Into<String>, name: impl Into<String>) -> Self {
Self::DuplicateDefinition { component_type: component_type.into(), name: name.into() }
}
pub fn invalid_number(value: impl Into<String>, position: usize) -> Self {
Self::InvalidNumber { value: value.into(), position }
}
pub fn missing_section(section: impl Into<String>) -> Self {
Self::MissingSection { section: section.into() }
}
pub fn invalid_bounds(variable: impl Into<String>, details: impl Into<String>) -> Self {
Self::InvalidBounds { variable: variable.into(), details: details.into() }
}
pub fn invalid_sos_constraint(constraint: impl Into<String>, details: impl Into<String>) -> Self {
Self::InvalidSosConstraint { constraint: constraint.into(), details: details.into() }
}
pub fn validation_error(message: impl Into<String>) -> Self {
Self::ValidationError { message: message.into() }
}
pub fn parse_error(position: usize, message: impl Into<String>) -> Self {
Self::ParseError { position, message: message.into() }
}
pub fn io_error(message: impl Into<String>) -> Self {
Self::IoError { message: message.into() }
}
pub fn internal_error(message: impl Into<String>) -> Self {
Self::InternalError { message: message.into() }
}
}
impl<'input> From<lalrpop_util::ParseError<usize, Token<'input>, LexerError>> for LpParseError {
fn from(err: lalrpop_util::ParseError<usize, Token<'input>, LexerError>) -> Self {
match err {
lalrpop_util::ParseError::InvalidToken { location } => Self::parse_error(location, "Invalid token"),
lalrpop_util::ParseError::UnrecognizedEof { location, expected } => {
let expected_str = if expected.is_empty() { String::new() } else { format!(", expected one of: {}", expected.join(", ")) };
Self::parse_error(location, format!("Unexpected end of input{expected_str}"))
}
lalrpop_util::ParseError::UnrecognizedToken { token: (start, tok, _), expected } => {
let expected_str = if expected.is_empty() { String::new() } else { format!(", expected one of: {}", expected.join(", ")) };
Self::parse_error(start, format!("Unexpected token {tok:?}{expected_str}"))
}
lalrpop_util::ParseError::ExtraToken { token: (start, tok, _) } => Self::parse_error(start, format!("Extra token {tok:?}")),
lalrpop_util::ParseError::User { error } => Self::parse_error(0, format!("Lexer error: {error:?}")),
}
}
}
impl From<io::Error> for LpParseError {
fn from(err: io::Error) -> Self {
Self::io_error(err.to_string())
}
}
impl From<Box<dyn std::error::Error + 'static>> for LpParseError {
fn from(err: Box<dyn std::error::Error + 'static>) -> Self {
Self::io_error(err.to_string())
}
}
pub type LpResult<T> = Result<T, LpParseError>;
pub trait ErrorContext<T> {
fn with_position(self, position: usize) -> LpResult<T>;
fn with_context(self, context: &str) -> LpResult<T>;
}
impl<T> ErrorContext<T> for Result<T, LpParseError> {
fn with_position(self, position: usize) -> Self {
self.map_err(|mut err| {
if let LpParseError::ParseError { position: pos, .. } = &mut err {
*pos = position;
}
err
})
}
fn with_context(self, context: &str) -> Self {
self.map_err(|err| match err {
LpParseError::ParseError { position, message } => LpParseError::parse_error(position, format!("{context}: {message}")),
other => other,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_creation() {
let err = LpParseError::constraint_syntax(42, "missing operator");
assert_eq!(err.to_string(), "Invalid constraint syntax at position 42: missing operator");
}
#[test]
fn test_error_context() {
let result: LpResult<()> = Err(LpParseError::parse_error(10, "test error"));
let with_context = result.with_context("parsing constraint");
assert!(with_context.is_err());
assert!(with_context.unwrap_err().to_string().contains("parsing constraint"));
}
#[test]
fn test_io_error_conversion() {
let io_err = io::Error::new(io::ErrorKind::NotFound, "file not found");
let lp_err: LpParseError = io_err.into();
match lp_err {
LpParseError::IoError { message } => assert!(message.contains("file not found")),
_ => panic!("Expected IoError"),
}
}
}