Skip to content

Commit c17e068

Browse files
committed
Merge branch 'release/1.1.0'
2 parents 1189aeb + 86715df commit c17e068

18 files changed

+1268
-24
lines changed

Formula.Console/Formula.Console.fsproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
5-
<TargetFramework>netcoreapp2.1</TargetFramework>
5+
<TargetFramework>netcoreapp3.1</TargetFramework>
66
</PropertyGroup>
77

88
<ItemGroup>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//-----------------------------------------------------------------------
2+
// <copyright file="Program.cs" company="Richard Smith">
3+
// Copyright (c) Richard Smith. All rights reserved.
4+
// </copyright>
5+
//-----------------------------------------------------------------------
6+
7+
namespace Formula.Parser.Benchmark
8+
{
9+
using BenchmarkDotNet.Attributes;
10+
using Formula.Parser.Integration;
11+
12+
[MemoryDiagnoser]
13+
//[ShortRunJob]
14+
public class ArithmeticBenchmark
15+
{
16+
private const string Formula = "SUM(SQRT((1 + 2 * [Var1] ^ [Var2]) / 5), SQRT((1 + 2 * [Var1] ^ [Var2]) / 5))";
17+
private readonly MapVariableProvider variableProvider;
18+
private readonly IAstItem<Ast.expr> ast;
19+
private readonly IAstItem<Ast.expr> folded;
20+
private readonly Func<IVariableProvider, IFunctionProvider, double?> expression;
21+
private readonly Func<IVariableProvider, IFunctionProvider, double?> il;
22+
private readonly Func<IVariableProvider, IFunctionProvider, double?> expressionF;
23+
private readonly Func<IVariableProvider, IFunctionProvider, double?> ilF;
24+
25+
public ArithmeticBenchmark()
26+
{
27+
variableProvider = new MapVariableProvider(new Dictionary<string, double>() { { "Var1", 10 }, { "Var2", 3 } });
28+
29+
ast = CsWrapper.ParseFormula(Formula);
30+
expression = CsWrapper.CompileExpression(ast);
31+
il = CsWrapper.ILCompileExpression(ast);
32+
33+
folded = CsWrapper.ConstantFoldExpression(ast);
34+
expressionF = CsWrapper.CompileExpression(folded);
35+
ilF = CsWrapper.ILCompileExpression(folded);
36+
}
37+
38+
[Benchmark]
39+
public double? InterpretText() => CsWrapper.InterpretFormula(Formula, this.variableProvider);
40+
41+
[Benchmark(Baseline = true)]
42+
public double? Interpret() => CsWrapper.InterpretExpression(ast, this.variableProvider);
43+
44+
[Benchmark]
45+
public double? InterpretFolded() => CsWrapper.InterpretExpression(folded, this.variableProvider);
46+
47+
[Benchmark]
48+
public double? ExpressionCompiler() => expression.Invoke(this.variableProvider, DefaultFunctionProvider.Instance);
49+
50+
[Benchmark]
51+
public double? ILCompiler() => il.Invoke(this.variableProvider, DefaultFunctionProvider.Instance);
52+
53+
[Benchmark]
54+
public double? ExpressionFolded() => expressionF.Invoke(this.variableProvider, DefaultFunctionProvider.Instance);
55+
56+
[Benchmark]
57+
public double? ILFolded() => ilF.Invoke(this.variableProvider, DefaultFunctionProvider.Instance);
58+
}
59+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net6.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<ProjectReference Include="..\Formula.Parser\Formula.Parser.fsproj" />
16+
</ItemGroup>
17+
18+
</Project>

Formula.Parser.Benchmark/Program.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//-----------------------------------------------------------------------
2+
// <copyright file="Program.cs" company="Richard Smith">
3+
// Copyright (c) Richard Smith. All rights reserved.
4+
// </copyright>
5+
//-----------------------------------------------------------------------
6+
7+
namespace Formula.Parser.Benchmark
8+
{
9+
using System;
10+
using BenchmarkDotNet.Running;
11+
12+
internal class Program
13+
{
14+
private static void Main(string[] args)
15+
{
16+
var summary = BenchmarkRunner.Run(typeof(Program).Assembly);
17+
}
18+
}
19+
}

Formula.Parser.CsTests/Formula.Parser.CsTests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>netcoreapp2.1</TargetFramework>
4+
<TargetFramework>netcoreapp3.1</TargetFramework>
55

66
<IsPackable>false</IsPackable>
77
</PropertyGroup>

Formula.Parser.CsTests/TestCsWrapper.cs

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,40 @@ public void TestInterpeterDepth()
6666
Assert.AreEqual(1, result);
6767
}
6868

69+
[TestMethod]
70+
public void TestCompilerDepth()
71+
{
72+
#if DEBUG
73+
Console.WriteLine("DEBUG");
74+
#else
75+
Console.WriteLine("RELEASE");
76+
#endif
77+
var depth = 190;
78+
var function = "SQRT(Test)";
79+
var input = new StringBuilder();
80+
81+
for (int i = 0; i < depth; i++)
82+
{
83+
input.Append("(");
84+
}
85+
input.Append($"{function})");
86+
for (int i = 0; i < depth - 1; i++)
87+
{
88+
input.Append($"* {function})");
89+
}
90+
91+
var inputStr = input.ToString();
92+
var compiled = CsWrapper.ILCompileExpression(CsWrapper.ParseFormula(inputStr));
93+
94+
var sw = Stopwatch.StartNew();
95+
var result = compiled.Invoke(new MapVariableProvider(new Dictionary<string, double>() { { "Test", 1 } }), DefaultFunctionProvider.Instance);
96+
sw.Stop();
97+
98+
Console.WriteLine($"Depth: {depth}, Time: {sw.ElapsedMilliseconds}ms");
99+
100+
Assert.AreEqual(1, result);
101+
}
102+
69103
class CustomFunctionProvider: IFunctionProvider
70104
{
71105
class MyFuncImplementation : IFunctionImplementation
@@ -113,6 +147,14 @@ public void TestCustomFunctionProvider()
113147
var result = CsWrapper.InterpretFormula(input, new CustomFunctionProvider());
114148

115149
Assert.AreEqual(42, result);
150+
151+
result = CsWrapper.CompileExpression(CsWrapper.ParseFormula(input)).Invoke(MapVariableProvider.Empty, new CustomFunctionProvider());
152+
153+
Assert.AreEqual(42, result);
154+
155+
result = CsWrapper.ILCompileExpression(CsWrapper.ParseFormula(input)).Invoke(MapVariableProvider.Empty, new CustomFunctionProvider());
156+
157+
Assert.AreEqual(42, result);
116158
}
117159

118160
[TestMethod]
@@ -123,6 +165,14 @@ public void TestCompositeFunctionProvider()
123165
var result = CsWrapper.InterpretFormula(input, new CompositeFunctionProvider(new IFunctionProvider[] {new CustomFunctionProvider(), DefaultFunctionProvider.Instance }));
124166

125167
Assert.AreEqual(84, result);
168+
169+
result = CsWrapper.CompileExpression(CsWrapper.ParseFormula(input)).Invoke(MapVariableProvider.Empty, new CompositeFunctionProvider(new IFunctionProvider[] {new CustomFunctionProvider(), DefaultFunctionProvider.Instance }));
170+
171+
Assert.AreEqual(84, result);
172+
173+
result = CsWrapper.ILCompileExpression(CsWrapper.ParseFormula(input)).Invoke(MapVariableProvider.Empty, new CompositeFunctionProvider(new IFunctionProvider[] {new CustomFunctionProvider(), DefaultFunctionProvider.Instance }));
174+
175+
Assert.AreEqual(84, result);
126176
}
127177

128178
[TestMethod]
@@ -178,5 +228,108 @@ public void TestMixedVariableProvider()
178228

179229
Assert.AreEqual(100.0, result);
180230
}
231+
232+
[TestMethod]
233+
public void TestExpressionDependenciesWithRanges()
234+
{
235+
var input = "42";
236+
237+
var ast = CsWrapper.ParseFormula(input);
238+
var dependencies = CsWrapper.ExtractExpressionDependenciesWithRanges(ast);
239+
240+
Assert.AreEqual(0, dependencies.Count);
241+
242+
input = "[A]";
243+
244+
ast = CsWrapper.ParseFormula(input);
245+
246+
dependencies = CsWrapper.ExtractExpressionDependenciesWithRanges(ast);
247+
248+
Assert.AreEqual(1, dependencies.Count);
249+
var dep = dependencies["A"];
250+
Assert.AreEqual("A", dep.Item1);
251+
Assert.AreEqual(0, dep.Item2);
252+
Assert.AreEqual(0, dep.Item3);
253+
254+
input = "SUM([A]|-1:0|)";
255+
256+
ast = CsWrapper.ParseFormula(input);
257+
258+
dependencies = CsWrapper.ExtractExpressionDependenciesWithRanges(ast);
259+
260+
Assert.AreEqual(1, dependencies.Count);
261+
dep = dependencies["A"];
262+
Assert.AreEqual("A", dep.Item1);
263+
Assert.AreEqual(-1, dep.Item2);
264+
Assert.AreEqual(0, dep.Item3);
265+
266+
input = "SUM([A]|\"ABC\":0|)";
267+
268+
ast = CsWrapper.ParseFormula(input);
269+
270+
dependencies = CsWrapper.ExtractExpressionDependenciesWithRanges(ast);
271+
272+
Assert.AreEqual(1, dependencies.Count);
273+
dep = dependencies["A"];
274+
Assert.AreEqual("A", dep.Item1);
275+
Assert.AreEqual("ABC", dep.Item2);
276+
Assert.AreEqual(0, dep.Item3);
277+
278+
input = "SUM([A]|[B]:0|)";
279+
280+
ast = CsWrapper.ParseFormula(input);
281+
282+
dependencies = CsWrapper.ExtractExpressionDependenciesWithRanges(ast);
283+
284+
Assert.AreEqual(2, dependencies.Count);
285+
dep = dependencies["A"];
286+
Assert.AreEqual("A", dep.Item1);
287+
Assert.AreEqual(null, dep.Item2);
288+
Assert.AreEqual(0, dep.Item3);
289+
290+
input = "SUM([A]|-1:[B]|)";
291+
292+
ast = CsWrapper.ParseFormula(input);
293+
294+
dependencies = CsWrapper.ExtractExpressionDependenciesWithRanges(ast);
295+
296+
Assert.AreEqual(2, dependencies.Count);
297+
dep = dependencies["A"];
298+
Assert.AreEqual("A", dep.Item1);
299+
Assert.AreEqual(-1, dep.Item2);
300+
Assert.AreEqual(null, dep.Item3);
301+
302+
input = "SUM([A]|[C]:[B]|)";
303+
304+
ast = CsWrapper.ParseFormula(input);
305+
306+
dependencies = CsWrapper.ExtractExpressionDependenciesWithRanges(ast);
307+
308+
Assert.AreEqual(3, dependencies.Count);
309+
dep = dependencies["A"];
310+
Assert.AreEqual("A", dep.Item1);
311+
Assert.AreEqual(null, dep.Item2);
312+
Assert.AreEqual(null, dep.Item3);
313+
314+
input = "[A]|42|";
315+
316+
ast = CsWrapper.ParseFormula(input);
317+
dependencies = CsWrapper.ExtractExpressionDependenciesWithRanges(ast);
318+
319+
Assert.AreEqual(1, dependencies.Count);
320+
dep = dependencies["A"];
321+
Assert.AreEqual(42, dep.Item2);
322+
Assert.AreEqual(42, dep.Item3);
323+
324+
input = "[A]|[B]|";
325+
326+
ast = CsWrapper.ParseFormula(input);
327+
dependencies = CsWrapper.ExtractExpressionDependenciesWithRanges(ast);
328+
329+
Assert.AreEqual(2, dependencies.Count);
330+
dep = dependencies["A"];
331+
Assert.AreEqual(null, dep.Item2);
332+
Assert.AreEqual(null, dep.Item3);
333+
}
181334
}
182335
}

Formula.Parser.Tests/CompilerTests.fs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -272,11 +272,11 @@ type CompilerTests () =
272272

273273
[<TestMethod>]
274274
member this.TestCompileFunctionWithParameters () =
275-
let result = parseFormulaString "COUNT(1 + 42, MyVar)"
275+
let result = parseFormulaString "SUM(1 + 42, MyVar)"
276276
match result with
277277
| Success (ast, userState, endPos) ->
278278
let value = (compileFormula ast).Invoke(varMap, DefaultFunctionProvider.Instance)
279-
let expected = Helpers.castToDouble((DefaultFunctionProvider.Instance.Lookup "COUNT").Execute (List.toArray [Number(1.0 + 42.0); varMap.Lookup "MyVar"]))
279+
let expected = Helpers.castToDouble((DefaultFunctionProvider.Instance.Lookup "SUM").Execute (List.toArray [Number(1.0 + 42.0); varMap.Lookup "MyVar"]))
280280
Assert.AreEqual(expected, value);
281281
| Failure (msg, error, userState) ->
282282
Assert.Fail(msg)
@@ -292,13 +292,24 @@ type CompilerTests () =
292292
| Failure (msg, error, userState) ->
293293
Assert.Fail(msg)
294294

295+
[<TestMethod>]
296+
member this.TestCompileFunctionOfFunctionWithRange () =
297+
let result = parseFormulaString "SUM(SUM(1 + 42, MyVar|1:10|), 1)"
298+
match result with
299+
| Success (ast, userState, endPos) ->
300+
let value = (compileFormula ast).Invoke(varMap, DefaultFunctionProvider.Instance)
301+
let expected = Helpers.castToDouble((DefaultFunctionProvider.Instance.Lookup "SUM").Execute (List.toArray [(DefaultFunctionProvider.Instance.Lookup "SUM").Execute (Array.concat [ [| Number(1.0 + 42.0) |]; varMap.LookupRange "MyVar" (Number(1.0)) (Number(10.0))]); Number(1.0)]))
302+
Assert.AreEqual(expected, value);
303+
| Failure (msg, error, userState) ->
304+
Assert.Fail(msg)
305+
295306
[<TestMethod>]
296307
member this.TestCompileFunctionWithRange () =
297-
let result = parseFormulaString "COUNT(1 + 42, MyVar|1:10|)"
308+
let result = parseFormulaString "SUM(1 + 42, MyVar|1:10|)"
298309
match result with
299310
| Success (ast, userState, endPos) ->
300311
let value = (compileFormula ast).Invoke(varMap, DefaultFunctionProvider.Instance)
301-
let expected = Helpers.castToDouble((DefaultFunctionProvider.Instance.Lookup "COUNT").Execute (Array.concat [ [| Number(1.0 + 42.0) |]; varMap.LookupRange "MyVar" (Number(1.0)) (Number(10.0))]))
312+
let expected = Helpers.castToDouble((DefaultFunctionProvider.Instance.Lookup "SUM").Execute (Array.concat [ [| Number(1.0 + 42.0) |]; varMap.LookupRange "MyVar" (Number(1.0)) (Number(10.0))]))
302313
Assert.AreEqual(expected, value);
303314
| Failure (msg, error, userState) ->
304315
Assert.Fail(msg)

Formula.Parser.Tests/Formula.Parser.Tests.fsproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>netcoreapp2.1</TargetFramework>
4+
<TargetFramework>netcoreapp3.1</TargetFramework>
55

66
<IsPackable>false</IsPackable>
77

@@ -16,6 +16,7 @@
1616
<Compile Include="ConstantFolderTests.fs" />
1717
<Compile Include="InterpreterTests.fs" />
1818
<Compile Include="CompilerTests.fs" />
19+
<Compile Include="ILCompilerTests.fs" />
1920
<Compile Include="DefaultFunctionsTests.fs" />
2021
<Compile Include="FinancialFunctionsTests.fs" />
2122
</ItemGroup>

0 commit comments

Comments
 (0)