Skip to content
Browse files

Variables are now bound by name instead of position.

Added new overload for passing variables as named arguments.
Added new overload for getting compiled delegate from the expression.
  • Loading branch information...
1 parent 4f1fccd commit 3a3e05e2db5070981608b5345f2599bcd0538a29 @Giorgi committed Apr 18, 2012
View
22 README.md
@@ -0,0 +1,22 @@
+Math Expression Evaluator is a library for evaluating simple mathematical expressions. It supports simple expressions such as `2.5+5.9`, `17.89-2.47+7.16`, `5/2/2+1.5*3+4.58`, expressions with parentheses `(((9-6/2)*2-4)/2-6-1)/(2+24/(2+4))` and expressions with variables:
+
+``` csharp
+
+var a = 6;
+var b = 4.32m;
+var c = 24.15m;
+Assert.That(engine.Evaluate("(((9-a/2)*2-b)/2-a-1)/(2+c/(2+4))",new { a, b, c}),
+ Is.EqualTo((((9 - a / 2) * 2 - b) / 2 - a - 1) / (2 + c / (2 + 4))));
+```
+It is also possible to specify variables by using named arguments like this:
+
+``` csharp
+
+dynamic dynamicEngine = new ExpressionEvaluator();
+
+var a = 6;
+var b = 4.5m;
+var c = 2.6m;
+Assert.That(dynamicEngine.Evaluate("(c+b)*a", a: 6, b: 4.5, c: 2.6),
+ Is.EqualTo((c + b) * a));
+```
View
42 SimpleExpressionEvaluator.Tests/ExpressionEvaluatorTests.cs
@@ -98,18 +98,48 @@ public void Can_Process_Simple_Variables()
decimal a = 2.6m;
decimal b = 5.7m;
- Assert.That(engine.Evaluate("a", a), Is.EqualTo(a));
- Assert.That(engine.Evaluate("a+a", a), Is.EqualTo(a + a));
- Assert.That(engine.Evaluate("a+b", a, b), Is.EqualTo(a + b));
+ Assert.That(engine.Evaluate("a", new { a }), Is.EqualTo(a));
+ Assert.That(engine.Evaluate("a+a", new { a }), Is.EqualTo(a + a));
+ Assert.That(engine.Evaluate("a+b", new { a, b }), Is.EqualTo(a + b));
}
[Test]
public void Can_Process_Multiple_Variables()
{
var a = 6;
- var b = 4.32m;
- var c = 24.15m;
- Assert.That(engine.Evaluate("(((9-a/2)*2-b)/2-a-1)/(2+c/(2+4))", a, b, c), Is.EqualTo((((9 - a / 2) * 2 - b) / 2 - a - 1) / (2 + c / (2 + 4))));
+ var b = 4.5m;
+ var c = 2.6m;
+ Assert.That(engine.Evaluate("(((9-a/2)*2-b)/2-a-1)/(2+c/(2+4))", new { a, b, c }), Is.EqualTo((((9 - a / 2) * 2 - b) / 2 - a - 1) / (2 + c / (2 + 4))));
+ Assert.That(engine.Evaluate("(c+b)*a", new { a, b, c }), Is.EqualTo((c + b) * a));
+ }
+
+ [Test]
+ public void Can_Pass_Named_Variables()
+ {
+ dynamic dynamicEngine = new ExpressionEvaluator();
+
+ var a = 6;
+ var b = 4.5m;
+ var c = 2.6m;
+
+ Assert.That(dynamicEngine.Evaluate("(c+b)*a", a: 6, b: 4.5, c: 2.6), Is.EqualTo((c + b) * a));
+ }
+
+ [Test]
+ public void Can_Invoke_Expression_Multiple_Times()
+ {
+ var a = 6m;
+ var b = 3.9m;
+ var c = 4.9m;
+
+ var compiled = engine.Compile("(a+b)/(a+c)");
+ Assert.That(compiled(new { a, b, c }), Is.EqualTo((a + b) / (a + c)));
+
+ a = 5.4m;
+ b = -2.4m;
+ c = 7.5m;
+
+ Assert.That(compiled(new { a, b, c }), Is.EqualTo((a + b) / (a + c)));
}
}
}
View
4 SimpleExpressionEvaluator.Tests/SimpleExpressionEvaluator.Tests.csproj
@@ -36,11 +36,7 @@
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
- <Reference Include="System.Xml.Linq" />
- <Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
- <Reference Include="System.Data" />
- <Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="ExpressionEvaluatorTests.cs" />
View
139 SimpleExpressionEvaluator/ExpressionEvaluator.cs
@@ -1,21 +1,80 @@
using System;
using System.Collections.Generic;
+using System.Dynamic;
using System.IO;
+using System.Linq;
using System.Linq.Expressions;
+using System.Reflection;
namespace SimpleExpressionEvaluator
{
- public class ExpressionEvaluator
+ public class ExpressionEvaluator : DynamicObject
{
private readonly Stack<Expression> expressionStack = new Stack<Expression>();
private readonly Stack<char> operatorStack = new Stack<char>();
private readonly List<string> parameters = new List<string>();
- public decimal Evaluate(string expression, params decimal[] arguments)
+
+ public Func<object, decimal> Compile(string expression)
+ {
+ var compiled = Parse(expression);
+
+ Func<object, decimal> result = argument =>
+ {
+ var arguments = ParseArguments(argument);
+ return Execute(compiled, arguments);
+ };
+
+ return result;
+ }
+
+ public decimal Evaluate(string expression, object argument = null)
+ {
+ var arguments = ParseArguments(argument);
+
+ return Evaluate(expression, arguments);
+ }
+
+ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
+ {
+ if ("Evaluate" != binder.Name)
+ {
+ return base.TryInvokeMember(binder, args, out result);
+ }
+
+ if (!(args[0] is string))
+ {
+ throw new ArgumentException("No expression specified for parsing");
+ }
+
+ //args will contain expression and arguments,
+ //ArgumentNames will contain only named arguments
+ if (args.Length != binder.CallInfo.ArgumentNames.Count + 1)
+ {
+ throw new ArgumentException("Argument names missing.");
+ }
+
+ var arguments = new Dictionary<string, decimal>();
+
+ for (int i = 0; i < binder.CallInfo.ArgumentNames.Count; i++)
+ {
+ if (IsNumeric(args[i + 1].GetType()))
+ {
+ arguments.Add(binder.CallInfo.ArgumentNames[i], Convert.ToDecimal(args[i + 1]));
+ }
+ }
+
+ result = Evaluate((string)args[0], arguments);
+
+ return true;
+ }
+
+
+ private Func<decimal[], decimal> Parse(string expression)
{
if (string.IsNullOrWhiteSpace(expression))
{
- return 0;
+ return s => 0;
}
var arrayParameter = Expression.Parameter(typeof(decimal[]), "args");
@@ -85,9 +144,57 @@ public decimal Evaluate(string expression, params decimal[] arguments)
var lambda = Expression.Lambda<Func<decimal[], decimal>>(expressionStack.Pop(), arrayParameter);
var compiled = lambda.Compile();
- return compiled(arguments);
+ return compiled;
+ }
+
+ private Dictionary<string, decimal> ParseArguments(object argument)
+ {
+ if (argument == null)
+ {
+ return new Dictionary<string, decimal>();
+ }
+
+ var argumentType = argument.GetType();
+
+ var properties = argumentType.GetProperties(BindingFlags.Instance | BindingFlags.Public)
+ .Where(p => p.CanRead && IsNumeric(p.PropertyType));
+
+ var arguments = properties.ToDictionary(property => property.Name,
+ property => Convert.ToDecimal(property.GetValue(argument, null)));
+
+ return arguments;
+ }
+
+ private decimal Evaluate(string expression, Dictionary<string, decimal> arguments)
+ {
+ var compiled = Parse(expression);
+
+ return Execute(compiled, arguments);
+ }
+
+ private decimal Execute(Func<decimal[], decimal> compiled, Dictionary<string, decimal> arguments)
+ {
+ arguments = arguments ?? new Dictionary<string, decimal>();
+
+ if (parameters.Count != arguments.Count)
+ {
+ throw new ArgumentException(string.Format("Expression contains {0} parameters but got only {1}",
+ parameters.Count, arguments.Count));
+ }
+
+ var missingParameters = parameters.Where(p => !arguments.ContainsKey(p)).ToList();
+
+ if (missingParameters.Any())
+ {
+ throw new ArgumentException("No values provided for parameters: " + string.Join(",", missingParameters));
+ }
+
+ var values = parameters.Select(parameter => arguments[parameter]).ToArray();
+
+ return compiled(values);
}
+
private void EvaluateWhile(Func<bool> condition)
{
while (condition())
@@ -99,7 +206,6 @@ private void EvaluateWhile(Func<bool> condition)
}
}
-
private Expression ReadOperand(TextReader reader)
{
var operand = string.Empty;
@@ -156,7 +262,28 @@ private Expression ReadParameter(TextReader reader, Expression arrayParameter)
parameters.Add(parameter);
}
- return Expression.ArrayAccess(arrayParameter, Expression.Constant(parameters.IndexOf(parameter)));
+ return Expression.ArrayIndex(arrayParameter, Expression.Constant(parameters.IndexOf(parameter)));
+ }
+
+
+ private bool IsNumeric(Type type)
+ {
+ switch (Type.GetTypeCode(type))
+ {
+ case TypeCode.SByte:
+ case TypeCode.Byte:
+ case TypeCode.Int16:
+ case TypeCode.UInt16:
+ case TypeCode.Int32:
+ case TypeCode.UInt32:
+ case TypeCode.Int64:
+ case TypeCode.UInt64:
+ case TypeCode.Single:
+ case TypeCode.Double:
+ case TypeCode.Decimal:
+ return true;
+ }
+ return false;
}
}
View
4 SimpleExpressionEvaluator/SimpleExpressionEvaluator.csproj
@@ -33,11 +33,7 @@
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
- <Reference Include="System.Xml.Linq" />
- <Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
- <Reference Include="System.Data" />
- <Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="ExpressionEvaluator.cs" />

0 comments on commit 3a3e05e

Please sign in to comment.
Something went wrong with that request. Please try again.