use std::fmt::{Debug, Display};
use crate::value::Value;
#[derive(Clone, PartialEq)]
pub enum Type {
Value,
String,
Fn {
args: Vec<Type>,
variadic_arg: Option<Box<Type>>,
returns: Box<Type>,
},
Bool,
}
impl Type {
pub fn name(&self) -> String {
match self {
Type::Value => "Value".to_string(),
Type::String => "String".to_string(),
Type::Fn {
args,
variadic_arg,
returns,
} => {
let mut args: Vec<String> = args.iter().map(|arg| arg.name()).collect();
if let Some(varg) = variadic_arg {
args.push(format!("...{}", varg.name()));
}
let args = args.join(", ");
let returns = returns.name();
format!("Fn({args}) -> {returns}")
}
Type::Bool => "Bool".to_string(),
}
}
}
impl Display for Type {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name())
}
}
impl Debug for Type {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name())
}
}
impl From<Value> for Type {
fn from(value: Value) -> Self {
match value {
Value::String(_) => Type::String,
Value::Fn(builtin_fn) => {
let mut args: Vec<Type> =
builtin_fn.args.iter().map(|arg| arg.ty.clone()).collect();
let variadic_arg = builtin_fn
.args
.last()
.filter(|arg| arg.variadic)
.map(|arg| Box::new(arg.ty.clone()));
if variadic_arg.is_some() {
args.pop();
}
Type::Fn {
args,
variadic_arg,
returns: Box::new(builtin_fn.return_type.clone()),
}
}
Value::Bool(_) => Type::Bool,
}
}
}
#[cfg(test)]
mod from_tests {
use std::rc::Rc;
use crate::prelude::{BuiltinFn, FnArg};
use super::*;
#[test]
fn test_from_string_value() {
let string_value = Value::String("test".to_string());
let ty: Type = string_value.into();
assert_eq!(Type::String, ty);
}
#[test]
fn test_get_type_string_value() {
let string_value = Value::String("test".to_string());
let ty: Type = string_value.get_type();
assert_eq!(Type::String, ty);
}
#[test]
fn test_from_bool_value() {
let bool_value = Value::Bool(true);
let ty: Type = bool_value.into();
assert_eq!(Type::Bool, ty);
}
#[test]
fn test_get_type_bool_value() {
let bool_value = Value::Bool(true);
let ty: Type = bool_value.get_type();
assert_eq!(Type::Bool, ty);
}
#[test]
fn test_from_fn_value() {
let builtin_fn = Value::Fn(Rc::new(BuiltinFn {
name: "test".to_string(),
args: vec![FnArg::new("a", Type::Value)],
return_type: Type::String,
func: Rc::new(|_| Value::String("".to_string())),
}));
let ty: Type = builtin_fn.into();
assert_eq!(
Type::Fn {
args: vec![Type::Value],
variadic_arg: None,
returns: Box::new(Type::String),
},
ty
);
}
#[test]
fn test_get_type_fn_value() {
let builtin_fn = Value::Fn(Rc::new(BuiltinFn {
name: "test".to_string(),
args: vec![FnArg::new("a", Type::Value)],
return_type: Type::String,
func: Rc::new(|_| Value::String("".to_string())),
}));
let ty: Type = builtin_fn.get_type();
assert_eq!(
Type::Fn {
args: vec![Type::Value],
variadic_arg: None,
returns: Box::new(Type::String),
},
ty
);
}
}
#[cfg(test)]
mod name_and_display_tests {
use super::*;
#[test]
fn test_name_value() {
assert_eq!("Value", Type::Value.name());
}
#[test]
fn test_name_string() {
assert_eq!("String", Type::String.name());
}
#[test]
fn test_name_fn_0_args() {
assert_eq!(
"Fn() -> String",
Type::Fn {
args: vec![],
variadic_arg: None,
returns: Type::String.into(),
}
.name()
);
}
#[test]
fn test_name_fn_0_args_and_varadic() {
assert_eq!(
"Fn(...Value) -> String",
Type::Fn {
args: vec![],
variadic_arg: Some(Type::Value.into()),
returns: Type::String.into(),
}
.name()
);
}
#[test]
fn test_name_fn_1_args() {
assert_eq!(
"Fn(Value) -> String",
Type::Fn {
args: vec![Type::Value],
variadic_arg: None,
returns: Type::String.into(),
}
.name()
);
}
#[test]
fn test_name_fn_2_args() {
assert_eq!(
"Fn(Value, String) -> String",
Type::Fn {
args: vec![Type::Value, Type::String],
variadic_arg: None,
returns: Type::String.into(),
}
.name()
);
}
#[test]
fn test_name_fn_2_args_and_varadic() {
assert_eq!(
"Fn(Value, String, ...Value) -> String",
Type::Fn {
args: vec![Type::Value, Type::String],
variadic_arg: Some(Type::Value.into()),
returns: Type::String.into(),
}
.name()
);
}
#[test]
fn test_name_bool() {
assert_eq!("Bool", Type::Bool.name());
}
}