Skip to content

Commit d0fcb35

Browse files
committed
MathExpr compilation
1 parent 3204dc0 commit d0fcb35

File tree

5 files changed

+167
-8
lines changed

5 files changed

+167
-8
lines changed

README.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
> V3 README can be found here: https://github.com/miroiu/string-math/tree/release-3.0.0
1+
> V3 README can be found here: <https://github.com/miroiu/string-math/tree/release-3.0.0>
22
33
# String Math [![NuGet](https://img.shields.io/nuget/v/StringMath?style=flat-square&logo=nuget)](https://www.nuget.org/packages/StringMath/) [![Downloads](https://img.shields.io/nuget/dt/StringMath?label=downloads&style=flat-square&logo=nuget)](https://www.nuget.org/packages/StringMath) ![.NET](https://img.shields.io/static/v1?label=%20&message=Framework%204.6.1%20to%20NET%206&color=5C2D91&style=flat-square&logo=.net) ![](https://img.shields.io/static/v1?label=%20&message=documentation&color=yellow&style=flat-square)
44

@@ -57,19 +57,26 @@ double result2 = "{PI} + 1".Eval(); // 4.1415926535897931
5757

5858
```csharp
5959
var expr = "{a} + {b} + {PI}".ToMathExpr();
60-
var variables = expr.Variables; // { "a", "b", "PI" }
60+
var variables = expr.Variables; // { "a", "b", "PI" }
6161
var localVariables = expr.LocalVariables; // { "a", "b" }
6262
```
6363

64+
### Compilation
65+
66+
```csharp
67+
Func<double, double> fn = "{a} + 2".ToMathExpr().Compile("a");
68+
double result = fn(5); // 7
69+
```
70+
6471
### Conditional substitution
6572

6673
```csharp
6774
MathExpr expr = "1 / {a}".Substitute("a", 1);
6875

6976
double temp = expr.Result; // 1
7077
71-
if (someCondition) // true
72-
expr.Substitute("a", 2);
78+
if (someCondition) // true
79+
expr.Substitute("a", 2);
7380

7481
double final = expr.Result; // 0.5
7582
```
@@ -88,7 +95,7 @@ double result = "1 + 2 + 3".Eval(expr.Context);
8895
### Custom math context
8996

9097
```csharp
91-
var context = new MathContext(); // new MathContext(MathContext.Default); // to inherit from global
98+
var context = new MathContext(); // new MathContext(MathContext.Default); // to inherit from global
9299
context.RegisterBinary("+", (a, b) => Math.Pow(a, b));
93100

94101
MathExpr expr = new MathExpr("{PI} + 1", context);
Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
namespace StringMath.Tests
55
{
66
[TestFixture]
7-
internal class CalculatorTests
7+
internal class MathExprTests
88
{
99
[SetUp]
1010
public void Setup()
@@ -278,5 +278,46 @@ public void Evaluate_Custom_Context()
278278
double result = "1 + 2 + 3".Eval(context);
279279
Assert.AreEqual(1, result);
280280
}
281+
282+
[Test]
283+
[TestCase("1 + 5", 6)]
284+
[TestCase("1 + -5", -4)]
285+
[TestCase("2 * (abs(-5) + 1)", 12)]
286+
public void Compile(string input, double expected)
287+
{
288+
Func<double> fn = input.ToMathExpr().Compile();
289+
double result = fn();
290+
291+
Assert.AreEqual(expected, result);
292+
}
293+
294+
[Test]
295+
[TestCase("1 + {a}", new[] { "a" }, new[] { 1d }, 2)]
296+
public void Compile1(string input, string[] paramsOrder, double[] paramsValues, double expected)
297+
{
298+
Func<double, double> fn = input.ToMathExpr().Compile(paramsOrder[0]);
299+
double result = fn(paramsValues[0]);
300+
301+
Assert.AreEqual(expected, result);
302+
}
303+
304+
[Test]
305+
[TestCase("(1 + {a}) * {b}", new[] { "b", "a" }, new[] { 2d, 3d }, 8)]
306+
[TestCase("(1 + {b}) * {a}", new[] { "b", "a" }, new[] { 2d, 3d }, 9)]
307+
public void Compile2(string input, string[] paramsOrder, double[] paramsValues, double expected)
308+
{
309+
Func<double, double, double> fn = input.ToMathExpr().Compile(paramsOrder[0], paramsOrder[1]);
310+
double result = fn(paramsValues[0], paramsValues[1]);
311+
312+
Assert.AreEqual(expected, result);
313+
}
314+
315+
[Test]
316+
public void Compile1_Throws_Missing_Variable()
317+
{
318+
MathException ex = Assert.Throws<MathException>(() => "1 + {a}".ToMathExpr().Compile("b"));
319+
320+
Assert.AreEqual(MathException.ErrorCode.UNEXISTING_VARIABLE, ex.Code);
321+
}
281322
}
282323
}

StringMath/MathExpr.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,54 @@ public MathExpr Substitute(string name, double value)
107107
return this;
108108
}
109109

110+
/// <summary>Compiles a <see cref="MathExpr"/> into a delegate.</summary>
111+
/// <returns>A type safe delegate.</returns>
112+
public Func<double> Compile()
113+
{
114+
var exp = new CompileExpression().Compile<Func<IMathContext, double>>(Expression).Compile();
115+
return () => exp(Context);
116+
}
117+
118+
/// <summary>Compiles a <see cref="MathExpr"/> into a delegate.</summary>
119+
/// <returns>A type safe delegate.</returns>
120+
public Func<double, double> Compile(string var)
121+
{
122+
var exp = new CompileExpression().Compile<Func<IMathContext, double, double>>(Expression, var).Compile();
123+
return (double x) => exp(Context, x);
124+
}
125+
126+
/// <summary>Compiles a <see cref="MathExpr"/> into a delegate.</summary>
127+
/// <returns>A type safe delegate.</returns>
128+
public Func<double, double, double> Compile(string var1, string var2)
129+
{
130+
var exp = new CompileExpression().Compile<Func<IMathContext, double, double, double>>(Expression, var1, var2).Compile();
131+
return (x, y) => exp(Context, x, y);
132+
}
133+
134+
/// <summary>Compiles a <see cref="MathExpr"/> into a delegate.</summary>
135+
/// <returns>A type safe delegate.</returns>
136+
public Func<double, double, double, double> Compile(string var1, string var2, string var3)
137+
{
138+
var exp = new CompileExpression().Compile<Func<IMathContext, double, double, double, double>>(Expression, var1, var2, var3).Compile();
139+
return (x, y, z) => exp(Context, x, y, z);
140+
}
141+
142+
/// <summary>Compiles a <see cref="MathExpr"/> into a delegate.</summary>
143+
/// <returns>A type safe delegate.</returns>
144+
public Func<double, double, double, double, double> Compile(string var1, string var2, string var3, string var4)
145+
{
146+
var exp = new CompileExpression().Compile<Func<IMathContext, double, double, double, double, double>>(Expression, var1, var2, var3, var4).Compile();
147+
return (x, y, z, w) => exp(Context, x, y, z, w);
148+
}
149+
150+
/// <summary>Compiles a <see cref="MathExpr"/> into a delegate.</summary>
151+
/// <returns>A type safe delegate.</returns>
152+
public Func<double, double, double, double, double, double> Compile(string var1, string var2, string var3, string var4, string var5)
153+
{
154+
var exp = new CompileExpression().Compile<Func<IMathContext, double, double, double, double, double, double>>(Expression, var1, var2, var3, var4, var5).Compile();
155+
return (x, y, z, w, q) => exp(Context, x, y, z, w, q);
156+
}
157+
110158
/// <summary>Converts a string to a <see cref="MathExpr"/>.</summary>
111159
/// <param name="value">The value to convert.</param>
112160
public static implicit operator MathExpr(string value) => new MathExpr(value);

StringMath/StringMath.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ Supports variables and user defined operators.</Description>
1212
<PackageProjectUrl>https://github.com/miroiu/string-math</PackageProjectUrl>
1313
<RepositoryUrl>https://github.com/miroiu/string-math</RepositoryUrl>
1414
<PackageTags>expression-evaluator calculator string-math math string-calculator user-defined-operators operators custom-operators</PackageTags>
15-
<Version>4.0.0</Version>
16-
<PackageReleaseNotes>Refactored everything and added support for .NET 5 and .NET 6</PackageReleaseNotes>
15+
<Version>4.1.0</Version>
16+
<PackageReleaseNotes>Added math expression compliation</PackageReleaseNotes>
1717
<PackageLicenseFile></PackageLicenseFile>
1818
<GenerateDocumentationFile>true</GenerateDocumentationFile>
1919
<Nullable>enable</Nullable>
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using SM = StringMath.Expressions;
2+
using System;
3+
using System.Linq.Expressions;
4+
using System.Linq;
5+
using System.Collections.Generic;
6+
7+
namespace StringMath
8+
{
9+
internal class CompileExpression
10+
{
11+
private static readonly ParameterExpression _contextParam = Expression.Parameter(typeof(IMathContext), "ctx");
12+
private Dictionary<string, ParameterExpression> _variables = new Dictionary<string, ParameterExpression>();
13+
14+
public Expression<T> Compile<T>(SM.IExpression expression, params string[] variables)
15+
{
16+
_variables = variables.ToDictionary(var => var, var => Expression.Parameter(typeof(double), var));
17+
var args = new List<ParameterExpression>(1 + variables.Length)
18+
{
19+
_contextParam
20+
};
21+
args.AddRange(_variables.Values);
22+
23+
var result = Convert(expression);
24+
return Expression.Lambda<T>(result, args);
25+
}
26+
27+
public Expression Convert(SM.IExpression expression)
28+
{
29+
Expression result = expression switch
30+
{
31+
SM.BinaryExpression binaryExpr => ConvertBinaryExpr(binaryExpr),
32+
SM.ConstantExpression constantExpr => ConvertConstantExpr(constantExpr),
33+
SM.UnaryExpression unaryExpr => ConvertUnaryExpr(unaryExpr),
34+
SM.VariableExpression variableExpr => ConvertVariableExpr(variableExpr),
35+
_ => throw new NotImplementedException($"'{expression?.GetType().Name}' Convertor is not implemented.")
36+
};
37+
38+
return result;
39+
}
40+
41+
public Expression ConvertVariableExpr(SM.VariableExpression variableExpr)
42+
{
43+
if (!_variables.TryGetValue(variableExpr.Name, out var result))
44+
throw MathException.MissingVariable(variableExpr.Name);
45+
46+
return result;
47+
}
48+
49+
public Expression ConvertConstantExpr(SM.ConstantExpression constantExpr) => Expression.Constant(constantExpr.Value);
50+
51+
public Expression ConvertBinaryExpr(SM.BinaryExpression binaryExpr) =>
52+
Expression.Call(_contextParam,
53+
nameof(IMathContext.EvaluateBinary),
54+
null,
55+
Expression.Constant(binaryExpr.OperatorName), Convert(binaryExpr.Left), Convert(binaryExpr.Right));
56+
57+
public Expression ConvertUnaryExpr(SM.UnaryExpression unaryExpr) =>
58+
Expression.Call(_contextParam,
59+
nameof(IMathContext.EvaluateUnary),
60+
null,
61+
Expression.Constant(unaryExpr.OperatorName), Convert(unaryExpr.Operand));
62+
}
63+
}

0 commit comments

Comments
 (0)