expy 0.0.2

Embeddable & extensible expression evaluator
Documentation
//! Evaluation errors.

use std::num::TryFromIntError;
use std::ops::Range;

use itertools::Itertools;
use thiserror::Error;

use crate::model::*;


/// Evaluation error.
#[derive(Debug, Error, PartialEq)]
pub enum Error {
    /// Name resolution error.
    #[error("unknown identifier: {ident}")]
    Name {
        ident: Ident,
    },

    /// Type mismatch.
    #[error(
        "type mismatch: expected {expected}; got ({actual})",
        expected = expected.iter().format_with(" or ", |sig, f| {
            f(&format_args!("({})", sig.iter().format(", ")))
        }),
        actual = actual.iter().format(", "),
    )]
    Type {
        expected: Vec<Vec<Type>>,
        actual: Vec<Type>,
    },

    /// Argument error.
    #[error("invalid argument {actual:?}, expected {expected}")]
    Argument {
        expected: String,  // message or description
        actual: Value,
    },

    /// Bounds error.
    #[error("index out of bounds: {actual} is not in {expected:?} range")]
    Bounds {
        expected: Range<usize>,
        actual: usize,
    },

    /// Conversion error.
    #[error("conversion error: {0}")]
    Conversion(ConversionError),  // no #[from], since it's covered by the manual impl below

    /// Custom error, typically from a user-defined function.
    #[error("custom error: {0}")]
    Custom(String),
}

impl Error {
    /// Construct a name error.
    pub fn name(ident: impl Into<Ident>) -> Self {
        Self::Name { ident: ident.into() }
    }

    /// Construct an argument error.
    pub fn argument(expected: impl Into<String>, actual: impl Into<Value>) -> Self {
        Self::Argument { expected: expected.into(), actual: actual.into() }
    }

    /// Construct a bounds error.
    pub fn bounds(expected: Range<usize>, actual: usize) -> Self {
        Self::Bounds { expected, actual }
    }

    /// Construct a custom error.
    pub fn custom(msg: impl Into<String>) -> Self {
        Self::Custom(msg.into())
    }

    /// Construct a type error.
    pub fn type_<E, S, A>(expected: E, actual: A) -> Self
        where E: IntoIterator<Item=S>, S: IntoIterator<Item=Type>,
              A: IntoIterator<Item=Type>
    {
        Self::Type {
            expected: expected.into_iter().map(|sig| sig.into_iter().collect()).collect(),
            actual: actual.into_iter().collect(),
        }
    }
}

impl<E> From<E> for Error
    where ConversionError: From<E>
{
    fn from(err: E) -> Self {
        Self::Conversion(err.into())
    }
}


// Conversion error.
#[derive(Debug, Error, Eq, PartialEq)]
pub enum ConversionError {
    #[error("conversion from integer failed: {0}")]
    FromInt(#[from] TryFromIntError),
}