use crate::parse::ast::BinaryOp; use crate::parser::ast::LiteralNode; use crate::runtime::numbers::Number; use std::fmt::{Display, Formatter}; use std::ops::{Add, Div, Mul, Not, Rem, Sub}; #[derive(Clone, Debug, Default)] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(rename_all = "snake_case") )] #[cfg_attr( all(feature = "serde", feature = "verbose-serde"), serde(tag = "type", content = "value") )] #[cfg_attr( all(feature = "serde", not(feature = "verbose-serde")), serde(untagged) )] pub enum ForgeValue { Number(Number), Boolean(bool), String(String), List(Vec<ForgeValue>), #[default] Null, } impl ForgeValue { pub fn null() -> Self { ForgeValue::Null } pub fn invert(&self) -> Self { match self { ForgeValue::Number(num) => match num { Number::Integer(val) => Number::Integer(-*val).into(), Number::Float(val) => Number::Float(-*val).into(), }, val => val.clone(), } } /// Perform type coercion to force this value into a bool /// /// ## True /// - Non-zero number /// - Literal value "true" /// - Non-empty list /// - Non-empty string /// /// ## False /// - Zero /// - Literal value "false" /// - Empty list /// - Empty string /// - Null pub fn as_bool(&self) -> bool { match self { ForgeValue::Number(val) => val != &Number::integer(0), ForgeValue::Boolean(val) => *val, ForgeValue::List(val) => !val.is_empty(), ForgeValue::String(val) => !val.is_empty(), ForgeValue::Null => false, } } /// Perform type coercion to force this value into a number /// /// Number => Number /// true => 1 /// false => 0 /// Non-Empty List => 1 /// Empty List => 0 /// Non-Empty String => 1 /// Empty String => 0 /// null => 0 pub fn as_number(&self) -> Number { match self { ForgeValue::Number(val) => *val, ForgeValue::Boolean(val) => { if *val { Number::Integer(1) } else { Number::Integer(0) } } ForgeValue::List(val) => { if val.is_empty() { Number::Integer(0) } else { Number::Integer(1) } } ForgeValue::String(val) => { if val.is_empty() { Number::Integer(0) } else { Number::Integer(1) } } ForgeValue::Null => Number::Integer(0), } } /// Perform type coercion to force this value into a string /// /// Does not quote strings, just returns their value /// Arrays print as a comma seperated list, surrounded by "\[" "]" pub fn as_string(&self) -> String { self.to_string() } } impl Display for ForgeValue { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { Self::Number(n) => write!(f, "{}", n), Self::String(st) => st.fmt(f), Self::Null => write!(f, "null"), Self::Boolean(val) => { if *val { write!(f, "true") } else { write!(f, "false") } } Self::List(val) => { write!( f, "[{}]", val.iter() .map(|val| format!("{}", val)) .fold(String::new(), |mut acc, va| { if acc.is_empty() { va } else { acc.push_str(", "); acc.push_str(va.as_str()); acc } }) ) } } } } impl From<LiteralNode> for ForgeValue { fn from(value: LiteralNode) -> Self { match value { LiteralNode::Boolean(val) => ForgeValue::Boolean(val), LiteralNode::String(val) => ForgeValue::String(val), LiteralNode::Number(val) => ForgeValue::Number(val), LiteralNode::Null => ForgeValue::Null, } } } impl From<String> for ForgeValue { fn from(value: String) -> Self { ForgeValue::String(value) } } impl<'a> From<&'a str> for ForgeValue { fn from(value: &'a str) -> Self { ForgeValue::String(String::from(value)) } } impl From<bool> for ForgeValue { fn from(value: bool) -> Self { ForgeValue::Boolean(value) } } impl From<Number> for ForgeValue { fn from(value: Number) -> Self { ForgeValue::Number(value) } } macro_rules! from_number { ($($typ:ty),+) => { $( impl From<$typ> for ForgeValue { fn from(value: $typ) -> Self { ForgeValue::Number(Number::from(value)) } } )+ }; } from_number!(u8, u16, u32, u64, usize, i8, i16, i32, i64, isize, f32, f64); impl PartialEq for ForgeValue { fn eq(&self, other: &Self) -> bool { match (self, other) { (ForgeValue::String(s1), ForgeValue::String(s2)) => s1.eq(s2), (ForgeValue::Number(n1), ForgeValue::Number(n2)) => n1.eq(n2), (ForgeValue::Boolean(b1), ForgeValue::Boolean(b2)) => b1.eq(b2), (ForgeValue::Null, ForgeValue::Null) => true, _ => false, } } } impl Add for ForgeValue { type Output = ForgeValue; fn add(self, rhs: Self) -> Self::Output { match (self, rhs) { (ForgeValue::String(s1), ForgeValue::String(s2)) => ForgeValue::String({ let mut buffer = String::with_capacity(s1.len() + s2.len()); buffer.push_str(s1.as_str()); buffer.push_str(s2.as_str()); buffer }), (ForgeValue::Number(n1), ForgeValue::Number(n2)) => ForgeValue::Number(n1 + n2), (ForgeValue::String(val1), ForgeValue::Number(val2)) => { ForgeValue::String(format!("{}{}", val1, val2)) } (ForgeValue::Number(val1), ForgeValue::String(val2)) => { ForgeValue::String(format!("{}{}", val1, val2)) } (ForgeValue::List(first), ForgeValue::List(second)) => { ForgeValue::List(first.iter().chain(second.iter()).cloned().collect()) } (other_value, ForgeValue::List(mut list)) | (ForgeValue::List(mut list), other_value) => { list.push(other_value); ForgeValue::List(list) } // Boolean && null values are ignored (ForgeValue::Number(val), ForgeValue::Null) | (ForgeValue::Number(val), ForgeValue::Boolean(_)) | (ForgeValue::Null, ForgeValue::Number(val)) | (ForgeValue::Boolean(_), ForgeValue::Number(val)) => ForgeValue::Number(val), (ForgeValue::Boolean(val), ForgeValue::Null) | (ForgeValue::Null, ForgeValue::Boolean(val)) => ForgeValue::Boolean(val), (ForgeValue::Null, ForgeValue::String(val)) | (ForgeValue::Boolean(_), ForgeValue::String(val)) | (ForgeValue::String(val), ForgeValue::Null) | (ForgeValue::String(val), ForgeValue::Boolean(_)) => ForgeValue::String(val), (ForgeValue::Null, ForgeValue::Null) => ForgeValue::Null, (ForgeValue::Boolean(b1), ForgeValue::Boolean(b2)) => ForgeValue::Boolean(b1 && b2), } } } #[derive(Copy, Clone, Debug, PartialEq)] pub struct UnsupportedOperation { operation: BinaryOp, left_type: &'static str, right_type: &'static str, } impl Display for UnsupportedOperation { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, "Cannot perform {} with lhs type {} and rhs type {}", self.operation, self.left_type, self.right_type ) } } impl Sub for ForgeValue { type Output = Result<ForgeValue, UnsupportedOperation>; fn sub(self, rhs: Self) -> Self::Output { match (self, rhs) { (ForgeValue::Number(n1), ForgeValue::Number(n2)) => Ok(ForgeValue::Number(n1 - n2)), (ForgeValue::Null, ForgeValue::Null) => Ok(ForgeValue::Null), (ForgeValue::Number(_), _) => Err(UnsupportedOperation { operation: BinaryOp::Subtract, left_type: "number", right_type: "non-number", }), (_, _) => Err(UnsupportedOperation { operation: BinaryOp::Subtract, left_type: "non-number", right_type: "non-number", }), } } } impl Div for ForgeValue { type Output = Result<ForgeValue, UnsupportedOperation>; fn div(self, rhs: Self) -> Self::Output { match (self, rhs) { (ForgeValue::Number(n1), ForgeValue::Number(n2)) => Ok(ForgeValue::Number(n1 / n2)), (ForgeValue::Null, ForgeValue::Null) => Ok(ForgeValue::Null), (ForgeValue::Number(_), _) => Err(UnsupportedOperation { operation: BinaryOp::Divide, left_type: "number", right_type: "non-number", }), (_, _) => Err(UnsupportedOperation { operation: BinaryOp::Divide, left_type: "non-number", right_type: "non-number", }), } } } impl Mul for ForgeValue { type Output = Result<ForgeValue, UnsupportedOperation>; fn mul(self, rhs: Self) -> Self::Output { match (self, rhs) { (ForgeValue::Number(n1), ForgeValue::Number(n2)) => Ok(ForgeValue::Number(n1 * n2)), (ForgeValue::Null, ForgeValue::Null) => Ok(ForgeValue::Null), (ForgeValue::Number(_), _) => Err(UnsupportedOperation { operation: BinaryOp::Multiply, left_type: "number", right_type: "non-number", }), (_, _) => Err(UnsupportedOperation { operation: BinaryOp::Multiply, left_type: "non-number", right_type: "non-number", }), } } } impl Rem for ForgeValue { type Output = Result<ForgeValue, UnsupportedOperation>; fn rem(self, rhs: Self) -> Self::Output { match (self, rhs) { (ForgeValue::Number(n1), ForgeValue::Number(n2)) => Ok(ForgeValue::Number(n1 % n2)), (ForgeValue::Null, ForgeValue::Null) => Ok(ForgeValue::Null), (ForgeValue::Number(_), _) => Err(UnsupportedOperation { operation: BinaryOp::Modulo, left_type: "number", right_type: "non-number", }), (_, _) => Err(UnsupportedOperation { operation: BinaryOp::Modulo, left_type: "non-number", right_type: "non-number", }), } } } impl Not for ForgeValue { type Output = ForgeValue; fn not(self) -> Self::Output { ForgeValue::Boolean(!self.as_bool()) } } #[cfg(test)] mod value_tests { use crate::runtime::value::ForgeValue; #[test] fn strict_type_equality() { assert_ne!(ForgeValue::from(22), ForgeValue::from("22")); assert_ne!(ForgeValue::from(true), ForgeValue::from("true")); assert_ne!(ForgeValue::from(false), ForgeValue::from("false")); assert_ne!(ForgeValue::from(true), ForgeValue::from(1)); assert_ne!(ForgeValue::from(false), ForgeValue::from(0)); } #[test] fn add_strings_concats() { let first = ForgeValue::from("hello_"); let second = ForgeValue::from("world"); assert_eq!( first + second, ForgeValue::String(String::from("hello_world")) ); } #[test] fn add_numbers() { let first = ForgeValue::from(250); let second = ForgeValue::from(250); let third = ForgeValue::from(250); let fourth = ForgeValue::from(250.0); assert_eq!(first + second, ForgeValue::from(500)); assert_eq!(third + fourth, ForgeValue::from(500.0)); } #[test] fn add_bool_noop() { assert_eq!( ForgeValue::from("Something") + ForgeValue::from(true), ForgeValue::from("Something") ); assert_eq!( ForgeValue::from("Something") + ForgeValue::from(false), ForgeValue::from("Something") ); assert_eq!( ForgeValue::from(1000) + ForgeValue::from(true), ForgeValue::from(1000) ); assert_eq!( ForgeValue::from(1000) + ForgeValue::from(false), ForgeValue::from(1000) ); } #[test] fn add_null_noop() { assert_eq!( ForgeValue::from("Something") + ForgeValue::null(), ForgeValue::from("Something") ); assert_eq!( ForgeValue::from(1000) + ForgeValue::null(), ForgeValue::from(1000) ); assert_eq!( ForgeValue::from(true) + ForgeValue::null(), ForgeValue::from(true) ); assert_eq!( ForgeValue::from(false) + ForgeValue::null(), ForgeValue::from(false) ); } }