use derive_more::Display;
use core::fmt;
use crate::{
alloc::{format, vec, String, ToOwned, Vec},
Value,
};
use arithmetic_parser::{create_span_ref, BinaryOp, LvalueLen, Op, Span, Spanned, UnaryOp};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TupleLenMismatchContext {
BinaryOp(BinaryOp),
Assignment,
}
impl fmt::Display for TupleLenMismatchContext {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BinaryOp(op) => write!(formatter, "{}", op),
Self::Assignment => formatter.write_str("assignment"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RepeatedAssignmentContext {
FnArgs,
Assignment,
}
impl fmt::Display for RepeatedAssignmentContext {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::FnArgs => "function args",
Self::Assignment => "assignment",
})
}
}
#[derive(Debug, Display)]
pub enum EvalError {
#[display(
fmt = "Mismatch between length of tuples in {}: LHS has {} element(s), whereas RHS has {}",
context,
lhs,
rhs
)]
TupleLenMismatch {
lhs: LvalueLen,
rhs: usize,
context: TupleLenMismatchContext,
},
#[display(
fmt = "Mismatch between the number of arguments in the function definition and its call: \
definition requires {} arg(s), call has {}",
def,
call
)]
ArgsLenMismatch {
def: LvalueLen,
call: usize,
},
#[display(fmt = "Cannot destructure a non-tuple variable")]
CannotDestructure,
#[display(fmt = "Repeated assignment to the same variable in {}", context)]
RepeatedAssignment {
context: RepeatedAssignmentContext,
},
#[display(fmt = "Variable `{}` is not defined", _0)]
Undefined(String),
#[display(fmt = "Value is not callable")]
CannotCall,
#[display(fmt = "Failed executing native function: {}", _0)]
NativeCall(String),
#[display(fmt = "Unexpected operand type for {}", op)]
UnexpectedOperand {
op: Op,
},
}
impl EvalError {
pub fn native(message: impl Into<String>) -> Self {
Self::NativeCall(message.into())
}
pub fn to_short_string(&self) -> String {
match self {
Self::TupleLenMismatch { context, .. } => {
format!("Mismatch between length of tuples in {}", context)
}
Self::ArgsLenMismatch { .. } => {
"Mismatch between the number of arguments in the function definition and its call"
.to_owned()
}
Self::CannotDestructure => "Cannot destructure a non-tuple variable".to_owned(),
Self::RepeatedAssignment { context } => {
format!("Repeated assignment to the same variable in {}", context)
}
Self::Undefined(name) => format!("Variable `{}` is not defined", name),
Self::CannotCall => "Value is not callable".to_owned(),
Self::NativeCall(message) => message.to_owned(),
Self::UnexpectedOperand { op } => format!("Unexpected operand type for {}", op),
}
}
pub fn main_span_info(&self) -> String {
match self {
Self::TupleLenMismatch { context, lhs, .. } => {
format!("LHS of {} with {} element(s)", context, lhs)
}
Self::ArgsLenMismatch { call, .. } => format!("Called with {} arg(s) here", call),
Self::CannotDestructure => "Failed destructuring".to_owned(),
Self::RepeatedAssignment { .. } => "Re-assigned variable".to_owned(),
Self::Undefined(_) => "Undefined variable occurrence".to_owned(),
Self::CannotCall | Self::NativeCall(_) => "Failed call".to_owned(),
Self::UnexpectedOperand { .. } => "Operand of wrong type".to_owned(),
}
}
pub fn help(&self) -> Option<String> {
Some(match self {
Self::TupleLenMismatch { context, .. } => format!(
"If both args of {} are tuples, the number of elements in them must agree",
context
),
Self::CannotDestructure => {
"Only tuples can be destructured; numbers, functions and booleans cannot".to_owned()
}
Self::RepeatedAssignment { context } => format!(
"In {}, all assigned variables must have different names",
context
),
Self::CannotCall => "Only functions are callable, i.e., can be used as `fn_name` \
in `fn_name(...)` expressions"
.to_owned(),
Self::UnexpectedOperand { op: Op::Binary(op) } if op.is_arithmetic() => {
"Operands of binary arithmetic ops must be numbers or tuples containing numbers"
.to_owned()
}
Self::UnexpectedOperand { op: Op::Binary(op) } if op.is_comparison() => {
"Operands of comparison ops must be numbers or tuples containing numbers".to_owned()
}
Self::UnexpectedOperand { op: Op::Binary(_) } => {
"Operands of binary boolean ops must be boolean".to_owned()
}
Self::UnexpectedOperand {
op: Op::Unary(UnaryOp::Neg),
} => "Operand of negation must be a number or a tuple".to_owned(),
Self::UnexpectedOperand {
op: Op::Unary(UnaryOp::Not),
} => "Operand of boolean negation must be boolean".to_owned(),
_ => return None,
})
}
}
#[cfg(feature = "std")]
impl std::error::Error for EvalError {}
#[derive(Debug)]
pub enum AuxErrorInfo {
FnArgs,
PrevAssignment,
Rvalue,
UnbalancedRhs(usize),
InvalidArg,
}
impl fmt::Display for AuxErrorInfo {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::FnArgs => formatter.write_str("Function arguments declared here"),
Self::PrevAssignment => formatter.write_str("Previous declaration"),
Self::Rvalue => formatter.write_str("RHS containing the invalid assignment"),
Self::UnbalancedRhs(size) => write!(formatter, "RHS with the {}-element tuple", size),
Self::InvalidArg => formatter.write_str("Invalid argument"),
}
}
}
#[derive(Debug)]
pub struct SpannedEvalError<'a> {
error: EvalError,
main_span: Span<'a>,
aux_spans: Vec<Spanned<'a, AuxErrorInfo>>,
}
impl<'a> SpannedEvalError<'a> {
pub(super) fn new<T>(main_span: &Spanned<'a, T>, error: EvalError) -> Self {
Self {
error,
main_span: create_span_ref(main_span, ()),
aux_spans: vec![],
}
}
pub(super) fn with_span<T>(mut self, span: &Spanned<'a, T>, info: AuxErrorInfo) -> Self {
self.aux_spans.push(create_span_ref(span, info));
self
}
}
pub type EvalResult<'a, T> = Result<Value<'a, T>, SpannedEvalError<'a>>;
#[derive(Debug, Default)]
pub struct Backtrace<'a> {
calls: Vec<BacktraceElement<'a>>,
}
#[derive(Debug, Clone, Copy)]
pub struct BacktraceElement<'a> {
pub fn_name: &'a str,
pub def_span: Option<Span<'a>>,
pub call_span: Span<'a>,
}
impl<'a> Backtrace<'a> {
pub fn calls(&self) -> impl Iterator<Item = BacktraceElement<'a>> + '_ {
self.calls.iter().rev().cloned()
}
pub(super) fn push_call(
&mut self,
fn_name: &'a str,
def_span: Option<Span<'a>>,
call_span: Span<'a>,
) {
self.calls.push(BacktraceElement {
fn_name,
def_span,
call_span,
});
}
pub(super) fn pop_call(&mut self) {
self.calls.pop();
}
}
#[derive(Debug)]
pub struct ErrorWithBacktrace<'a> {
inner: SpannedEvalError<'a>,
backtrace: Backtrace<'a>,
}
impl<'a> ErrorWithBacktrace<'a> {
pub(super) fn new(inner: SpannedEvalError<'a>, backtrace: Backtrace<'a>) -> Self {
Self { inner, backtrace }
}
pub(super) fn with_empty_trace(inner: SpannedEvalError<'a>) -> Self {
Self {
inner,
backtrace: Backtrace::default(),
}
}
pub fn source(&self) -> &EvalError {
&self.inner.error
}
pub fn main_span(&self) -> Span<'a> {
self.inner.main_span
}
pub fn aux_spans(&self) -> &[Spanned<'a, AuxErrorInfo>] {
&self.inner.aux_spans
}
pub fn backtrace(&self) -> &Backtrace<'a> {
&self.backtrace
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::alloc::ToString;
#[test]
fn display_for_error() {
let err = EvalError::Undefined("test".to_owned());
assert_eq!(err.to_string(), "Variable `test` is not defined");
let err = EvalError::ArgsLenMismatch {
def: LvalueLen::AtLeast(2),
call: 1,
};
assert!(err
.to_string()
.ends_with("definition requires at least 2 arg(s), call has 1"));
}
}