use camel_api::{ErrorPosition, ExpressionErrorClass};
use thiserror::Error;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum LanguageError {
#[error("parse error in expression `{expr}`: {reason}")]
ParseError { expr: String, reason: String },
#[error("evaluation error: {0}")]
EvalError(String),
#[error("unknown variable: {0}")]
UnknownVariable(String),
#[error("language `{0}` not found in registry")]
NotFound(String),
#[error("feature '{feature}' not supported by language '{language}'")]
NotSupported { feature: String, language: String },
#[error(
"evaluation failure: {class}{}{}",
position.map(|p| format!(" at {p}")).unwrap_or_default(),
detail.as_deref().map(|d| format!(": {d}")).unwrap_or_default()
)]
EvalFailure {
class: ExpressionErrorClass,
position: Option<ErrorPosition>,
detail: Option<String>,
},
#[error(
"type mismatch: expected {expected}, actual {actual}{}",
position.map(|p| format!(" at {p}")).unwrap_or_default()
)]
TypeMismatch {
expected: String,
actual: String,
position: Option<ErrorPosition>,
},
#[error("conversion error: cannot convert {source_type} to {target}")]
ConversionError { source_type: String, target: String },
}
impl LanguageError {
pub fn eval_error(expr: &str, message: impl std::fmt::Display) -> Self {
LanguageError::EvalError(format!("in expression `{expr}`: {message}"))
}
pub fn class(&self) -> Option<ExpressionErrorClass> {
match self {
Self::ParseError { .. } => Some(ExpressionErrorClass::Parse),
Self::EvalError(_) => Some(ExpressionErrorClass::Runtime),
Self::EvalFailure { class, .. } => Some(*class),
Self::TypeMismatch { .. } => Some(ExpressionErrorClass::TypeMismatch),
Self::ConversionError { .. } => Some(ExpressionErrorClass::Conversion),
Self::UnknownVariable(_) | Self::NotFound(_) | Self::NotSupported { .. } => None,
}
}
pub fn position(&self) -> Option<ErrorPosition> {
match self {
Self::EvalFailure { position, .. } | Self::TypeMismatch { position, .. } => *position,
_ => None,
}
}
}