use std::fmt;
use thiserror::Error;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Error, Debug)]
pub enum Error {
#[error("Lexer error at line {line}, column {column}: {message}")]
LexerError {
line: usize,
column: usize,
message: String,
},
#[error("Parse error at line {line}: {message}")]
ParseError {
line: usize,
message: String,
},
#[error("Runtime error: {message}")]
RuntimeError {
message: String,
stack_trace: Option<Vec<String>>,
},
#[error("Variable '{name}' is not defined")]
UndefinedVariable {
name: String,
},
#[error("Function '{name}' is not defined")]
UndefinedFunction {
name: String,
},
#[error("Type error: expected {expected}, got {actual}")]
TypeError {
expected: String,
actual: String,
},
#[error("Argument error: expected {expected} arguments, got {actual}")]
ArgumentError {
expected: usize,
actual: usize,
},
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),
#[error("Network error: {0}")]
NetworkError(String),
#[error("AI service error: {0}")]
AiError(String),
#[error("JSON error: {0}")]
JsonError(#[from] serde_json::Error),
#[error("Internal error: {0}")]
InternalError(String),
}
impl Error {
pub fn lexer_error(line: usize, column: usize, message: impl Into<String>) -> Self {
Self::LexerError {
line,
column,
message: message.into(),
}
}
pub fn parse_error(line: usize, message: impl Into<String>) -> Self {
Self::ParseError {
line,
message: message.into(),
}
}
pub fn runtime_error(message: impl Into<String>) -> Self {
Self::RuntimeError {
message: message.into(),
stack_trace: None,
}
}
pub fn runtime_error_with_trace(
message: impl Into<String>,
stack_trace: Vec<String>,
) -> Self {
Self::RuntimeError {
message: message.into(),
stack_trace: Some(stack_trace),
}
}
pub fn undefined_variable(name: impl Into<String>) -> Self {
Self::UndefinedVariable { name: name.into() }
}
pub fn undefined_function(name: impl Into<String>) -> Self {
Self::UndefinedFunction { name: name.into() }
}
pub fn type_error(expected: impl Into<String>, actual: impl Into<String>) -> Self {
Self::TypeError {
expected: expected.into(),
actual: actual.into(),
}
}
pub fn argument_error(expected: usize, actual: usize) -> Self {
Self::ArgumentError { expected, actual }
}
pub fn network_error(message: impl Into<String>) -> Self {
Self::NetworkError(message.into())
}
pub fn ai_error(message: impl Into<String>) -> Self {
Self::AiError(message.into())
}
pub fn internal_error(message: impl Into<String>) -> Self {
Self::InternalError(message.into())
}
pub fn is_recoverable(&self) -> bool {
matches!(
self,
Error::UndefinedVariable { .. }
| Error::UndefinedFunction { .. }
| Error::TypeError { .. }
| Error::ArgumentError { .. }
)
}
pub fn severity(&self) -> ErrorSeverity {
match self {
Error::LexerError { .. } | Error::ParseError { .. } => ErrorSeverity::Fatal,
Error::RuntimeError { .. } => ErrorSeverity::Error,
Error::UndefinedVariable { .. }
| Error::UndefinedFunction { .. }
| Error::TypeError { .. }
| Error::ArgumentError { .. } => ErrorSeverity::Error,
Error::IoError(_) | Error::NetworkError(_) | Error::AiError(_) => ErrorSeverity::Warning,
Error::JsonError(_) | Error::InternalError(_) => ErrorSeverity::Fatal,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorSeverity {
Fatal,
Error,
Warning,
}
impl fmt::Display for ErrorSeverity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ErrorSeverity::Fatal => write!(f, "FATAL"),
ErrorSeverity::Error => write!(f, "ERROR"),
ErrorSeverity::Warning => write!(f, "WARNING"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_creation() {
let err = Error::lexer_error(1, 10, "Unexpected character");
assert!(matches!(err, Error::LexerError { line: 1, column: 10, .. }));
assert_eq!(err.severity(), ErrorSeverity::Fatal);
}
#[test]
fn test_error_recoverability() {
let recoverable = Error::undefined_variable("x");
assert!(recoverable.is_recoverable());
let fatal = Error::lexer_error(1, 1, "Invalid token");
assert!(!fatal.is_recoverable());
}
}