From 7f9eb505f8a1af221effb490f9a3f54f27ecb86e Mon Sep 17 00:00:00 2001
From: Louis Capitanchik <contact@louiscap.co>
Date: Wed, 17 May 2023 14:39:29 +0100
Subject: [PATCH] Use 'test_case' for paramatised tests

---
 Cargo.lock                                    |  83 +++++++++-
 forge-script-lang/Cargo.toml                  |   5 +-
 forge-script-lang/src/error/mod.rs            |   4 +-
 forge-script-lang/src/lexer/tokens.rs         | 154 ++++++------------
 .../src/runtime/executor/simple.rs            | 121 +++++---------
 forge-script-lang/src/runtime/value.rs        |  51 ++++++
 6 files changed, 221 insertions(+), 197 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 7203c79..9324f86 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -42,6 +42,7 @@ dependencies = [
  "nom_locate",
  "peg",
  "serde",
+ "test-case",
 ]
 
 [[package]]
@@ -138,6 +139,30 @@ version = "0.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9fa00462b37ead6d11a82c9d568b26682d78e0477dc02d1966c013af80969739"
 
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
 [[package]]
 name = "proc-macro2"
 version = "1.0.56"
@@ -184,7 +209,18 @@ checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.15",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
 ]
 
 [[package]]
@@ -198,12 +234,53 @@ dependencies = [
  "unicode-ident",
 ]
 
+[[package]]
+name = "test-case"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a1d6e7bde536b0412f20765b76e921028059adfd1b90d8974d33fd3c91b25df"
+dependencies = [
+ "test-case-macros",
+]
+
+[[package]]
+name = "test-case-core"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d10394d5d1e27794f772b6fc854c7e91a2dc26e2cbf807ad523370c2a59c0cee"
+dependencies = [
+ "cfg-if",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "test-case-macros"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eeb9a44b1c6a54c1ba58b152797739dba2a83ca74e18168a68c980eb142f9404"
+dependencies = [
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+ "test-case-core",
+]
+
 [[package]]
 name = "unicode-ident"
 version = "1.0.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
 
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
 [[package]]
 name = "wasm-bindgen"
 version = "0.2.85"
@@ -225,7 +302,7 @@ dependencies = [
  "once_cell",
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.15",
  "wasm-bindgen-shared",
 ]
 
@@ -247,7 +324,7 @@ checksum = "4783ce29f09b9d93134d41297aded3a712b7b979e9c6f28c32cb88c973a94869"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.15",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
diff --git a/forge-script-lang/Cargo.toml b/forge-script-lang/Cargo.toml
index 13235fc..ecf2488 100644
--- a/forge-script-lang/Cargo.toml
+++ b/forge-script-lang/Cargo.toml
@@ -19,4 +19,7 @@ verbose-serde = []
 nom.workspace = true
 nom_locate.workspace = true
 peg.workspace = true
-serde = { version = "1.0", optional = true, features = ["derive"] }
\ No newline at end of file
+serde = { version = "1.0", optional = true, features = ["derive"] }
+
+[dev-dependencies]
+test-case = "3.1.0"
\ No newline at end of file
diff --git a/forge-script-lang/src/error/mod.rs b/forge-script-lang/src/error/mod.rs
index 7019a93..b3986af 100644
--- a/forge-script-lang/src/error/mod.rs
+++ b/forge-script-lang/src/error/mod.rs
@@ -53,7 +53,7 @@ pub struct ParseError<'a> {
 	pub kind: ParseErrorKind<'a>,
 }
 
-#[derive(Debug)]
+#[derive(Debug, PartialEq)]
 pub enum ForgeErrorKind<'a> {
 	IncompleteInput,
 	LexerError(nom::error::Error<Span<'a>>),
@@ -63,7 +63,7 @@ pub enum ForgeErrorKind<'a> {
 	},
 }
 
-#[derive(Debug)]
+#[derive(Debug, PartialEq)]
 pub struct ForgeError<'a> {
 	pub kind: ForgeErrorKind<'a>,
 }
diff --git a/forge-script-lang/src/lexer/tokens.rs b/forge-script-lang/src/lexer/tokens.rs
index bb2731c..ac79700 100644
--- a/forge-script-lang/src/lexer/tokens.rs
+++ b/forge-script-lang/src/lexer/tokens.rs
@@ -241,7 +241,7 @@ impl<'a> TryFrom<&'a str> for ScriptTokenType<'a> {
 	}
 }
 
-#[derive(Clone)]
+#[derive(Clone, PartialEq)]
 pub struct ScriptToken<'a> {
 	pub position: Span<'a>,
 	pub token_type: ScriptTokenType<'a>,
@@ -267,112 +267,52 @@ impl<'a> Debug for ScriptToken<'a> {
 
 #[cfg(test)]
 mod token_tests {
-	use super::ScriptTokenType;
+	use super::{ScriptTokenType, TokenFromStringError};
+	use test_case::test_case;
 
-	#[test]
-	fn match_type_from_string() {
-		assert_eq!(
-			ScriptTokenType::try_from("("),
-			Ok(ScriptTokenType::LeftParen)
-		);
-		assert_eq!(
-			ScriptTokenType::try_from(")"),
-			Ok(ScriptTokenType::RightParen)
-		);
-		assert_eq!(
-			ScriptTokenType::try_from("{"),
-			Ok(ScriptTokenType::LeftBrace)
-		);
-		assert_eq!(
-			ScriptTokenType::try_from("}"),
-			Ok(ScriptTokenType::RightBrace)
-		);
-		assert_eq!(ScriptTokenType::try_from(","), Ok(ScriptTokenType::Comma));
-		assert_eq!(ScriptTokenType::try_from("."), Ok(ScriptTokenType::Dot));
-		assert_eq!(ScriptTokenType::try_from("-"), Ok(ScriptTokenType::Minus));
-		assert_eq!(ScriptTokenType::try_from("+"), Ok(ScriptTokenType::Plus));
-		assert_eq!(
-			ScriptTokenType::try_from(";"),
-			Ok(ScriptTokenType::Semicolon)
-		);
-		assert_eq!(ScriptTokenType::try_from("/"), Ok(ScriptTokenType::Slash));
-		assert_eq!(
-			ScriptTokenType::try_from("*"),
-			Ok(ScriptTokenType::Asterisk)
-		);
-		assert_eq!(ScriptTokenType::try_from("!"), Ok(ScriptTokenType::Bang));
-		assert_eq!(
-			ScriptTokenType::try_from("!="),
-			Ok(ScriptTokenType::BangEqual)
-		);
-		assert_eq!(ScriptTokenType::try_from("="), Ok(ScriptTokenType::Equal));
-		assert_eq!(
-			ScriptTokenType::try_from("=="),
-			Ok(ScriptTokenType::EqualEqual)
-		);
-		assert_eq!(ScriptTokenType::try_from(">"), Ok(ScriptTokenType::Greater));
-		assert_eq!(
-			ScriptTokenType::try_from(">="),
-			Ok(ScriptTokenType::GreaterEqual)
-		);
-		assert_eq!(ScriptTokenType::try_from("<"), Ok(ScriptTokenType::Less));
-		assert_eq!(
-			ScriptTokenType::try_from("<="),
-			Ok(ScriptTokenType::LessEqual)
-		);
-		assert_eq!(
-			ScriptTokenType::try_from("||"),
-			Ok(ScriptTokenType::DoublePipe)
-		);
-		assert_eq!(
-			ScriptTokenType::try_from("&&"),
-			Ok(ScriptTokenType::DoubleAmpersand)
-		);
-		assert_eq!(ScriptTokenType::try_from("%"), Ok(ScriptTokenType::Modulo));
-		assert_eq!(ScriptTokenType::try_from("^"), Ok(ScriptTokenType::Caret));
-		assert_eq!(
-			ScriptTokenType::try_from("struct"),
-			Ok(ScriptTokenType::Class)
-		);
-		assert_eq!(ScriptTokenType::try_from("else"), Ok(ScriptTokenType::Else));
-		assert_eq!(
-			ScriptTokenType::try_from("fn"),
-			Ok(ScriptTokenType::Function)
-		);
-		assert_eq!(ScriptTokenType::try_from("for"), Ok(ScriptTokenType::For));
-		assert_eq!(ScriptTokenType::try_from("if"), Ok(ScriptTokenType::If));
-		assert_eq!(ScriptTokenType::try_from("null"), Ok(ScriptTokenType::Null));
-		assert_eq!(
-			ScriptTokenType::try_from("print"),
-			Ok(ScriptTokenType::Print)
-		);
-		assert_eq!(
-			ScriptTokenType::try_from("return"),
-			Ok(ScriptTokenType::Return)
-		);
-		assert_eq!(
-			ScriptTokenType::try_from("super"),
-			Ok(ScriptTokenType::Super)
-		);
-		assert_eq!(ScriptTokenType::try_from("this"), Ok(ScriptTokenType::This));
-		assert_eq!(ScriptTokenType::try_from("let"), Ok(ScriptTokenType::Let));
-		assert_eq!(
-			ScriptTokenType::try_from("while"),
-			Ok(ScriptTokenType::While)
-		);
-		assert_eq!(
-			ScriptTokenType::try_from("export"),
-			Ok(ScriptTokenType::Export)
-		);
-		assert_eq!(
-			ScriptTokenType::try_from("import"),
-			Ok(ScriptTokenType::Import)
-		);
-		assert_eq!(ScriptTokenType::try_from("as"), Ok(ScriptTokenType::Alias));
-		assert_eq!(ScriptTokenType::try_from("from"), Ok(ScriptTokenType::From));
-		assert_eq!(
-			ScriptTokenType::try_from("typeof"),
-			Ok(ScriptTokenType::Typeof)
-		);
+	#[test_case("(" => Ok(ScriptTokenType::LeftParen); r#"Parse LeftParen"#)]
+	#[test_case(")" => Ok(ScriptTokenType::RightParen); r#"Parse RightParen"#)]
+	#[test_case("{" => Ok(ScriptTokenType::LeftBrace); r#"Parse LeftBrace"#)]
+	#[test_case("}" => Ok(ScriptTokenType::RightBrace); r#"Parse RightBrace"#)]
+	#[test_case("," => Ok(ScriptTokenType::Comma); r#"Parse Comma"#)]
+	#[test_case("." => Ok(ScriptTokenType::Dot); r#"Parse Dot"#)]
+	#[test_case("-" => Ok(ScriptTokenType::Minus); r#"Parse Minus"#)]
+	#[test_case("+" => Ok(ScriptTokenType::Plus); r#"Parse Plus"#)]
+	#[test_case(";" => Ok(ScriptTokenType::Semicolon); r#"Parse Semicolon"#)]
+	#[test_case("/" => Ok(ScriptTokenType::Slash); r#"Parse Slash"#)]
+	#[test_case("*" => Ok(ScriptTokenType::Asterisk); r#"Parse Asterisk"#)]
+	#[test_case("!" => Ok(ScriptTokenType::Bang); r#"Parse Bang"#)]
+	#[test_case("!=" => Ok(ScriptTokenType::BangEqual); r#"Parse BangEqual"#)]
+	#[test_case("=" => Ok(ScriptTokenType::Equal); r#"Parse Equal"#)]
+	#[test_case("==" => Ok(ScriptTokenType::EqualEqual); r#"Parse EqualEqual"#)]
+	#[test_case(">" => Ok(ScriptTokenType::Greater); r#"Parse Greater"#)]
+	#[test_case(">=" => Ok(ScriptTokenType::GreaterEqual); r#"Parse GreaterEqual"#)]
+	#[test_case("<" => Ok(ScriptTokenType::Less); r#"Parse Less"#)]
+	#[test_case("<=" => Ok(ScriptTokenType::LessEqual); r#"Parse LessEqual"#)]
+	#[test_case("||" => Ok(ScriptTokenType::DoublePipe); r#"Parse DoublePipe"#)]
+	#[test_case("&&" => Ok(ScriptTokenType::DoubleAmpersand); r#"Parse DoubleAmpersand"#)]
+	#[test_case("%" => Ok(ScriptTokenType::Modulo); r#"Parse Modulo"#)]
+	#[test_case("^" => Ok(ScriptTokenType::Caret); r#"Parse Caret"#)]
+	#[test_case("struct" => Ok(ScriptTokenType::Class); r#"Parse Class"#)]
+	#[test_case("else" => Ok(ScriptTokenType::Else); r#"Parse Else"#)]
+	#[test_case("fn" => Ok(ScriptTokenType::Function); r#"Parse Function"#)]
+	#[test_case("for" => Ok(ScriptTokenType::For); r#"Parse For"#)]
+	#[test_case("if" => Ok(ScriptTokenType::If); r#"Parse If"#)]
+	#[test_case("null" => Ok(ScriptTokenType::Null); r#"Parse Null"#)]
+	#[test_case("print" => Ok(ScriptTokenType::Print); r#"Parse Print"#)]
+	#[test_case("return" => Ok(ScriptTokenType::Return); r#"Parse Return"#)]
+	#[test_case("super" => Ok(ScriptTokenType::Super); r#"Parse Super"#)]
+	#[test_case("this" => Ok(ScriptTokenType::This); r#"Parse This"#)]
+	#[test_case("let" => Ok(ScriptTokenType::Let); r#"Parse Let"#)]
+	#[test_case("while" => Ok(ScriptTokenType::While); r#"Parse While"#)]
+	#[test_case("export" => Ok(ScriptTokenType::Export); r#"Parse Export"#)]
+	#[test_case("import" => Ok(ScriptTokenType::Import); r#"Parse Import"#)]
+	#[test_case("as" => Ok(ScriptTokenType::Alias); r#"Parse Alias"#)]
+	#[test_case("from" => Ok(ScriptTokenType::From); r#"Parse From"#)]
+	#[test_case("typeof" => Ok(ScriptTokenType::Typeof); r#"Parse Typeof"#)]
+	#[test_case("true" => Ok(ScriptTokenType::Boolean(true)); r#"Parse true"#)]
+	#[test_case("false" => Ok(ScriptTokenType::Boolean(false)); r#"Parse false"#)]
+	fn parse_token_type(value: &'static str) -> Result<ScriptTokenType, TokenFromStringError> {
+		ScriptTokenType::try_from(value)
 	}
 }
diff --git a/forge-script-lang/src/runtime/executor/simple.rs b/forge-script-lang/src/runtime/executor/simple.rs
index 46fb554..a0b728d 100644
--- a/forge-script-lang/src/runtime/executor/simple.rs
+++ b/forge-script-lang/src/runtime/executor/simple.rs
@@ -238,85 +238,44 @@ impl Visitor for SimpleExecutor {
 #[cfg(test)]
 mod interpreter_test {
 	use crate::parse::parse_program;
-	use crate::runtime::executor::simple::{RuntimeError, SimpleExecutor};
+	use crate::runtime::executor::simple::{RuntimeError, RuntimeResult, SimpleExecutor};
 	use crate::runtime::executor::Visitor;
-	use crate::runtime::value::ForgeValue;
-
-	#[test]
-	fn the_basics() {
-		let add_numbers = parse_program("1 + 1").expect("Failed to parse");
-		let add_strings = parse_program("\"foo\" + \" \" + \"bar\"").expect("Failed to parse");
-		let concat_string_num = parse_program("\"#\" + 1").expect("Failed to parse");
-		let bad_mult = parse_program("false * 123").expect("Failed to parse");
+	use crate::runtime::value::{ForgeValue, UnsupportedOperation};
+	use test_case::test_case;
+
+	#[test_case("1 + 1" => Ok(ForgeValue::from(2)) ; "numeric addition")]
+	#[test_case(r#""foo" + " " + "bar""# => Ok(ForgeValue::from("foo bar")) ; "string concatenation")]
+	#[test_case(
+		r#"false * 132"# => Err(RuntimeError::BadOperands(UnsupportedOperation::mul("non-number", "number")));
+		"bad multiplication error"
+	)]
+	#[test_case("\"#\" + 1" => Ok(ForgeValue::from("#1")) ; "coerce number to string with concat")]
+	#[test_case("1 + -1" => Ok(ForgeValue::from(0)) ; "unary nested in binary")]
+	#[test_case("print 2 + 2" => Ok(ForgeValue::Null) ; "print is nullish")]
+	fn simple_expr(prog: &'static str) -> RuntimeResult<ForgeValue> {
+		let ast = parse_program(prog).expect("Failed to parse program");
 		let mut vm = SimpleExecutor::default();
-
-		assert_eq!(vm.evaluate_program(&add_numbers), Ok(ForgeValue::from(2)));
-		assert_eq!(
-			vm.evaluate_program(&add_strings),
-			Ok(ForgeValue::from("foo bar"))
-		);
-		assert_eq!(
-			vm.evaluate_program(&concat_string_num),
-			Ok(ForgeValue::from("#1"))
-		);
-		assert!(matches!(
-			vm.evaluate_program(&bad_mult),
-			Err(RuntimeError::BadOperands(_))
-		))
+		vm.evaluate_program(&ast)
 	}
 
-	#[test]
-	fn combos() {
-		let add_numbers = parse_program("1 + -1").expect("Failed to parse");
+	#[test_case("let foo" => Ok(ForgeValue::Null); "Initialise as null")]
+	#[test_case("let bar = 123" => Ok(ForgeValue::from(123)); "Initialise with value")]
+	#[test_case("let bar = 123; let bar = 456" => Err(RuntimeError::VariableAlreadyDefined(String::from("bar"))); "Double initialise error")]
+	#[test_case("print missing" => Err(RuntimeError::UndefinedVariable(String::from("missing"))); "Access undefined error")]
+	#[test_case("let bar = 123; let foo = bar * 2" =>Ok(ForgeValue::from(246)); "Initialise from other var")]
+	fn declare_variables(prog: &'static str) -> RuntimeResult<ForgeValue> {
+		let ast = parse_program(prog).expect("Failed to parse program");
 		let mut vm = SimpleExecutor::default();
-		assert_eq!(vm.evaluate_program(&add_numbers), Ok(ForgeValue::from(0)));
+		vm.evaluate_program(&ast)
 	}
 
-	#[test]
-	fn print() {
-		let print_4 = parse_program("print 2 + 2").expect("Failed to parse");
-
+	#[test_case("let value = true; if value { 123 } else { 456 }" => Ok(ForgeValue::from(123)); "eval if branch")]
+	#[test_case("let value = true; if !value { 123 } else { 456 }" => Ok(ForgeValue::from(456)); "eval else branch")]
+	#[test_case("let value = 2; if value == 1 { 123 } else if value == 2 { 456 } else { \"Nothing\" }" => Ok(ForgeValue::from(456)); "eval middle if else branch")]
+	fn conditional_blocks(prog: &'static str) -> RuntimeResult<ForgeValue> {
+		let ast = parse_program(prog).expect("Failed to parse program");
 		let mut vm = SimpleExecutor::default();
-		assert_eq!(vm.evaluate_program(&print_4), Ok(ForgeValue::Null));
-	}
-
-	#[test]
-	fn define_variables() {
-		let simple_define = parse_program("let foo").expect("Failed to parse");
-		let assignment = parse_program("let bar = 123").expect("Failed to parse");
-		let redecl_err = parse_program("let foo = 123").expect("Failed to parse");
-		let missing_err = parse_program("print missing").expect("Failed to parse");
-		let use_vars = parse_program("let splop = bar * 2").expect("Failed to parse");
-
-		let mut vm = SimpleExecutor::default();
-
-		assert_eq!(vm.evaluate_program(&simple_define), Ok(ForgeValue::Null));
-		assert_eq!(vm.evaluate_program(&assignment), Ok(ForgeValue::from(123)));
-		assert_eq!(
-			vm.evaluate_program(&redecl_err),
-			Err(RuntimeError::VariableAlreadyDefined(String::from("foo")))
-		);
-		assert_eq!(
-			vm.evaluate_program(&missing_err),
-			Err(RuntimeError::UndefinedVariable(String::from("missing")))
-		);
-		assert_eq!(vm.evaluate_program(&use_vars), Ok(ForgeValue::from(246)));
-		assert_eq!(vm.get_variable("splop"), Some(&ForgeValue::from(246)));
-	}
-
-	#[test]
-	fn conditional_blocks() {
-		let tests = [
-			("let value = true; if value { 123 } else { 456 }", Ok(ForgeValue::from(123))),
-			("let value = true; if !value { 123 } else { 456 }", Ok(ForgeValue::from(456))),
-			("let value = 2; if value == 1 { 123 } else if value == 2 { 456 } else { \"Nothing\" }", Ok(ForgeValue::from(456))),
-		];
-
-		for (prog, expected) in tests {
-			let ast = parse_program(prog).expect("Failed to parse");
-			let mut vm = SimpleExecutor::default();
-			assert_eq!(vm.evaluate_program(&ast), expected);
-		}
+		vm.evaluate_program(&ast)
 	}
 
 	#[test]
@@ -328,18 +287,12 @@ mod interpreter_test {
 		assert!(vm.get_variable("bar").is_none());
 	}
 
-	#[test]
-	fn variable_type() {
-		let tests = [
-			("typeof 123", Ok(ForgeValue::from("number"))),
-			(r#"typeof "some string""#, Ok(ForgeValue::from("string"))),
-			("typeof false", Ok(ForgeValue::from("boolean"))),
-		];
-
-		for (prog, expected) in tests {
-			let ast = parse_program(prog).expect("Failed to parse");
-			let mut vm = SimpleExecutor::default();
-			assert_eq!(vm.evaluate_program(&ast), expected);
-		}
+	#[test_case("typeof 123" => Ok(ForgeValue::from("number")); "typeof number")]
+	#[test_case(r#"typeof "some string""# => Ok(ForgeValue::from("string")); "typeof string")]
+	#[test_case("typeof false" => Ok(ForgeValue::from("boolean")); "typeof bool")]
+	fn variable_type(prog: &'static str) -> RuntimeResult<ForgeValue> {
+		let ast = parse_program(prog).expect("Failed to parse program");
+		let mut vm = SimpleExecutor::default();
+		vm.evaluate_program(&ast)
 	}
 }
diff --git a/forge-script-lang/src/runtime/value.rs b/forge-script-lang/src/runtime/value.rs
index 3619e10..39e8030 100644
--- a/forge-script-lang/src/runtime/value.rs
+++ b/forge-script-lang/src/runtime/value.rs
@@ -275,6 +275,37 @@ pub struct UnsupportedOperation {
 	right_type: &'static str,
 }
 
+impl UnsupportedOperation {
+	pub fn mul(left: &'static str, right: &'static str) -> Self {
+		Self {
+			operation: BinaryOp::Multiply,
+			left_type: left,
+			right_type: right,
+		}
+	}
+	pub fn add(left: &'static str, right: &'static str) -> Self {
+		Self {
+			operation: BinaryOp::Add,
+			left_type: left,
+			right_type: right,
+		}
+	}
+	pub fn sub(left: &'static str, right: &'static str) -> Self {
+		Self {
+			operation: BinaryOp::Subtract,
+			left_type: left,
+			right_type: right,
+		}
+	}
+	pub fn div(left: &'static str, right: &'static str) -> Self {
+		Self {
+			operation: BinaryOp::Divide,
+			left_type: left,
+			right_type: right,
+		}
+	}
+}
+
 impl Display for UnsupportedOperation {
 	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
 		write!(
@@ -296,6 +327,11 @@ impl Sub for ForgeValue {
 				left_type: "number",
 				right_type: "non-number",
 			}),
+			(_, ForgeValue::Number(_)) => Err(UnsupportedOperation {
+				operation: BinaryOp::Subtract,
+				left_type: "non-number",
+				right_type: "number",
+			}),
 			(_, _) => Err(UnsupportedOperation {
 				operation: BinaryOp::Subtract,
 				left_type: "non-number",
@@ -316,6 +352,11 @@ impl Div for ForgeValue {
 				left_type: "number",
 				right_type: "non-number",
 			}),
+			(_, ForgeValue::Number(_)) => Err(UnsupportedOperation {
+				operation: BinaryOp::Divide,
+				left_type: "non-number",
+				right_type: "number",
+			}),
 			(_, _) => Err(UnsupportedOperation {
 				operation: BinaryOp::Divide,
 				left_type: "non-number",
@@ -336,6 +377,11 @@ impl Mul for ForgeValue {
 				left_type: "number",
 				right_type: "non-number",
 			}),
+			(_, ForgeValue::Number(_)) => Err(UnsupportedOperation {
+				operation: BinaryOp::Multiply,
+				left_type: "non-number",
+				right_type: "number",
+			}),
 			(_, _) => Err(UnsupportedOperation {
 				operation: BinaryOp::Multiply,
 				left_type: "non-number",
@@ -356,6 +402,11 @@ impl Rem for ForgeValue {
 				left_type: "number",
 				right_type: "non-number",
 			}),
+			(_, ForgeValue::Number(_)) => Err(UnsupportedOperation {
+				operation: BinaryOp::Modulo,
+				left_type: "non-number",
+				right_type: "number",
+			}),
 			(_, _) => Err(UnsupportedOperation {
 				operation: BinaryOp::Modulo,
 				left_type: "non-number",
-- 
GitLab