Skip to content

Commit 56ea729

Browse files
authored
Merge pull request #18 from miroiu/master
Update to 4.1.3
2 parents f55ccaa + 4067ebe commit 56ea729

13 files changed

Lines changed: 104 additions & 62 deletions

File tree

.github/workflows/dotnet.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ on:
99
jobs:
1010
build:
1111

12-
runs-on: ubuntu-latest
12+
runs-on: windows-latest
1313

1414
steps:
1515
- uses: actions/checkout@v2

.github/workflows/publish.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ on:
88

99
jobs:
1010
publish:
11-
runs-on: ubuntu-latest
11+
runs-on: windows-latest
1212

1313
steps:
1414
- uses: actions/checkout@v2

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
> **NEW!** Boolean math example: https://github.com/miroiu/string-math/pull/6/files
44
5-
# 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)
5+
# 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%208&color=5C2D91&style=flat-square&logo=.net) ![](https://img.shields.io/static/v1?label=%20&message=documentation&color=yellow&style=flat-square)
66

77
Calculates the value of a math expression from a string returning a double.
88
Supports variables, user defined operators and expression compilation.

StringMath.Benchmarks/Benchmarks.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ public class Benchmarks
1010
[Benchmark]
1111
public void Tokenize()
1212
{
13-
var tokenizer = new Tokenizer("1.23235456576878798 - ((3 + {b}) max .1) ^ sqrt(-999 / 2 * 3 max 5) + !5 - 0.00000000002 / {ahghghh}");
13+
var context = MathContext.Default;
14+
15+
var tokenizer = new Tokenizer("1.23235456576878798 - ((3 + {b}) max .1) ^ sqrt(-999 / 2 * 3 max 5) + !5 - 0.00000000002 / {ahghghh}", context);
1416

1517
Token token;
1618

@@ -24,8 +26,9 @@ public void Tokenize()
2426
[Benchmark]
2527
public void Parse()
2628
{
27-
var tokenizer = new Tokenizer("1.23235456576878798 - ((3 + {b}) max .1) ^ sqrt(-999 / 2 * 3 max 5) + !5 - 0.00000000002 / {ahghghh}");
28-
var parser = new Parser(tokenizer, MathContext.Default);
29+
var context = MathContext.Default;
30+
var tokenizer = new Tokenizer("1.23235456576878798 - ((3 + {b}) max .1) ^ sqrt(-999 / 2 * 3 max 5) + !5 - 0.00000000002 / {ahghghh}", context);
31+
var parser = new Parser(tokenizer, context);
2932
_ = parser.Parse();
3033
}
3134

StringMath.Tests/Extensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ namespace StringMath.Tests
44
{
55
static class Extensions
66
{
7-
public static List<Token> ReadAllTokens(this string input)
7+
public static List<Token> ReadAllTokens(this string input, IMathContext context)
88
{
9-
Tokenizer tokenizer = new Tokenizer(input);
9+
Tokenizer tokenizer = new Tokenizer(input, context);
1010
List<Token> tokens = new List<Token>();
1111

1212
Token t;

StringMath.Tests/MathExprTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,11 @@ public void SetOperator_Unary_Should_Not_Overwrite_Global_Operator()
154154
[TestCase("1 + sqrt 4", 3)]
155155
[TestCase("sind(90) + sind 30", 1.5)]
156156
[TestCase("((1 + 1) + ((1 + 1) + (((1) + 1)) + 1))", 7)]
157+
[TestCase("719.04+sin(60)", 718.735d)]
157158
public void Evaluate(string input, double expected)
158159
{
159160
double result = input.Eval();
160-
Assert.AreEqual(expected, result);
161+
Assert.AreEqual(expected, result, 0.001);
161162
}
162163

163164
[TestCase("{b}+3*{a}", 3, 2, 11)]

StringMath.Tests/ParserTests.cs

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,12 @@ public void Setup()
2323
[TestCase("1.15215345346", "1.15215345346")]
2424
[TestCase("0", "0")]
2525
[TestCase("!2", "2!")]
26+
[TestCase("--1", "-(-1)")]
27+
[TestCase("1+sin(3)", "1 + sin(3)")]
28+
[TestCase("1+sin 3", "1 + sin(3)")]
2629
public void ParseMathExpression(string input, string expected)
2730
{
28-
Tokenizer tokenizer = new Tokenizer(input);
31+
Tokenizer tokenizer = new Tokenizer(input, _context);
2932
Parser parser = new Parser(tokenizer, _context);
3033

3134
IExpression result = parser.Parse();
@@ -56,8 +59,9 @@ public void ParseMathExpression(string input, string expected)
5659
[TestCase("1+")]
5760
[TestCase("1.")]
5861
[TestCase("1..1")]
59-
[TestCase("--1")]
62+
[TestCase("-*1")]
6063
[TestCase("-+1")]
64+
[TestCase("+-1")]
6165
[TestCase("{")]
6266
[TestCase("}")]
6367
[TestCase("asd")]
@@ -67,7 +71,7 @@ public void ParseMathExpression(string input, string expected)
6771
[TestCase("1 + 2 1")]
6872
public void ParseBadExpression_Exception(string input)
6973
{
70-
Tokenizer tokenizer = new Tokenizer(input);
74+
Tokenizer tokenizer = new Tokenizer(input, _context);
7175
Parser parser = new Parser(tokenizer, _context);
7276

7377
MathException exception = Assert.Throws<MathException>(() => parser.Parse());
@@ -84,7 +88,7 @@ public void ParseExpression_CustomOperators(string input, string expected)
8488
context.RegisterBinary("pow", (a, b) => a);
8589
context.RegisterUnary("rand", (a) => a);
8690

87-
Tokenizer tokenizer = new Tokenizer(input);
91+
Tokenizer tokenizer = new Tokenizer(input, _context);
8892
Parser parser = new Parser(tokenizer, context);
8993

9094
IExpression result = parser.Parse();
@@ -100,7 +104,7 @@ public void ParseExpression_CustomOperators_Exception(string expected)
100104
{
101105
MathContext context = new MathContext();
102106

103-
Tokenizer tokenizer = new Tokenizer(expected);
107+
Tokenizer tokenizer = new Tokenizer(expected, _context);
104108
Parser parser = new Parser(tokenizer, context);
105109

106110
MathException exception = Assert.Throws<MathException>(() => parser.Parse());
@@ -119,7 +123,7 @@ public void ParseExpression_CustomOperators_Exception(string expected)
119123
[TestCase("{a13}", "a13")]
120124
public void ParseVariableExpression(string expected, string name)
121125
{
122-
Tokenizer tokenizer = new Tokenizer(expected);
126+
Tokenizer tokenizer = new Tokenizer(expected, _context);
123127
Parser parser = new Parser(tokenizer, _context);
124128

125129
IExpression result = parser.Parse();
@@ -139,7 +143,7 @@ public void ParseVariableExpression(string expected, string name)
139143
[TestCase("{-a}")]
140144
public void ParseVariableExpression_Exception(string expected)
141145
{
142-
Tokenizer tokenizer = new Tokenizer(expected);
146+
Tokenizer tokenizer = new Tokenizer(expected, _context);
143147
Parser parser = new Parser(tokenizer, _context);
144148

145149
MathException exception = Assert.Throws<MathException>(() => parser.Parse());
@@ -159,7 +163,7 @@ public void ParseVariableExpression_Exception(string expected)
159163
[TestCase("1 / 2 / 3", "1 / 2 / 3")]
160164
public void ParseBinaryExpression(string input, string expected)
161165
{
162-
Tokenizer tokenizer = new Tokenizer(input);
166+
Tokenizer tokenizer = new Tokenizer(input, _context);
163167
Parser parser = new Parser(tokenizer, _context);
164168

165169
IExpression result = parser.Parse();
@@ -183,7 +187,7 @@ public void ParseBinaryExpression(string input, string expected)
183187
[TestCase("sqrt{a}", "sqrt({a})")]
184188
public void ParseUnaryExpression(string input, string expected)
185189
{
186-
Tokenizer tokenizer = new Tokenizer(input);
190+
Tokenizer tokenizer = new Tokenizer(input, _context);
187191
Parser parser = new Parser(tokenizer, _context);
188192

189193
IExpression result = parser.Parse();
@@ -199,7 +203,7 @@ public void ParseUnaryExpression(string input, string expected)
199203
[TestCase("+5")]
200204
public void ParseUnaryExpression_Exception(string input)
201205
{
202-
Tokenizer tokenizer = new Tokenizer(input);
206+
Tokenizer tokenizer = new Tokenizer(input, _context);
203207
Parser parser = new Parser(tokenizer, _context);
204208

205209
MathException exception = Assert.Throws<MathException>(() => parser.Parse());
@@ -213,7 +217,7 @@ public void ParseUnaryExpression_Exception(string input)
213217
[TestCase("9999999", "9999999")]
214218
public void ParseConstantExpression(string input, string expected)
215219
{
216-
Tokenizer tokenizer = new Tokenizer(input);
220+
Tokenizer tokenizer = new Tokenizer(input, _context);
217221
Parser parser = new Parser(tokenizer, _context);
218222

219223
IExpression result = parser.Parse();
@@ -231,7 +235,7 @@ public void ParseConstantExpression(string input, string expected)
231235
[TestCase("9.01+")]
232236
public void ParseConstantExpression_Exception(string expected)
233237
{
234-
Tokenizer tokenizer = new Tokenizer(expected);
238+
Tokenizer tokenizer = new Tokenizer(expected, _context);
235239
Parser parser = new Parser(tokenizer, _context);
236240

237241
MathException exception = Assert.Throws<MathException>(() => parser.Parse());
@@ -250,7 +254,7 @@ public void ParseConstantExpression_Exception(string expected)
250254
[TestCase("((5 - 2) + ((-1 + 2) * 3))", "5 - 2 + (-1 + 2) * 3")]
251255
public void ParseGroupingExpression(string input, string expected)
252256
{
253-
Tokenizer tokenizer = new Tokenizer(input);
257+
Tokenizer tokenizer = new Tokenizer(input, _context);
254258
Parser parser = new Parser(tokenizer, _context);
255259

256260
IExpression result = parser.Parse();
@@ -271,7 +275,7 @@ public void ParseGroupingExpression(string input, string expected)
271275
[TestCase("({a} + (1 + 2)")]
272276
public void ParseGroupingExpression_Fail(string expected)
273277
{
274-
Tokenizer tokenizer = new Tokenizer(expected);
278+
Tokenizer tokenizer = new Tokenizer(expected, _context);
275279
Parser parser = new Parser(tokenizer, _context);
276280

277281
MathException exception = Assert.Throws<MathException>(() => parser.Parse());

StringMath.Tests/TokenizerTests.cs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,25 @@ namespace StringMath.Tests
77
[TestFixture]
88
internal class TokenizerTests
99
{
10+
private IMathContext _context;
11+
12+
[OneTimeSetUp]
13+
public void Setup()
14+
{
15+
_context = MathContext.Default;
16+
}
17+
1018
[Test]
1119
[TestCase("-1 * 3.5", new[] { TokenType.Operator, TokenType.Number, TokenType.Operator, TokenType.Number })]
1220
[TestCase("2 pow 3", new[] { TokenType.Number, TokenType.Operator, TokenType.Number })]
1321
[TestCase("{a} + 2", new[] { TokenType.Identifier, TokenType.Operator, TokenType.Number })]
1422
[TestCase("(-1) + 2", new[] { TokenType.OpenParen, TokenType.Operator, TokenType.Number, TokenType.CloseParen, TokenType.Operator, TokenType.Number })]
1523
[TestCase("5!", new[] { TokenType.Number, TokenType.Exclamation })]
24+
[TestCase("1+sin(3)", new[] { TokenType.Number, TokenType.Operator, TokenType.Operator, TokenType.OpenParen, TokenType.Number, TokenType.CloseParen })]
25+
[TestCase("1+sin 3", new[] { TokenType.Number, TokenType.Operator, TokenType.Operator, TokenType.Number })]
1626
public void ReadToken(string input, TokenType[] expected)
1727
{
18-
IEnumerable<TokenType> actualTokens = input.ReadAllTokens()
28+
IEnumerable<TokenType> actualTokens = input.ReadAllTokens(_context)
1929
.Where(token => token.Type != TokenType.EndOfCode)
2030
.Select(t => t.Type);
2131
Assert.That(actualTokens, Is.EquivalentTo(expected));
@@ -29,7 +39,7 @@ public void ReadToken(string input, TokenType[] expected)
2939
public void ReadToken_IgnoresWhitespace(string input)
3040
{
3141
// Arrange
32-
Tokenizer tokenizer = new Tokenizer(input);
42+
Tokenizer tokenizer = new Tokenizer(input, _context);
3343

3444
// Act
3545
Token token1 = tokenizer.ReadToken();
@@ -53,7 +63,7 @@ public void ReadToken_IgnoresWhitespace(string input)
5363
public void ReadIdentifier(string input)
5464
{
5565
// Arrange
56-
Tokenizer tokenizer = new Tokenizer(input);
66+
Tokenizer tokenizer = new Tokenizer(input, _context);
5767

5868
// Act
5969
Token token = tokenizer.ReadToken();
@@ -75,7 +85,7 @@ public void ReadIdentifier(string input)
7585
public void ReadIdentifier_Exception(string input)
7686
{
7787
// Arrange
78-
Tokenizer tokenizer = new Tokenizer(input);
88+
Tokenizer tokenizer = new Tokenizer(input, _context);
7989

8090
// Act & Assert
8191
MathException exception = Assert.Throws<MathException>(() => tokenizer.ReadToken());
@@ -91,8 +101,10 @@ public void ReadIdentifier_Exception(string input)
91101
[TestCase("a@a")]
92102
public void ReadOperator(string input)
93103
{
104+
_context.RegisterBinary("**", (a, b) => a * b, Precedence.Multiplication);
105+
94106
// Arrange
95-
Tokenizer tokenizer = new Tokenizer(input);
107+
Tokenizer tokenizer = new Tokenizer(input, _context);
96108

97109
// Act
98110
Token token = tokenizer.ReadToken();
@@ -113,7 +125,7 @@ public void ReadOperator(string input)
113125
public void ReadNumber(string input)
114126
{
115127
// Arrange
116-
Tokenizer tokenizer = new Tokenizer(input);
128+
Tokenizer tokenizer = new Tokenizer(input, _context);
117129

118130
// Act
119131
Token token = tokenizer.ReadToken();
@@ -133,7 +145,7 @@ public void ReadNumber(string input)
133145
public void ReadNumber_Exception(string input)
134146
{
135147
// Arrange
136-
Tokenizer tokenizer = new Tokenizer(input);
148+
Tokenizer tokenizer = new Tokenizer(input, _context);
137149

138150
// Act & Assert
139151
MathException exception = Assert.Throws<MathException>(() => tokenizer.ReadToken());

StringMath/Extensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public static IExpression Parse(this string text, IMathContext context)
4040
{
4141
text.EnsureNotNull(nameof(text));
4242

43-
Tokenizer tokenizer = new Tokenizer(text);
43+
Tokenizer tokenizer = new Tokenizer(text, context);
4444
Parser parser = new Parser(tokenizer, context);
4545
return parser.Parse();
4646
}

StringMath/MathContext.cs

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ public sealed class MathContext : IMathContext
1010
private readonly Dictionary<string, Func<double, double, double>> _binaryEvaluators = new Dictionary<string, Func<double, double, double>>(StringComparer.Ordinal);
1111
private readonly Dictionary<string, Func<double, double>> _unaryEvaluators = new Dictionary<string, Func<double, double>>(StringComparer.Ordinal);
1212
private readonly Dictionary<string, Precedence> _binaryPrecedence = new Dictionary<string, Precedence>(StringComparer.Ordinal);
13-
private readonly HashSet<string> _operators = new HashSet<string>(StringComparer.Ordinal);
1413

1514
/// <summary>The global instance used by <see cref="MathExpr.AddOperator(string, Func{double, double})"/> methods.</summary>
1615
public static readonly IMathContext Default = new MathContext();
@@ -28,25 +27,25 @@ static MathContext()
2827
Default.RegisterBinary("*", (a, b) => a * b, Precedence.Multiplication);
2928
Default.RegisterBinary("/", (a, b) => a / b, Precedence.Multiplication);
3029
Default.RegisterBinary("%", (a, b) => a % b, Precedence.Multiplication);
31-
Default.RegisterBinary("^", (a, b) => Math.Pow(a, b), Precedence.Power);
32-
Default.RegisterBinary("log", (a, b) => Math.Log(a, b), Precedence.Logarithmic);
33-
Default.RegisterBinary("max", (a, b) => Math.Max(a, b), Precedence.UserDefined);
34-
Default.RegisterBinary("min", (a, b) => Math.Min(a, b), Precedence.UserDefined);
30+
Default.RegisterBinary("^", Math.Pow, Precedence.Power);
31+
Default.RegisterBinary("log", Math.Log, Precedence.Logarithmic);
32+
Default.RegisterBinary("max", Math.Max, Precedence.UserDefined);
33+
Default.RegisterBinary("min", Math.Min, Precedence.UserDefined);
3534

3635
Default.RegisterUnary("-", a => -a);
37-
Default.RegisterUnary("!", a => ComputeFactorial(a));
38-
Default.RegisterUnary("sqrt", a => Math.Sqrt(a));
39-
Default.RegisterUnary("sin", a => Math.Sin(a));
40-
Default.RegisterUnary("asin", a => Math.Asin(a));
41-
Default.RegisterUnary("cos", a => Math.Cos(a));
42-
Default.RegisterUnary("acos", a => Math.Acos(a));
43-
Default.RegisterUnary("tan", a => Math.Tan(a));
44-
Default.RegisterUnary("atan", a => Math.Atan(a));
45-
Default.RegisterUnary("ceil", a => Math.Ceiling(a));
46-
Default.RegisterUnary("floor", a => Math.Floor(a));
47-
Default.RegisterUnary("round", a => Math.Round(a));
48-
Default.RegisterUnary("exp", a => Math.Exp(a));
49-
Default.RegisterUnary("abs", a => Math.Abs(a));
36+
Default.RegisterUnary("!", ComputeFactorial);
37+
Default.RegisterUnary("sqrt", Math.Sqrt);
38+
Default.RegisterUnary("sin", Math.Sin);
39+
Default.RegisterUnary("asin", Math.Asin);
40+
Default.RegisterUnary("cos", Math.Cos);
41+
Default.RegisterUnary("acos", Math.Acos);
42+
Default.RegisterUnary("tan", Math.Tan);
43+
Default.RegisterUnary("atan", Math.Atan);
44+
Default.RegisterUnary("ceil", Math.Ceiling);
45+
Default.RegisterUnary("floor", Math.Floor);
46+
Default.RegisterUnary("round", Math.Round);
47+
Default.RegisterUnary("exp", Math.Exp);
48+
Default.RegisterUnary("abs", Math.Abs);
5049
Default.RegisterUnary("rad", a => rad * a);
5150
Default.RegisterUnary("deg", a => deg * a);
5251
}
@@ -70,8 +69,8 @@ public bool IsBinary(string operatorName)
7069
/// <inheritdoc />
7170
public Precedence GetBinaryPrecedence(string operatorName)
7271
{
73-
return _binaryPrecedence.ContainsKey(operatorName)
74-
? _binaryPrecedence[operatorName]
72+
return _binaryPrecedence.TryGetValue(operatorName, out var value)
73+
? value
7574
: Parent?.GetBinaryPrecedence(operatorName)
7675
?? throw MathException.MissingBinaryOperator(operatorName);
7776
}
@@ -84,7 +83,6 @@ public void RegisterBinary(string operatorName, Func<double, double, double> ope
8483

8584
_binaryEvaluators[operatorName] = operation;
8685
_binaryPrecedence[operatorName] = precedence ?? Precedence.UserDefined;
87-
_operators.Add(operatorName);
8886
}
8987

9088
/// <inheritdoc />
@@ -94,14 +92,13 @@ public void RegisterUnary(string operatorName, Func<double, double> operation)
9492
operation.EnsureNotNull(nameof(operation));
9593

9694
_unaryEvaluators[operatorName] = operation;
97-
_operators.Add(operatorName);
9895
}
9996

10097
/// <inheritdoc />
10198
public double EvaluateBinary(string op, double a, double b)
10299
{
103-
double result = _binaryEvaluators.ContainsKey(op)
104-
? _binaryEvaluators[op](a, b)
100+
double result = _binaryEvaluators.TryGetValue(op, out var value)
101+
? value(a, b)
105102
: Parent?.EvaluateBinary(op, a, b)
106103
?? throw MathException.MissingBinaryOperator(op);
107104

@@ -111,8 +108,8 @@ public double EvaluateBinary(string op, double a, double b)
111108
/// <inheritdoc />
112109
public double EvaluateUnary(string op, double a)
113110
{
114-
double result = _unaryEvaluators.ContainsKey(op)
115-
? _unaryEvaluators[op](a)
111+
double result = _unaryEvaluators.TryGetValue(op, out var value)
112+
? value(a)
116113
: Parent?.EvaluateUnary(op, a)
117114
?? throw MathException.MissingUnaryOperator(op);
118115

0 commit comments

Comments
 (0)