Skip to content
Snippets Groups Projects
value.rs 10.8 KiB
Newer Older
Louis's avatar
Louis committed
use crate::parser::ast::LiteralNode;
use crate::runtime::numbers::Number;
use std::fmt::{Display, Formatter};
use std::ops::{Add, Div, Mul, Not, Rem, Sub};
Louis's avatar
Louis committed

#[derive(Clone, Debug, Default)]
Louis's avatar
Louis committed
#[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>),
Louis's avatar
Louis committed
	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(),
		}
	}

Louis's avatar
Louis committed
	/// 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 {
Louis's avatar
Louis committed
		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)
		);
	}
}