expy 0.0.2

Embeddable & extensible expression evaluator
Documentation
//! Abstract syntax tree for the expressions.

use std::borrow::{Borrow, Cow};

use derive_more::{Deref, Display, IsVariant, Unwrap};
use enum_as_inner::EnumAsInner;

use super::value::Value;


/// Expression that can be evaluated.
#[derive(Clone, Debug, IsVariant, PartialEq)]
pub enum Expr {
    /// Literal (constant) value.
    Literal(Literal),
    /// Reference to an identifier in the evaluation context.
    Ref(Ident),

    /// Vector expression.
    ///
    /// Currently, this is only used to represent 2D/3D/4D vectors.
    #[cfg(glam)]
    Vector(Vec<Expr>),

    /// Function call.
    Call(Box<Expr>, Vec<Expr>),
    /// Subscript (indexing) of a value (usually a compound one, like a vector).
    ///
    /// `Subscript(x, i)` represents `x[i]`.
    Subscript(Box<Expr>, Box<Expr>),
    /// Access to a member / inner part of a value.
    Access(Box<Expr>, Ident),

    /// Unary operator expression.
    Unary(UnaryOp, Box<Expr>),
    /// Binary operator expression.
    Binary(BinaryOp, Box<Expr>, Box<Expr>),
}

#[cfg(serde)]
impl<'de> serde::Deserialize<'de> for Expr {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where D: serde::Deserializer<'de>
    {
        <String as serde::Deserialize>::deserialize(deserializer)
            .and_then(|expr| crate::parse(expr).map_err(serde::de::Error::custom))
    }
}

impl Expr {
    /// Create a new literal expression.
    pub fn literal(lit: impl Into<Literal>) -> Self {
        Self::Literal(lit.into())
    }

    /// Create a new identifier reference expression.
    pub fn ref_(ident: impl Into<Ident>) -> Self {
        Self::Ref(ident.into())
    }

    /// Create a new vector expression.
    #[cfg(glam)]
    pub fn vector(items: impl IntoIterator<Item=Expr>) -> Self {
        Self::Vector(items.into_iter().collect())
    }

    /// Create a new call expression.
    pub fn call(callable: Expr, args: impl IntoIterator<Item=Expr>) -> Self {
        Self::Call(Box::new(callable), args.into_iter().collect())
    }

    /// Create a new subscript expression.
    pub fn subscript(value: Expr, index: Expr) -> Self {
        Self::Subscript(Box::new(value), Box::new(index))
    }

    /// Create a new member access expression.
    pub fn access(value: Expr, member: impl Into<Ident>) -> Self {
        Self::Access(Box::new(value), member.into())
    }

    /// Create a new unary operator expression.
    pub fn unary(op: UnaryOp, arg: Expr) -> Self {
        Self::Unary(op, Box::new(arg))
    }

    /// Create a new binary operator expression.
    pub fn binary(op: BinaryOp, lhs: Expr, rhs: Expr) -> Self {
        Self::Binary(op, Box::new(lhs), Box::new(rhs))
    }
}

#[cfg(serde)]
impl serde::Serialize for Expr {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
        where S: serde::Serializer
    {
        use crate::parser::Handler;

        fn dump(fmt: &mut crate::parser::FormatHandler, expr: &Expr) -> String {
            match expr {
                Expr::Literal(lit) => match lit {
                    Literal::Bool(b) => fmt.bool(&b.to_string()),
                    Literal::Integer(i) => fmt.int(&i.to_string()),
                    Literal::Float(f) => fmt.float(&f.to_string()),
                    Literal::Symbol(s) => fmt.symbol(s),
                },
                Expr::Ref(ident) => fmt.ident(ident.as_str()),
                #[cfg(glam)] Expr::Vector(v) => {
                    let items: Vec<_> = v.iter().map(|item| dump(fmt, item)).collect();
                    fmt.vector(items)
                },
                Expr::Call(target, args) => {
                    let target = dump(fmt, target);
                    let args: Vec<_> = args.iter().map(|item| dump(fmt, item)).collect();
                    fmt.call(target, args)
                },
                Expr::Subscript(target, index) => {
                    let target = dump(fmt, target);
                    let index = dump(fmt, index);
                    fmt.subscript(target, index)
                },
                Expr::Access(target, member) => {
                    let target = dump(fmt, target);
                    fmt.access(target, member.as_str())
                },
                Expr::Unary(op, arg) => {
                    let arg = dump(fmt, arg);
                    fmt.unary_expr(*op, arg)
                },
                Expr::Binary(op, lhs, rhs) => {
                    // TODO: can we avoid adding superfluous parens here?
                    let lhs = dump(fmt, lhs);
                    let rhs = dump(fmt, rhs);
                    format!("({})", fmt.binary_expr(lhs, *op, rhs))
                },
            }
        }

        let mut formatter = crate::parser::FormatHandler::default();
        let expr = dump(&mut formatter, self);
        serializer.serialize_str(&expr)
    }
}


/// Literal (constant) value
///
/// Unlike [`Value`], this is parsed directly from a literal in the expression source,
/// and can thus only represent values which can be represented literally.
#[derive(Clone, Debug, EnumAsInner, PartialEq, Unwrap)]
pub enum Literal {
    Bool(bool),
    Integer(i64),
    Float(f32),
    Symbol(String),
}

impl Literal {
    /// Create a new symbol literal.
    pub fn symbol(s: impl Into<String>) -> Self {
        Self::Symbol(s.into())
    }
}

macro_rules! impl_Literal_from {
    ($ty:ty => $variant:ident) => {
        impl From<$ty> for Literal {
            fn from(v: $ty) -> Self {
                Self::$variant(v)
            }
        }
    }
}

impl_Literal_from!(bool => Bool);
impl_Literal_from!(i64 => Integer);
impl_Literal_from!(f32 => Float);
// There is no String => Symbol conversion, on the off-chance we have literal strings and
// string operations at some point in the future.

impl From<&Literal> for Value {
    fn from(literal: &Literal) -> Self {
        match literal {
            Literal::Bool(b) => Value::Bool(*b),
            Literal::Integer(i) => Value::Integer(*i),
            Literal::Float(f) => Value::Float(*f),
            Literal::Symbol(s) => Value::symbol(s),
        }
    }
}

impl From<Literal> for Value {
    fn from(literal: Literal) -> Self {
        match literal {
            Literal::Symbol(s) => Value::symbol(s),
            lit => Value::from(&lit),
        }
    }
}


/// Identifier, such as a name of a variable or function.
#[derive(Clone, Debug, Default, Deref, Display, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Ident(Cow<'static, str>);

impl From<&'static str> for Ident {
    fn from(s: &'static str) -> Self {
        Ident(Cow::Borrowed(s))
    }
}

impl From<String> for Ident {
    fn from(s: String) -> Self {
        Ident(Cow::Owned(s))
    }
}

impl Ident {
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl AsRef<str> for Ident {
    fn as_ref(&self) -> &str {
        self.0.as_ref()
    }
}

impl Borrow<str> for Ident {
    fn borrow(&self) -> &str {
        self.as_ref()
    }
}


/// Unary operator.
#[derive(Clone, Copy, Debug, Display, Eq, Hash, IsVariant, PartialEq)]
pub enum UnaryOp {
    /// Unary arithmetic minus.
    #[display(fmt = "-")]
    Neg,

    /// Logical NOT.
    #[display(fmt = "!")]
    Not,
}


/// Binary operator.
#[derive(Clone, Copy, Debug, Display, Eq, Hash, IsVariant, PartialEq)]
pub enum BinaryOp {
    /// Addition.
    #[display(fmt = "+")]
    Add,

    /// Subtraction.
    #[display(fmt = "-")]
    Sub,

    /// Multiplication.
    #[display(fmt = "*")]
    Mul,

    /// Division.
    #[display(fmt = "/")]
    Div,

    /// Exponentiation (raising to a power).
    #[display(fmt = "^")]
    Pow,

    /// Equality.
    #[display(fmt = "==")]
    Eq,

    /// Inequality.
    #[display(fmt = "!=")]
    NotEq,

    /// Less-than comparison.
    #[display(fmt = "<")]
    Less,

    /// Less-or-equal comparison.
    #[display(fmt = "<=")]
    LessOrEq,

    /// Greater-than comparison.
    #[display(fmt = ">")]
    Greater,

    /// Greater-or-equal comparison.
    #[display(fmt = ">=")]
    GreaterOrEq,

    /// Logical AND.
    #[display(fmt = "&&")]
    And,

    /// Logical OR.
    #[display(fmt = "||")]
    Or,
}