Click here to Skip to main content
15,886,069 members
Articles / Web Development / HTML

Dynamic Expresso

Rate me:
Please Sign up or sign in to vote.
4.91/5 (44 votes)
18 Jan 2015MIT11 min read 80.4K   935   72  
Generate LambdaExpression or function delegate on the fly without compilation.
using System;
using NUnit.Framework;

namespace DynamicExpresso.UnitTest
{
	[TestFixture]
	public class OperatorsTest
	{
		[Test]
		public void Multiplicative_Operators()
		{
			var target = new Interpreter();

			Assert.AreEqual(2 * 4, target.Eval("2 * 4"));
			Assert.AreEqual(8 / 2, target.Eval("8 / 2"));
			Assert.AreEqual(7 % 3, target.Eval("7 % 3"));
		}

		[Test]
		public void Additive_Operators()
		{
			var target = new Interpreter();

			Assert.AreEqual(45 + 5, target.Eval("45 + 5"));
			Assert.AreEqual(45 - 5, target.Eval("45 - 5"));
			Assert.AreEqual(1.0 - 0.5, target.Eval("1.0 - 0.5"));
		}

		[Test]
		public void Unary_Operators()
		{
			var target = new Interpreter();

			Assert.AreEqual(-45, target.Eval("-45"));
			Assert.AreEqual(5, target.Eval("+5"));
			Assert.AreEqual(false, target.Eval("!true"));
		}

		[Test]
		public void Unary_Cast_Operator()
		{
			var target = new Interpreter();

			var x = 51.5;
			target.SetVariable("x", x);

			Assert.AreEqual((int)x, target.Eval("(int)x"));
			Assert.AreEqual(typeof(int), target.Parse("(int)x").ReturnType);
			Assert.AreEqual(typeof(object), target.Parse("(object)x").ReturnType);
			Assert.AreEqual((double)84 + 9 * 8, target.Eval("(double)84 + 9 *8"));
		}

		[Test]
		public void Numeric_Operators_Priority()
		{
			var target = new Interpreter();

			Assert.AreEqual(8 / 2 + 2, target.Eval("8 / 2 + 2"));
			Assert.AreEqual(8 + 2 / 2, target.Eval("8 + 2 / 2"));
		}

		[Test]
		public void Typeof_Operator()
		{
			var target = new Interpreter();

			Assert.AreEqual(typeof(string), target.Eval("typeof(string)"));
		}

		[Test]
		public void Is_Operator()
		{
			object a = "string value";
			object b = 64;
			var target = new Interpreter()
													.SetVariable("a", a, typeof(object))
													.SetVariable("b", b, typeof(object));

			Assert.AreEqual(a is string, target.Eval("a is string"));
			Assert.AreEqual(typeof(bool), target.Parse("a is string").ReturnType);
			Assert.AreEqual(b is string, target.Eval("b is string"));
			Assert.AreEqual(b is int, target.Eval("b is int"));
		}

		[Test]
		public void As_Operator()
		{
			object a = "string value";
			object b = 64;
			var target = new Interpreter()
													.SetVariable("a", a, typeof(object))
													.SetVariable("b", b, typeof(object));

			Assert.AreEqual(a as string, target.Eval("a as string"));
			Assert.AreEqual(typeof(string), target.Parse("a as string").ReturnType);
			Assert.AreEqual(b as string, target.Eval("b as string"));
			Assert.AreEqual(typeof(string), target.Parse("b as string").ReturnType);
		}

		[Test]
		public void Type_Operators()
		{
			var target = new Interpreter();

			Assert.AreEqual(typeof(string) != typeof(int), target.Eval("typeof(string) != typeof(int)"));
			Assert.AreEqual(typeof(string) == typeof(string), target.Eval("typeof(string) == typeof(string)"));
		}

		[Test]
		public void String_Concatenation()
		{
			var target = new Interpreter();

			Assert.AreEqual("ciao " + "mondo", target.Eval("\"ciao \" + \"mondo\""));
		}

		[Test]
		public void Numeric_Expression()
		{
			var target = new Interpreter();

			Assert.AreEqual(8 / (2 + 2), target.Eval("8 / (2 + 2)"));
			Assert.AreEqual(58 / (2 * (8 + 2)), target.Eval(" 58 / (2 * (8 + 2))"));

			Assert.AreEqual(-(8 / (2 + 2)), target.Eval("-(8 / (2 + 2))"));
			Assert.AreEqual(+(8 / (2 + 2)), target.Eval("+(8 / (2 + 2))"));
		}

		[Test]
		public void Comparison_Operators()
		{
			var target = new Interpreter();

			Assert.IsFalse((bool)target.Eval("0 > 3"));
			Assert.IsFalse((bool)target.Eval("0 >= 3"));
			Assert.IsTrue((bool)target.Eval("3 < 5"));
			Assert.IsTrue((bool)target.Eval("3 <= 5"));
			Assert.IsFalse((bool)target.Eval("5 == 3"));
			Assert.IsTrue((bool)target.Eval("5 == 5"));
			Assert.IsTrue((bool)target.Eval("5 >= 5"));
			Assert.IsTrue((bool)target.Eval("5 <= 5"));
			Assert.IsTrue((bool)target.Eval("5 != 3"));
			Assert.IsTrue((bool)target.Eval("\"dav\" == \"dav\""));
			Assert.IsFalse((bool)target.Eval("\"dav\" == \"jack\""));
		}

		[Test]
		public void Assignment_Operator_Equal()
		{
			var x = new TypeWithProperty();

			var target = new Interpreter()
				.SetVariable("x", x);

			// simple assignment
			target.Eval("x.Property1 = 156");
			Assert.AreEqual(156, x.Property1);

			// assignment without space
			target.Eval("x.Property1=156");
			Assert.AreEqual(156, x.Property1);

			// assignment with many spaces
			target.Eval("x.Property1     =    156");
			Assert.AreEqual(156, x.Property1);

			// assignment should return the assigned value
			var returnValue = target.Eval("x.Property1 = 81");
			Assert.AreEqual(81, x.Property1);
			Assert.AreEqual(x.Property1, returnValue);

			// assignment can be chained
			returnValue = target.Eval("x.Property1 = x.Property2 = 2014");
			Assert.AreEqual(2014, x.Property1);
			Assert.AreEqual(x.Property1, x.Property2);

			// assignment can be nested with other operators
			returnValue = target.Eval("x.Property1 = (486 + 4) * 10");
			Assert.AreEqual(4900, x.Property1);
			Assert.AreEqual(x.Property1, returnValue);

			// right member is not modified
			x.Property2 = 2;
			returnValue = target.Eval("x.Property1 = x.Property2 * 10");
			Assert.AreEqual(20, x.Property1);
			Assert.AreEqual(2, x.Property2);
		}
		class TypeWithProperty { public int Property1 { get; set; } public int Property2 { get; set; } }

		[Test]
		public void Can_assign_a_parameter()
		{
			var target = new Interpreter();

			var result = target.Eval<int>("x = 5", new Parameter("x", 0));

			Assert.AreEqual(5, result);
		}

		[Test]
		public void Cannot_assign_a_variable()
		{
			var target = new Interpreter()
				.SetVariable("x", 0);

			Assert.Throws<ArgumentException>(() => target.Parse("x = 5"));
		}

		[Test]
		public void Assignment_Operators_can_be_disabled()
		{
			var target = new Interpreter()
				.EnableAssignment(AssignmentOperators.None);

			Assert.AreEqual(AssignmentOperators.None, target.AssignmentOperators);

			Assert.Throws<AssignmentOperatorDisabledException>(() => target.Parse("x = 5", new Parameter("x", 0)));
		}

		[Test]
		public void Can_compare_numeric_parameters_of_different_compatible_types()
		{
			var target = new Interpreter();

			double x1 = 5;
			Assert.AreEqual(true, target.Eval("x > 3", new Parameter("x", x1)));
			double x2 = 1;
			Assert.AreEqual(false, target.Eval("x > 3", new Parameter("x", x2)));
			decimal x3 = 5;
			Assert.AreEqual(true, target.Eval("x > 3", new Parameter("x", x3)));
			decimal x4 = 1;
			Assert.AreEqual(false, target.Eval("x > 3", new Parameter("x", x4)));
			int x5 = 1;
			double y1 = 10;
			Assert.AreEqual(true, target.Eval("x < y", new Parameter("x", x5), new Parameter("y", y1)));
			double x6 = 0;
			Assert.AreEqual(true, target.Eval("x == 0", new Parameter("x", x6)));
		}

		[Test]
		public void Can_compare_enum_parameters()
		{
			var target = new Interpreter();

			InterpreterOptions x = InterpreterOptions.CaseInsensitive;
			InterpreterOptions y = InterpreterOptions.CaseInsensitive;

			Assert.AreEqual(x == y, target.Eval("x == y", new Parameter("x", x), new Parameter("y", y)));

			y = InterpreterOptions.CommonTypes;
			Assert.AreEqual(x != y, target.Eval("x != y", new Parameter("x", x), new Parameter("y", y)));
		}

		[Test]
		public void Logical_Operators()
		{
			var target = new Interpreter();

			Assert.IsTrue((bool)target.Eval("0 > 3 || true"));
			Assert.IsFalse((bool)target.Eval("0 > 3 && 4 < 6"));
		}

		[Test]
		public void If_Operators()
		{
			var target = new Interpreter();

			Assert.AreEqual(10 > 3 ? "yes" : "no", target.Eval("10 > 3 ? \"yes\" : \"no\""));
			Assert.AreEqual(10 < 3 ? "yes" : "no", target.Eval("10 < 3 ? \"yes\" : \"no\""));
		}

		[Test]
		[ExpectedException(typeof(ParseException))]
		public void Operator_LessGreater_Is_Not_Supported()
		{
			var target = new Interpreter();

			target.Parse("5 <> 4");
		}

		[Test]
		public void Implicit_conversion_operator_for_lambda()
		{
			var target = new Interpreter()
					.SetVariable("x", new TypeWithImplicitConversion(10));

			var func = target.ParseAsDelegate<Func<int>>("x");
			int val = func();

			Assert.AreEqual(10, val);
		}

		struct TypeWithImplicitConversion
		{
			private int _value;

			public TypeWithImplicitConversion(byte value)
			{
				this._value = value;
			}

			public static implicit operator int(TypeWithImplicitConversion d)
			{
				return d._value;
			}
		}

		[Test]
		public void Can_use_overloaded_operators_on_struct()
		{
			var target = new Interpreter();

			var x = new StructWithOverloadedBinaryOperators(3);
			target.SetVariable("x", x);

			string y = "5";
			Assert.IsFalse(x == y);
			Assert.IsFalse(target.Eval<bool>("x == y", new Parameter("y", y)));

			y = "3";
			Assert.IsTrue(x == y);
			Assert.IsTrue(target.Eval<bool>("x == y", new Parameter("y", y)));

			Assert.IsFalse(target.Eval<bool>("x == \"4\""));
			Assert.IsTrue(target.Eval<bool>("x == \"3\""));

			Assert.IsTrue(!x == "-3");
			Assert.IsTrue(target.Eval<bool>("!x == \"-3\""));

			var z = new StructWithOverloadedBinaryOperators(10);
			Assert.IsTrue((x + z) == "13");
			Assert.IsTrue(target.Eval<bool>("(x + z) == \"13\"", new Parameter("z", z)));
		}

		struct StructWithOverloadedBinaryOperators
		{
			private int _value;

			public StructWithOverloadedBinaryOperators(int value)
			{
				_value = value;
			}

			public static bool operator ==(StructWithOverloadedBinaryOperators instance, string value)
			{
				return instance._value.ToString().Equals(value);
			}

			public static bool operator !=(StructWithOverloadedBinaryOperators instance, string value)
			{
				return !instance._value.ToString().Equals(value);
			}

			public static StructWithOverloadedBinaryOperators operator +(StructWithOverloadedBinaryOperators left, StructWithOverloadedBinaryOperators right)
			{
				return new StructWithOverloadedBinaryOperators(left._value + right._value);
			}

			public static StructWithOverloadedBinaryOperators operator !(StructWithOverloadedBinaryOperators instance)
			{
				return new StructWithOverloadedBinaryOperators(-instance._value);
			}

			public override bool Equals(object obj)
			{
				if (obj == null)
					return false;
				if (obj is StructWithOverloadedBinaryOperators)
				{
					return this._value.Equals(((StructWithOverloadedBinaryOperators)obj)._value);
				}
				return base.Equals(obj);
			}

			public override int GetHashCode()
			{
				return _value.GetHashCode();
			}
		}

		[Test]
		public void Can_use_overloaded_operators_on_class()
		{
			var target = new Interpreter();

			var x = new ClassWithOverloadedBinaryOperators(3);
			target.SetVariable("x", x);

			string y = "5";
			Assert.IsFalse(x == y);
			Assert.IsFalse(target.Eval<bool>("x == y", new Parameter("y", y)));

			y = "3";
			Assert.IsTrue(x == y);
			Assert.IsTrue(target.Eval<bool>("x == y", new Parameter("y", y)));

			Assert.IsFalse(target.Eval<bool>("x == \"4\""));
			Assert.IsTrue(target.Eval<bool>("x == \"3\""));

			Assert.IsTrue(!x == "-3");
			Assert.IsTrue(target.Eval<bool>("!x == \"-3\""));

			var z = new ClassWithOverloadedBinaryOperators(10);
			Assert.IsTrue((x + z) == "13");
			Assert.IsTrue(target.Eval<bool>("(x + z) == \"13\"", new Parameter("z", z)));
		}

		class ClassWithOverloadedBinaryOperators
		{
			private int _value;

			public ClassWithOverloadedBinaryOperators(int value)
			{
				_value = value;
			}

			public static bool operator ==(ClassWithOverloadedBinaryOperators instance, string value)
			{
				return instance._value.ToString().Equals(value);
			}

			public static bool operator !=(ClassWithOverloadedBinaryOperators instance, string value)
			{
				return !instance._value.ToString().Equals(value);
			}

			public static ClassWithOverloadedBinaryOperators operator +(ClassWithOverloadedBinaryOperators left, ClassWithOverloadedBinaryOperators right)
			{
				return new ClassWithOverloadedBinaryOperators(left._value + right._value);
			}

			public static ClassWithOverloadedBinaryOperators operator !(ClassWithOverloadedBinaryOperators instance)
			{
				return new ClassWithOverloadedBinaryOperators(-instance._value);
			}

			public override bool Equals(object obj)
			{
				if (obj == null)
					return false;
				if (obj is ClassWithOverloadedBinaryOperators)
				{
					return this._value.Equals(((ClassWithOverloadedBinaryOperators)obj)._value);
				}
				return base.Equals(obj);
			}

			public override int GetHashCode()
			{
				return _value.GetHashCode();
			}
		}

		[Test]
		[ExpectedException(typeof(InvalidOperationException))]
		public void Throw_an_exception_if_a_custom_type_doesnt_define_equal_operator()
		{
			var target = new Interpreter();

			var x = new TypeWithoutOverloadedBinaryOperators(3);
			target.SetVariable("x", x);

			string y = "5";
			target.Parse("x == y", new Parameter("y", y));
		}

		[Test]
		[ExpectedException(typeof(InvalidOperationException))]
		public void Throw_an_exception_if_a_custom_type_doesnt_define_plus_operator()
		{
			var target = new Interpreter();

			var x = new TypeWithoutOverloadedBinaryOperators(3);
			target.SetVariable("x", x);

			int y = 5;
			target.Parse("x + y", new Parameter("y", y));
		}

		[Test]
		[ExpectedException(typeof(InvalidOperationException))]
		public void Throw_an_exception_if_a_custom_type_doesnt_define_not_operator()
		{
			var target = new Interpreter();

			var x = new TypeWithoutOverloadedBinaryOperators(3);
			target.SetVariable("x", x);

			target.Parse("!x");
		}

		struct TypeWithoutOverloadedBinaryOperators
		{
			public TypeWithoutOverloadedBinaryOperators(int value)
			{
			}
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer
Italy Italy
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions