use crate::{
lang::{CallStack, Signal},
object::Expr,
parser::*,
};
use core::fmt;
use pest::error::LineColLocation::Pos;
#[macro_export]
macro_rules! internal_err {
() => {
$crate::error::Error::Internal(None, std::file!(), std::line!()).into()
};
( $x:expr ) => {
$crate::error::Error::Internal(Some($x.to_string()), std::file!(), std::line!()).into()
};
}
#[derive(Debug, Clone, PartialEq)]
pub enum Error {
VariableNotFound(String),
IncorrectContext(String),
NotInterpretableAsLogical,
ConditionIsNotScalar,
CannotBeCoercedToCharacter,
CannotBeCoercedToDouble,
CannotBeCoercedToInteger,
CannotBeCoercedToLogical,
CannotBeCoercedTo(&'static str),
InvalidRange,
NonRecyclableLengths(usize, usize),
CannotBeDestructuredIntoList,
InvalidFunctionParameter(Expr),
DuplicatedParameter(String),
DuplicatedMoreParameter(),
Missing,
ArgumentMissing(String),
ArgumentInvalid(String),
ParseFailureVerbose(pest::error::Error<en::Rule>),
ParseFailure(pest::error::Error<en::Rule>),
ParseUnexpected(en::Rule, (usize, usize)),
WithCallStack(Box<Error>, CallStack),
Unimplemented(Option<String>),
Internal(Option<String>, &'static str, u32),
CannotEvaluateAsMutable(Expr),
FeatureDisabledRestArgs,
Other(String),
}
impl Error {
fn as_str(&self) -> String {
match self {
Error::IncorrectContext(x) => format!("'{}' used in an incorrect context", x),
Error::VariableNotFound(v) => format!("object '{}' not found", v.as_str()),
Error::ParseFailureVerbose(e) => format!("{}", e),
Error::ParseFailure(e) => match e.line_col {
Pos((line, col)) => format!("Parse failed at Line {}, Column {}", line, col),
_ => format!("Parse failed at {:?}", e.line_col),
},
Error::ParseUnexpected(rule, _span) => {
format!("Parse failed. Found unexpected parsing rule '{:#?}'", rule)
}
Error::NotInterpretableAsLogical => {
"argument is not interpretable as logical".to_string()
}
Error::NonRecyclableLengths(l, r) => {
format!("Vector lengths {l} and {r} cannot be recycled.")
}
Error::ConditionIsNotScalar => "the condition has length > 1".to_string(),
Error::CannotBeCoercedToCharacter => {
"object cannot be coerced to type 'character'".to_string()
}
Error::CannotBeCoercedToLogical => {
"object cannot be coerced to type 'logical'".to_string()
}
Error::CannotBeCoercedToInteger => {
"object cannot be coerced to type 'integer'".to_string()
}
Error::CannotBeCoercedToDouble => {
"object cannot be coerced to type 'double'".to_string()
}
Error::CannotBeCoercedTo(to) => {
format!("object cannot be coerced to type '{to}'")
}
Error::InvalidRange => {
"Ranges without a `by` should have an end >= start. Provide a negative `by` for the range using `start:by:end`".to_string()
}
Error::Other(s) => s.to_string(),
Error::WithCallStack(e, c) => format!("{}\n{c}", e.as_str()),
Error::ArgumentMissing(s) => format!("argument '{s}' is missing with no default"),
Error::ArgumentInvalid(s) => format!("argument '{s}' is invalid"),
Error::Unimplemented(Some(s)) => {
format!("Uh, oh! Looks like '{s}' is only partially implemented")
}
Error::CannotEvaluateAsMutable(expr) => {
format!("Expression {expr} cannot be evaluated mutably")
}
Error::Unimplemented(_) => {
"Uh, oh! You tried to do something that is only partially implemented".to_string()
}
Error::Internal(None, file, line) => format!("Internal Error ({file}:{line})"),
Error::Internal(Some(msg), file, line) => {
format!("Internal Error ({file}:{line})\n{msg}")
}
Error::FeatureDisabledRestArgs => {
"..rest syntax currently disabled. To enable launch with\n\n --experiments rest-args\n".to_string()
}
Error::Missing => "object is missing".to_string(),
Error::InvalidFunctionParameter(expr) => format!("invalid function parameter: {}", expr),
Error::DuplicatedParameter(name) => format!("duplicated parameter name: {}", name),
Error::DuplicatedMoreParameter() => "duplicated '..<more>' parameters".to_string(),
Error::CannotBeDestructuredIntoList => "object cannot be coerced into a list for destructuring".to_string(),
}
}
pub fn from_parse_error<R>(input: &str, error: pest::error::Error<R>) -> Error
where
R: pest::RuleType + Into<en::Rule>,
{
use pest::error::Error as E;
use pest::error::ErrorVariant::*;
use pest::error::InputLocation;
use pest::{Position, Span};
let variant = match error.variant {
ParsingError { positives: p, negatives: n } => ParsingError {
positives: p.into_iter().map(|i| i.into()).collect(),
negatives: n.into_iter().map(|i| i.into()).collect(),
},
CustomError { message } => CustomError { message },
};
match error.location {
InputLocation::Pos(p) => {
let pos = Position::new(input, p).unwrap();
let err = E::new_from_pos(variant, pos);
Error::ParseFailure(err)
}
InputLocation::Span((s, e)) => {
let span = Span::new(input, s, e).unwrap();
let err = E::new_from_span(variant, span);
Error::ParseFailure(err)
}
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Error: {}", self.as_str())
}
}
impl From<Error> for Signal {
fn from(val: Error) -> Self {
Signal::Error(val)
}
}
impl<T> From<Error> for Result<T, Signal> {
fn from(val: Error) -> Self {
Err(Signal::Error(val))
}
}
impl From<&str> for Signal {
fn from(msg: &str) -> Self {
Signal::Error(Error::Other(msg.to_string()))
}
}
impl<T> From<Signal> for Result<T, Signal> {
fn from(value: Signal) -> Self {
Err(value)
}
}