diff --git a/VAR.ExpressionEvaluator.Tests/ParserTests.cs b/VAR.ExpressionEvaluator.Tests/ParserTests.cs index 0690896..75847fb 100644 --- a/VAR.ExpressionEvaluator.Tests/ParserTests.cs +++ b/VAR.ExpressionEvaluator.Tests/ParserTests.cs @@ -401,5 +401,45 @@ namespace VAR.ExpressionEvaluator.Tests #endregion BooleanOps + #region Null value + + + [TestMethod()] + public void NullValue_NullPlusAnything_EqualsNull() + { + Assert.AreEqual(null, Parser.EvaluateString("null + 1")); + Assert.AreEqual(null, Parser.EvaluateString("null + 100")); + Assert.AreEqual(null, Parser.EvaluateString("1 + null")); + Assert.AreEqual(null, Parser.EvaluateString("100 + null")); + Assert.AreEqual(null, Parser.EvaluateString("null + null")); + } + + [TestMethod()] + public void NullValue_NullMinusAnything_EqualsNull() + { + Assert.AreEqual(null, Parser.EvaluateString("null - 1")); + Assert.AreEqual(null, Parser.EvaluateString("null - 100")); + Assert.AreEqual(null, Parser.EvaluateString("1 - null")); + Assert.AreEqual(null, Parser.EvaluateString("100 - null")); + Assert.AreEqual(null, Parser.EvaluateString("null - null")); + } + + [TestMethod()] + public void NullValue_NullByAnything_EqualsNull() + { + Assert.AreEqual(null, Parser.EvaluateString("null * 1")); + Assert.AreEqual(null, Parser.EvaluateString("null * 100")); + Assert.AreEqual(null, Parser.EvaluateString("1 * null")); + Assert.AreEqual(null, Parser.EvaluateString("100 * null")); + Assert.AreEqual(null, Parser.EvaluateString("null * null")); + + Assert.AreEqual(null, Parser.EvaluateString("null / 1")); + Assert.AreEqual(null, Parser.EvaluateString("null / 100")); + Assert.AreEqual(null, Parser.EvaluateString("1 / null")); + Assert.AreEqual(null, Parser.EvaluateString("100 / null")); + Assert.AreEqual(null, Parser.EvaluateString("null / null")); + } + + #endregion } } \ No newline at end of file diff --git a/VAR.ExpressionEvaluator/EvaluationContext.cs b/VAR.ExpressionEvaluator/EvaluationContext.cs index 3ba47ca..b092d82 100644 --- a/VAR.ExpressionEvaluator/EvaluationContext.cs +++ b/VAR.ExpressionEvaluator/EvaluationContext.cs @@ -39,6 +39,11 @@ namespace VAR.ExpressionEvaluator public void SetVariable(string name, object value) { + if (value is DBNull) + { + value = null; + } + if (value is DateTime) { value = ((DateTime)value).ToString("s"); @@ -60,7 +65,7 @@ namespace VAR.ExpressionEvaluator { if (_variables.ContainsKey(name) == false) { - return null; + throw new Exception(string.Format("Variable {0} not found", name)); } return _variables[name]; } diff --git a/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionBooleanAndNode.cs b/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionBooleanAndNode.cs index dca5046..24fa304 100644 --- a/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionBooleanAndNode.cs +++ b/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionBooleanAndNode.cs @@ -9,9 +9,10 @@ private static object BooleanAndOp(object leftValue, object rightValue) { - bool bLeftValue = ExpressionBooleanNode.ConvertToBoolean(leftValue); - bool brightValue = ExpressionBooleanNode.ConvertToBoolean(rightValue); - return bLeftValue && brightValue; + bool? bLeftValue = ExpressionBooleanNode.ConvertToBoolean(leftValue); + bool? brightValue = ExpressionBooleanNode.ConvertToBoolean(rightValue); + if (bLeftValue == null || bLeftValue == null) { return null; } + return bLeftValue.Value && brightValue.Value; } } } diff --git a/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionBooleanNode.cs b/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionBooleanNode.cs index cc4efae..3f3e042 100644 --- a/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionBooleanNode.cs +++ b/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionBooleanNode.cs @@ -14,8 +14,13 @@ return _value; } - public static bool ConvertToBoolean(object value) + public static bool? ConvertToBoolean(object value) { + if (value == null) + { + return null; + } + if (value is bool) { return (bool)value; diff --git a/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionBooleanNotNode.cs b/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionBooleanNotNode.cs index ebb06cb..81b5436 100644 --- a/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionBooleanNotNode.cs +++ b/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionBooleanNotNode.cs @@ -10,6 +10,7 @@ private static object BooleanNotOp(object value) { value = ExpressionBooleanNode.ConvertToBoolean(value); + if (value == null) { return null; } return !(bool)value; } } diff --git a/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionBooleanOrNode.cs b/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionBooleanOrNode.cs index d0127db..2a0682b 100644 --- a/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionBooleanOrNode.cs +++ b/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionBooleanOrNode.cs @@ -9,9 +9,10 @@ private static object BooleanOrOp(object leftValue, object rightValue) { - bool bLeftValue = ExpressionBooleanNode.ConvertToBoolean(leftValue); - bool brightValue = ExpressionBooleanNode.ConvertToBoolean(rightValue); - return bLeftValue || brightValue; + bool? bLeftValue = ExpressionBooleanNode.ConvertToBoolean(leftValue); + bool? brightValue = ExpressionBooleanNode.ConvertToBoolean(rightValue); + if (bLeftValue == null || bLeftValue == null) { return null; } + return bLeftValue.Value || brightValue.Value; } } } diff --git a/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionDivisionNode.cs b/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionDivisionNode.cs index 5385222..62c04a3 100644 --- a/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionDivisionNode.cs +++ b/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionDivisionNode.cs @@ -11,6 +11,11 @@ namespace VAR.ExpressionEvaluator private static object DivisionOp(object leftValue, object rightValue) { + if (leftValue == null || rightValue == null) + { + return null; + } + if (leftValue is string) { if (decimal.TryParse((string)leftValue, out decimal dec) == false) diff --git a/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionEqualsNode.cs b/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionEqualsNode.cs index 3ed90a7..20d0c99 100644 --- a/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionEqualsNode.cs +++ b/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionEqualsNode.cs @@ -11,6 +11,15 @@ namespace VAR.ExpressionEvaluator private static object EqualsOp(object leftValue, object rightValue) { + if (leftValue == null && rightValue == null) + { + return true; + } + if (leftValue == null || rightValue == null) + { + return false; + } + if (leftValue is string && rightValue is string) { return string.Compare((string)leftValue, (string)rightValue) == 0; diff --git a/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionGreaterOrEqualThanNode.cs b/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionGreaterOrEqualThanNode.cs index 7fd26dc..c6f88d2 100644 --- a/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionGreaterOrEqualThanNode.cs +++ b/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionGreaterOrEqualThanNode.cs @@ -11,6 +11,11 @@ namespace VAR.ExpressionEvaluator private static object GreaterOrEqualThanOp(object leftValue, object rightValue) { + if (leftValue == null || rightValue == null) + { + return false; + } + if (leftValue is string && rightValue is string) { return string.Compare((string)leftValue, (string)rightValue) >= 0; diff --git a/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionGreaterThanNode.cs b/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionGreaterThanNode.cs index d7a0fb9..2029c58 100644 --- a/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionGreaterThanNode.cs +++ b/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionGreaterThanNode.cs @@ -11,6 +11,11 @@ namespace VAR.ExpressionEvaluator private static object GreaterThanOp(object leftValue, object rightValue) { + if (leftValue == null || rightValue == null) + { + return false; + } + if (leftValue is string && rightValue is string) { return string.Compare((string)leftValue, (string)rightValue) > 0; diff --git a/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionLessOrEqualThanNode.cs b/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionLessOrEqualThanNode.cs index 8028d70..b644ad1 100644 --- a/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionLessOrEqualThanNode.cs +++ b/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionLessOrEqualThanNode.cs @@ -11,6 +11,11 @@ namespace VAR.ExpressionEvaluator private static object LessOrEqualThanOp(object leftValue, object rightValue) { + if (leftValue == null || rightValue == null) + { + return false; + } + if (leftValue is string && rightValue is string) { return string.Compare((string)leftValue, (string)rightValue) <= 0; diff --git a/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionLessThanNode.cs b/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionLessThanNode.cs index 6d38cbb..9e42daf 100644 --- a/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionLessThanNode.cs +++ b/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionLessThanNode.cs @@ -11,6 +11,11 @@ namespace VAR.ExpressionEvaluator private static object LessThanOp(object leftValue, object rightValue) { + if (leftValue == null || rightValue == null) + { + return false; + } + if (leftValue is string && rightValue is string) { return string.Compare((string)leftValue, (string)rightValue) < 0; diff --git a/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionMinusNode.cs b/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionMinusNode.cs index 76ef5c1..9264bcf 100644 --- a/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionMinusNode.cs +++ b/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionMinusNode.cs @@ -11,6 +11,11 @@ namespace VAR.ExpressionEvaluator private static object MinusOp(object leftValue, object rightValue) { + if (leftValue == null || rightValue == null) + { + return null; + } + if (leftValue is string) { if (decimal.TryParse((string)leftValue, out decimal dec) == false) diff --git a/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionMultiplyNode.cs b/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionMultiplyNode.cs index e581840..b9dbb1f 100644 --- a/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionMultiplyNode.cs +++ b/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionMultiplyNode.cs @@ -11,6 +11,11 @@ namespace VAR.ExpressionEvaluator private static object MultiplyOp(object leftValue, object rightValue) { + if (leftValue == null || rightValue == null) + { + return null; + } + if (leftValue is string) { if (decimal.TryParse((string)leftValue, out decimal dec) == false) diff --git a/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionNotEqualsNode.cs b/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionNotEqualsNode.cs index e1f5b88..0428e12 100644 --- a/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionNotEqualsNode.cs +++ b/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionNotEqualsNode.cs @@ -11,6 +11,15 @@ namespace VAR.ExpressionEvaluator private static object NotEqualsOp(object leftValue, object rightValue) { + if (leftValue == null && rightValue == null) + { + return false; + } + if (leftValue == null || rightValue == null) + { + return true; + } + if (leftValue is string && rightValue is string) { return string.Compare((string)leftValue, (string)rightValue) != 0; diff --git a/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionNullNode.cs b/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionNullNode.cs new file mode 100644 index 0000000..288cc24 --- /dev/null +++ b/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionNullNode.cs @@ -0,0 +1,10 @@ +namespace VAR.ExpressionEvaluator +{ + public class ExpressionNullNode : IExpressionNode + { + public object Eval(IEvaluationContext evaluationContext) + { + return null; + } + } +} diff --git a/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionNumberNegateNode.cs b/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionNumberNegateNode.cs index b76f5eb..cd70aba 100644 --- a/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionNumberNegateNode.cs +++ b/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionNumberNegateNode.cs @@ -11,6 +11,11 @@ namespace VAR.ExpressionEvaluator private static object NumberNegateOp(object value) { + if (value == null) + { + return null; + } + if (value is string) { if (decimal.TryParse((string)value, out decimal dec) == false) diff --git a/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionPlusNode.cs b/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionPlusNode.cs index 91430cc..991bb2a 100644 --- a/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionPlusNode.cs +++ b/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionPlusNode.cs @@ -11,6 +11,11 @@ namespace VAR.ExpressionEvaluator private static object PlusOp(object leftValue, object rightValue) { + if (leftValue == null || rightValue == null) + { + return null; + } + if (leftValue is string || rightValue is string) { return string.Concat(Convert.ToString(leftValue), Convert.ToString(rightValue)); diff --git a/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionVariableNode.cs b/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionVariableNode.cs index 0bb209a..36a6fbf 100644 --- a/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionVariableNode.cs +++ b/VAR.ExpressionEvaluator/ExpressionNodes/ExpressionVariableNode.cs @@ -14,10 +14,6 @@ namespace VAR.ExpressionEvaluator public object Eval(IEvaluationContext evaluationContext) { object value = evaluationContext.GetVariable(_name); - if (value == null) - { - throw new Exception(string.Format("Variable {0} not found", _name)); - } return value; } } diff --git a/VAR.ExpressionEvaluator/Parser.cs b/VAR.ExpressionEvaluator/Parser.cs index 9d9fd31..d6da62e 100644 --- a/VAR.ExpressionEvaluator/Parser.cs +++ b/VAR.ExpressionEvaluator/Parser.cs @@ -197,6 +197,11 @@ namespace VAR.ExpressionEvaluator _tokenizer.NextToken(); return new ExpressionBooleanNode(false); } + if (identifierToLower == "null") + { + _tokenizer.NextToken(); + return new ExpressionNullNode(); + } string identifier = _tokenizer.Text; _tokenizer.NextToken(); diff --git a/VAR.ExpressionEvaluator/VAR.ExpressionEvaluator.csproj b/VAR.ExpressionEvaluator/VAR.ExpressionEvaluator.csproj index f19bedd..51059c2 100644 --- a/VAR.ExpressionEvaluator/VAR.ExpressionEvaluator.csproj +++ b/VAR.ExpressionEvaluator/VAR.ExpressionEvaluator.csproj @@ -55,6 +55,7 @@ +