diff --git a/CodingSeb.ExpressionEvaluator.Tests/ExpressionEvaluatorTests.cs b/CodingSeb.ExpressionEvaluator.Tests/ExpressionEvaluatorTests.cs index d6cf102..423fc78 100644 --- a/CodingSeb.ExpressionEvaluator.Tests/ExpressionEvaluatorTests.cs +++ b/CodingSeb.ExpressionEvaluator.Tests/ExpressionEvaluatorTests.cs @@ -500,6 +500,10 @@ public void TypeTesting(string expression, Type type) [TestCase("null ?? \"Option2\"", TestOf = typeof(string), ExpectedResult = "Option2", Category = "Null Coalescing Operator")] #endregion + #region Null conditional Operator + [TestCase("null?.Trim()", ExpectedResult = null, Category = "Null conditional Operator")] + #endregion + #region default values [TestCase("default(int)", TestOf = typeof(int), ExpectedResult = 0, Category = "default values")] [TestCase("default(bool)", TestOf = typeof(bool), ExpectedResult = false, Category = "default values")] @@ -1075,9 +1079,11 @@ public static IEnumerable TestCasesForWithCustomVariablesExpressio Dictionary onInstanceVariables = new Dictionary() { { "simpleArray", new object[] {2 , "Hello", true} }, + { "simpleArray2", new object[] {2 , " Hello ", true, null } }, { "otherArray", new object[] {2 , "Hello", true, new ClassForTest1() { IntProperty = 18 } } }, { "simpleList", new List() {"Test" ,false, -15, 123.8f} }, { "nullVar", null }, + { "notTrimmedString", " Hello " }, { "simpleInt", 42 }, { "simpleChar", 'n' }, { "simpleLineFeed", '\n' }, @@ -1090,9 +1096,18 @@ public static IEnumerable TestCasesForWithCustomVariablesExpressio yield return new TestCaseData("simpleList?.Count", onInstanceVariables, true).SetCategory("Instance Property,Null Conditional Property").Returns(4); yield return new TestCaseData("nullVar?.Length", onInstanceVariables, true).SetCategory("Instance Property,Null Conditional Property").Returns(null); yield return new TestCaseData("nullVar?.Count", onInstanceVariables, true).SetCategory("Instance Property,Null Conditional Property").Returns(null); + yield return new TestCaseData("nullVar?.Trim()", onInstanceVariables, true).SetCategory("Instance Property,Null Conditional Method").Returns(null); + yield return new TestCaseData("nullVar?.Trim().Length", onInstanceVariables, true).SetCategory("Instance Property,Null Conditional Method").Returns(null); + yield return new TestCaseData("notTrimmedString?.Trim()", onInstanceVariables, true).SetCategory("Instance Property,Null Conditional Method").Returns("Hello"); + yield return new TestCaseData("notTrimmedString?.Trim().Length", onInstanceVariables, true).SetCategory("Instance Property,Null Conditional Method").Returns(5); yield return new TestCaseData("simpleArray?[2]", onInstanceVariables, true).SetCategory("Instance Property,Null Conditional indexing").Returns(true); yield return new TestCaseData("simpleList?[2]", onInstanceVariables, true).SetCategory("Instance Property,Null Conditional indexing").Returns(-15); + yield return new TestCaseData("simpleArray2[1].Trim()", onInstanceVariables, true).SetCategory("Instance Property,Null Conditional indexing").Returns("Hello"); yield return new TestCaseData("nullVar?[2]", onInstanceVariables, true).SetCategory("Instance Property,Null Conditional indexing").Returns(null); + yield return new TestCaseData("nullVar?[1].Trim()", onInstanceVariables, true).SetCategory("Instance Property,Null Conditional indexing").Returns(null); + yield return new TestCaseData("nullVar?[1]?.Trim()", onInstanceVariables, true).SetCategory("Instance Property,Null Conditional indexing").Returns(null); + yield return new TestCaseData("nullVar?[1][3]", onInstanceVariables, true).SetCategory("Instance Property,Null Conditional indexing").Returns(null); + yield return new TestCaseData("simpleArray2?[3]?.Trim()", onInstanceVariables, true).SetCategory("Instance Property,Null Conditional indexing").Returns(null); yield return new TestCaseData("simpleInt.ToString()", onInstanceVariables, true).SetCategory("Instance Method").Returns("42"); yield return new TestCaseData("simpleInt.ToString().Length", onInstanceVariables, true).SetCategory("Instance Method,Instance Property").Returns(2); diff --git a/CodingSeb.ExpressionEvaluator/CodingSeb.ExpressionEvaluator.csproj b/CodingSeb.ExpressionEvaluator/CodingSeb.ExpressionEvaluator.csproj index a89a1c8..635270f 100644 --- a/CodingSeb.ExpressionEvaluator/CodingSeb.ExpressionEvaluator.csproj +++ b/CodingSeb.ExpressionEvaluator/CodingSeb.ExpressionEvaluator.csproj @@ -5,9 +5,9 @@ CodingSeb.ExpressionEvaluator A Simple Math and Pseudo C# Expression Evaluator in One C# File. Can also execute small C# like scripts Copyright © Coding Seb 2017 - 1.4.9.0 - 1.4.9.0 - 1.4.9.0 + 1.4.10.0 + 1.4.10.0 + 1.4.10.0 bin\$(Configuration)\ Coding Seb CodingSeb.ExpressionEvaluator @@ -18,7 +18,7 @@ true https://github.com/codingseb/ExpressionEvaluator/blob/master/Icon.png?raw=true false - * You can now create SubExpression variables that are evaluate when met + * Correction of some bugs linked to nullconditional operator LICENSE.md https://github.com/codingseb/ExpressionEvaluator diff --git a/CodingSeb.ExpressionEvaluator/ExpressionEvaluator.cs b/CodingSeb.ExpressionEvaluator/ExpressionEvaluator.cs index a115bb3..cb0a5a2 100644 --- a/CodingSeb.ExpressionEvaluator/ExpressionEvaluator.cs +++ b/CodingSeb.ExpressionEvaluator/ExpressionEvaluator.cs @@ -1,6 +1,6 @@ /****************************************************************************************************** Title : ExpressionEvaluator (https://github.com/codingseb/ExpressionEvaluator) - Version : 1.4.9.0 + Version : 1.4.10.0 (if last digit (the forth) is not a zero, the version is an intermediate version and can be unstable) Author : Coding Seb @@ -212,28 +212,39 @@ protected enum TryBlockEvaluatedState protected virtual IList RightOperandOnlyOperatorsEvaluationDictionary => rightOperandOnlyOperatorsEvaluationDictionary; protected virtual IList>> OperatorsEvaluations => operatorsEvaluations; + protected static object IndexingOperatorFunc(dynamic left, dynamic right) + { + if (left is NullConditionalNullValue) + return left; + + Type type = ((object)left).GetType(); + + if (left is IDictionary dictionaryLeft) + { + return dictionaryLeft[right]; + } + else if (type.GetMethod("Item", new Type[] { ((object)right).GetType() }) is MethodInfo methodInfo) + { + return methodInfo.Invoke(left, new object[] { right }); + } + + return left[right]; + } + protected static readonly IList>> operatorsEvaluations = new List>>() { new Dictionary>() { - {ExpressionOperator.Indexing, (dynamic left, dynamic right) => + {ExpressionOperator.Indexing, IndexingOperatorFunc}, + {ExpressionOperator.IndexingWithNullConditional, (dynamic left, dynamic right) => { - Type type = ((object)left).GetType(); - - if(left is IDictionary dictionaryLeft) - { - return dictionaryLeft[right]; - } - else if(type.GetMethod("Item", new Type[] { ((object)right).GetType() }) is MethodInfo methodInfo) - { - return methodInfo.Invoke(left, new object[] { right }); - } + if(left == null) + return new NullConditionalNullValue(); - return left[right]; + return IndexingOperatorFunc(left, right); } }, - {ExpressionOperator.IndexingWithNullConditional, (dynamic left, dynamic right) => left is IDictionary dictionaryLeft ? dictionaryLeft[right] : left?[right] }, }, new Dictionary>() { @@ -358,7 +369,6 @@ protected enum TryBlockEvaluatedState return null; } }, - //{ "if", (self, args) => (bool)self.Evaluate(args[0]) ? self.Evaluate(args[1]) : self.Evaluate(args[2]) }, { "in", (self, args) => args.Skip(1).ToList().ConvertAll(self.Evaluate).Contains(self.Evaluate(args[0])) }, { "List", (self, args) => args.ConvertAll(self.Evaluate) }, { "ListOfType", (self, args) => @@ -1787,7 +1797,7 @@ protected virtual bool EvaluateVarOrFunc(string expression, Stack stack, object obj = inObject ? stack.Pop() : Context; object keepObj = obj; Type objType = null; - Type[] inferedGenericsTypes = obj.GetType().GenericTypeArguments; + Type[] inferedGenericsTypes = obj?.GetType().GenericTypeArguments; ValueTypeNestingTrace valueTypeNestingTrace = null; if (obj != null && TypesToBlock.Contains(obj.GetType())) @@ -1799,9 +1809,13 @@ protected virtual bool EvaluateVarOrFunc(string expression, Stack stack, try { - if (varFuncMatch.Groups["nullConditional"].Success && obj == null) + if(obj is NullConditionalNullValue) + { + stack.Push(obj); + } + else if (varFuncMatch.Groups["nullConditional"].Success && obj == null) { - stack.Push(null); + stack.Push(new NullConditionalNullValue()); } else { @@ -1977,9 +1991,13 @@ protected virtual bool EvaluateVarOrFunc(string expression, Stack stack, try { - if (varFuncMatch.Groups["nullConditional"].Success && obj == null) + if (obj is NullConditionalNullValue) { - stack.Push(null); + stack.Push(obj); + } + else if (varFuncMatch.Groups["nullConditional"].Success && obj == null) + { + stack.Push(new NullConditionalNullValue()); } else { @@ -2544,9 +2562,17 @@ protected virtual bool EvaluateIndexing(string expression, Stack stack, throw new Exception($"{bracketCount} ']' character {beVerb} missing in expression : [{expression}]"); } + dynamic left = stack.Pop(); + + if (left is NullConditionalNullValue) + { + stack.Push(left); + return true; + } + dynamic right = Evaluate(innerExp.ToString()); ExpressionOperator op = indexingBeginningMatch.Length == 2 ? ExpressionOperator.IndexingWithNullConditional : ExpressionOperator.Indexing; - dynamic left = stack.Pop(); + if (OptionForceIntegerNumbersEvaluationsAsDoubleByDefault && right is double && Regex.IsMatch(innerExp.ToString(), @"^\d+$")) right = (int)right; @@ -2566,7 +2592,7 @@ protected virtual bool EvaluateIndexing(string expression, Stack stack, throw new ExpressionEvaluatorSyntaxErrorException($"The left part of {exceptionContext} must be a variable, a property or an indexer."); if (op == ExpressionOperator.IndexingWithNullConditional) - throw new ExpressionEvaluatorSyntaxErrorException($"Null coalescing is not usable left to {exceptionContext}"); + throw new ExpressionEvaluatorSyntaxErrorException($"Null conditional is not usable left to {exceptionContext}"); if (postFixOperator) { @@ -2752,6 +2778,7 @@ protected virtual object ProcessStack(Stack stack) List list = stack .Select(e => e is ValueTypeNestingTrace valueTypeNestingTrace ? valueTypeNestingTrace.Value : e) .Select(e => e is SubExpression subExpression ? Evaluate(subExpression.Expression) : e) + .Select(e => e is NullConditionalNullValue ? null : e) .ToList(); OperatorsEvaluations.ToList().ForEach((IDictionary> operatorEvalutationsDict) => @@ -3400,7 +3427,7 @@ protected virtual string GetCodeUntilEndOfStringInterpolation(string subExpr) #endregion - #region Utils private sub classes for parsing and interpretation + #region Utils protected sub classes for parsing and interpretation protected class ValueTypeNestingTrace { @@ -3424,6 +3451,9 @@ public void AssignValue() } } + protected class NullConditionalNullValue + { } + protected class DelegateEncaps { private readonly InternalDelegate lambda;