reqlang-expr 0.9.0

A tiny (bytecode compiled, stack VM interpreted) expression language for reqlang's templating engine.
Documentation
//! The core value type used in the virtual machine.

use std::fmt::Display;

use crate::{
    builtins::BuiltinFn,
    errors::{ExprResult, RuntimeError},
    types::Type,
};

#[derive(Debug, Clone, PartialEq)]
pub enum Value {
    String(String),
    Number(f64),
    Fn(Box<BuiltinFn<'static>>),
    Bool(bool),
    Type(Box<Type>),
}

impl Value {
    pub fn get_type(&self) -> Type {
        self.clone().into()
    }

    pub fn get_string(&self) -> ExprResult<&str> {
        match self {
            Value::String(s) => Ok(s.as_str()),
            _ => Err(vec![(
                RuntimeError::TypeMismatch {
                    expected: Type::String,
                    actual: self.get_type(),
                }
                .into(),
                0..0,
            )]),
        }
    }

    pub fn get_func(&self) -> ExprResult<Box<BuiltinFn<'_>>> {
        match self {
            Value::Fn(f) => Ok(f.clone()),
            _ => Err(vec![(
                RuntimeError::TypeMismatch {
                    expected: Type::Fn {
                        args: vec![],
                        variadic_arg: Some(Type::Value.into()),
                        returns: Type::Value.into(),
                    },
                    actual: self.get_type(),
                }
                .into(),
                0..0,
            )]),
        }
    }

    pub fn get_bool(&self) -> ExprResult<bool> {
        match self {
            Value::Bool(s) => Ok(*s),
            _ => Err(vec![(
                RuntimeError::TypeMismatch {
                    expected: Type::Bool,
                    actual: self.get_type(),
                }
                .into(),
                0..0,
            )]),
        }
    }
}

impl Display for Value {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            Value::String(string) => write!(f, "`{string}`"),
            Value::Number(value) => write!(f, "{value}"),
            Value::Fn(builtin) => write!(f, "{builtin:?}"),
            Value::Bool(value) => write!(f, "{value}"),
            Value::Type(ty) => write!(f, "Type<{ty}>"),
        }
    }
}

impl From<bool> for Value {
    fn from(value: bool) -> Self {
        Self::Bool(value)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    use pretty_assertions::assert_eq;

    fn example_builtin(_args: Vec<Value>) -> ExprResult<Value> {
        Ok(Value::String("".to_string()))
    }

    #[test]
    fn get_bool_on_string() {
        assert_eq!(
            Err(vec![(
                RuntimeError::TypeMismatch {
                    expected: Type::Bool,
                    actual: Type::String
                }
                .into(),
                0..0
            )]),
            Value::String("string".to_string()).get_bool()
        );
    }

    #[test]
    fn get_bool_on_bool_true() {
        assert_eq!(Ok(true), Value::Bool(true).get_bool());
    }

    #[test]
    fn get_string_on_bool() {
        assert_eq!(
            Err(vec![(
                RuntimeError::TypeMismatch {
                    expected: Type::String,
                    actual: Type::Bool
                }
                .into(),
                0..0
            )]),
            Value::Bool(true).get_string()
        );
    }

    #[test]
    fn get_string_on_string() {
        let value = Value::String("test".to_string());
        assert_eq!(Ok("test"), value.get_string());
    }

    #[test]
    fn get_func_on_string() {
        let value = Value::String("not a function".to_string());
        assert_eq!(
            Err(vec![(
                RuntimeError::TypeMismatch {
                    expected: Type::Fn {
                        args: vec![],
                        variadic_arg: Some(Type::Value.into()),
                        returns: Type::Value.into()
                    },
                    actual: Type::String
                }
                .into(),
                0..0
            )]),
            value.get_func()
        );
    }

    #[test]
    fn get_func_on_func() {
        let expected_fn: Box<BuiltinFn> = BuiltinFn {
            name: "name",
            args: &[],
            return_type: Type::Unknown,
            func: example_builtin,
        }
        .into();

        let value = Value::Fn(expected_fn.clone());

        assert_eq!(Ok(expected_fn), value.get_func());
    }

    #[test]
    fn get_func_on_bool() {
        let value = Value::Bool(true);
        assert_eq!(
            Err(vec![(
                RuntimeError::TypeMismatch {
                    expected: Type::Fn {
                        args: vec![],
                        variadic_arg: Some(Type::Value.into()),
                        returns: Type::Value.into()
                    },
                    actual: Type::Bool
                }
                .into(),
                0..0
            )]),
            value.get_func()
        );
    }
}