Skip to content

Commit 4067ebe

Browse files
committed
Merge branch 'master' of https://github.com/miroiu/string-math
2 parents d907d81 + 23f6cb4 commit 4067ebe

File tree

10 files changed

+78
-33
lines changed

10 files changed

+78
-33
lines changed

.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

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/Parser/SourceText.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace StringMath
66
internal sealed class SourceText : IEnumerator<char>
77
{
88
public string Text { get; }
9-
public int Position { get; private set; }
9+
public int Position { get; set; }
1010
public char Current => Text[Position];
1111
object IEnumerator.Current => Current;
1212

StringMath/Parser/Tokenizer.cs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ namespace StringMath
77
internal sealed partial class Tokenizer
88
{
99
private readonly SourceText _text;
10+
private readonly IMathContext _context;
1011

1112
// Excluded characters for custom operators
1213
private static readonly HashSet<char> _invalidOperatorCharacters = new HashSet<char>
@@ -16,14 +17,17 @@ internal sealed partial class Tokenizer
1617

1718
/// <summary>Creates a new instance of the tokenizer.</summary>
1819
/// <param name="text">The text to tokenize.</param>
19-
public Tokenizer(SourceText text)
20+
/// <param name="context">The math context.</param>
21+
public Tokenizer(SourceText text, IMathContext context)
2022
{
2123
_text = text;
24+
_context = context;
2225
}
2326

2427
/// <summary>Creates a new instance of the tokenizer.</summary>
2528
/// <param name="text">The text to tokenize.</param>
26-
public Tokenizer(string text) : this(new SourceText(text))
29+
/// <param name="context">The math context.</param>
30+
public Tokenizer(string text, IMathContext context) : this(new SourceText(text), context)
2731
{
2832
}
2933

@@ -139,9 +143,30 @@ private string ReadOperator(SourceText stream)
139143
stream.MoveNext();
140144
}
141145

146+
string op = builder.ToString();
147+
if (IsOperator(op))
148+
{
149+
return builder.ToString();
150+
}
151+
152+
for (int i = 0; i < op.Length; i++)
153+
{
154+
var possibleOperator = builder.ToString(0, i);
155+
if (IsOperator(possibleOperator))
156+
{
157+
stream.Position -= op.Length - i;
158+
return possibleOperator;
159+
}
160+
}
161+
142162
return builder.ToString();
143163
}
144164

165+
private bool IsOperator(string text)
166+
{
167+
return _context.IsBinary(text) || _context.IsUnary(text);
168+
}
169+
145170
private string ReadNumber(SourceText stream)
146171
{
147172
StringBuilder builder = new StringBuilder(8);

0 commit comments

Comments
 (0)