Skip to content

Commit 3dbd6c1

Browse files
spudstuffpitermarx
authored andcommitted
Check for null when comparing using the most precise type. (#16)
* Check for null when comparing using the most precise type. This allows null value parameters to be used in expressions such as "'a string' == null" where null is defined as e.Parameters["null"] = null. * Added AllowNullParameter option to define a null parameter and allow comparison of values to null. Set as an option to not affecting existing use of NCalc. * Added some additional null checks from #3 * Remove .GetType() call from loop, fix typo. * Removed unused overload.
1 parent 6b2e52a commit 3dbd6c1

4 files changed

Lines changed: 101 additions & 5 deletions

File tree

Evaluant.Calculator.Tests/Fixtures.cs

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,81 @@ public void ExpressionShouldEvaluateParameters()
146146
Assert.AreEqual(117.07, e.Evaluate());
147147
}
148148

149+
[Test]
150+
public void ExpressionShouldHandleNullRightParameters()
151+
{
152+
var e = new Expression("'a string' == null", EvaluateOptions.AllowNullParameter);
153+
154+
Assert.AreEqual(false, e.Evaluate());
155+
}
156+
157+
[Test]
158+
public void ExpressionShouldHandleNullLeftParameters()
159+
{
160+
var e = new Expression("null == 'a string'", EvaluateOptions.AllowNullParameter);
161+
162+
Assert.AreEqual(false, e.Evaluate());
163+
}
164+
165+
[Test]
166+
public void ExpressionShouldHandleNullBothParameters()
167+
{
168+
var e = new Expression("null == null", EvaluateOptions.AllowNullParameter);
169+
170+
Assert.AreEqual(true, e.Evaluate());
171+
}
172+
173+
[Test]
174+
public void ShouldCompareNullToNull()
175+
{
176+
var e = new Expression("[x] = null", EvaluateOptions.AllowNullParameter);
177+
178+
e.Parameters["x"] = null;
179+
180+
Assert.AreEqual(true, e.Evaluate());
181+
}
182+
183+
[Test]
184+
public void ShouldCompareNullableToNonNullable()
185+
{
186+
var e = new Expression("[x] = 5", EvaluateOptions.AllowNullParameter);
187+
188+
e.Parameters["x"] = (int?)5;
189+
Assert.AreEqual(true, e.Evaluate());
190+
191+
e.Parameters["x"] = (int?)6;
192+
Assert.AreEqual(false, e.Evaluate());
193+
}
194+
195+
[Test]
196+
public void ShouldCompareNullToString()
197+
{
198+
var e = new Expression("[x] = 'foo'", EvaluateOptions.AllowNullParameter);
199+
200+
e.Parameters["x"] = null;
201+
202+
Assert.AreEqual(false, e.Evaluate());
203+
}
204+
205+
[Test]
206+
public void ExpressionDoesNotDefineNullParameterWithoutNullOption()
207+
{
208+
var e = new Expression("'a string' == null");
209+
210+
var ex = Assert.Throws<ArgumentException>(() => e.Evaluate());
211+
Assert.IsTrue(ex.Message.Contains("Parameter name: null"));
212+
}
213+
214+
[Test]
215+
public void ExpressionThrowsNullReferenceExceptionWithoutNullOption()
216+
{
217+
var e = new Expression("'a string' == null");
218+
219+
e.Parameters["null"] = null;
220+
221+
Assert.Throws<NullReferenceException>(() => e.Evaluate());
222+
}
223+
149224
[Test]
150225
public void ShouldEvaluateConditionnal()
151226
{
@@ -260,7 +335,7 @@ public void ShouldNotLoosePrecision()
260335
}
261336

262337
[Test]
263-
public void ShouldThrowAnExpcetionWhenInvalidNumber()
338+
public void ShouldThrowAnExceptionWhenInvalidNumber()
264339
{
265340
try
266341
{

Evaluant.Calculator/Domain/EvaluationVisitor.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public override void Visit(LogicalExpression expression)
2929
private static readonly Type[] CommonTypes = { typeof(Int64), typeof(Double), typeof(Boolean), typeof(String), typeof(Decimal) };
3030

3131
/// <summary>
32-
/// Gets the the most precise type.
32+
/// Gets the the most precise type of two types.
3333
/// </summary>
3434
/// <param name="a">Type a.</param>
3535
/// <param name="b">Type b.</param>
@@ -44,12 +44,16 @@ private static Type GetMostPreciseType(Type a, Type b)
4444
}
4545
}
4646

47-
return a;
47+
return a ?? b;
4848
}
4949

5050
public int CompareUsingMostPreciseType(object a, object b)
5151
{
52-
Type mpt = GetMostPreciseType(a.GetType(), b.GetType());
52+
Type mpt = options.AllowNullParameter()
53+
// Allow nulls to be compared with other values
54+
? GetMostPreciseType(a?.GetType(), b?.GetType()) ?? typeof(object)
55+
// No breaking changes fallback
56+
: GetMostPreciseType(a.GetType(), b.GetType());
5357
return Comparer.Default.Compare(Convert.ChangeType(a, mpt), Convert.ChangeType(b, mpt));
5458
}
5559

Evaluant.Calculator/EvaluationOption.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,12 @@ public enum EvaluateOptions
2929
//
3030
// Summary:
3131
// When using Round(), if a number is halfway between two others, it is rounded toward the nearest number that is away from zero.
32-
RoundAwayFromZero = 16
32+
RoundAwayFromZero = 16,
33+
34+
//
35+
// Summary:
36+
// Defines a "null" parameter and allows comparison of values to null.
37+
AllowNullParameter = 32
3338
}
3439

3540
internal static class EvaluateOptionsExtensions
@@ -58,5 +63,10 @@ public static bool RoundAwayFromZero(this EvaluateOptions opts)
5863
{
5964
return opts.Has(EvaluateOptions.RoundAwayFromZero);
6065
}
66+
67+
public static bool AllowNullParameter(this EvaluateOptions opts)
68+
{
69+
return opts.Has(EvaluateOptions.AllowNullParameter);
70+
}
6171
}
6272
}

Evaluant.Calculator/Expression.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,13 @@ public object Evaluate()
196196
visitor.EvaluateParameter += EvaluateParameter;
197197
visitor.Parameters = Parameters;
198198

199+
// Add a "null" parameter which returns null if configured to do so
200+
// Configured as an option to ensure no breaking changes for historical use
201+
if (Options.AllowNullParameter() && !visitor.Parameters.ContainsKey("null"))
202+
{
203+
visitor.Parameters["null"] = null;
204+
}
205+
199206
// if array evaluation, execute the same expression multiple times
200207
if (Options.IterateParameters())
201208
{

0 commit comments

Comments
 (0)