From 6368b4f8cb245eb36a7bc035d83304ae07af7c65 Mon Sep 17 00:00:00 2001 From: Rajesh Jinaga Date: Tue, 11 Oct 2022 13:06:40 +0530 Subject: [PATCH 1/2] Fix indexer expression as an argument to a function, issue #35, add $str function --- test/Simpleflow.Tests/Functions/StringFunctionsTest.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/Simpleflow.Tests/Functions/StringFunctionsTest.cs b/test/Simpleflow.Tests/Functions/StringFunctionsTest.cs index 6e1b5af..bd20126 100644 --- a/test/Simpleflow.Tests/Functions/StringFunctionsTest.cs +++ b/test/Simpleflow.Tests/Functions/StringFunctionsTest.cs @@ -16,6 +16,10 @@ public void CheckContains() // Arrange var script = @" + let v = $str(value: 2) + let v1 = $str(value: false) + let v2 = $str(value: 'as') + let v3 = $str(value: 2.3) let hasValue = $Contains(input: arg.Text, value: ""here"" ) let hasValue2 = $Contains(input: arg.Text, value: ""no"" ) From 0e092ffc05d409a040fad33de31abd3c6a2bf51c Mon Sep 17 00:00:00 2001 From: Rajesh Jinaga Date: Tue, 11 Oct 2022 13:06:40 +0530 Subject: [PATCH 2/2] Fix indexer expression as an argument to a function, issue #35, add $str function --- .../SimpleflowCodeVisitor.Helpers.cs | 6 +- ...owCodeVisitor.VisitExpression.Predicate.cs | 14 +- .../SimpleflowCodeVisitor.VisitFunction.cs | 5 +- .../SimpleflowCodeVisitor.VisitLiterals.cs | 15 +- src/Simpleflow/FunctionRegister.BuiltIn.cs | 3 + .../Functions/DataTypeConversionFunctions.cs | 10 + src/Simpleflow/Simpleflow.csproj | 4 +- .../Functions/DataTypeConversionFunctions.cs | 196 ++++++++++++++++++ .../Functions/StringFunctionsTest.cs | 186 ++--------------- .../Scripting/PredicateStatementsTest.cs | 42 +++- 10 files changed, 287 insertions(+), 194 deletions(-) create mode 100644 src/Simpleflow/Functions/DataTypeConversionFunctions.cs create mode 100644 test/Simpleflow.Tests/Functions/DataTypeConversionFunctions.cs diff --git a/src/Simpleflow/CodeGenerator/SimpleflowCodeVisitor.Helpers.cs b/src/Simpleflow/CodeGenerator/SimpleflowCodeVisitor.Helpers.cs index 3292482..7f4d9e2 100644 --- a/src/Simpleflow/CodeGenerator/SimpleflowCodeVisitor.Helpers.cs +++ b/src/Simpleflow/CodeGenerator/SimpleflowCodeVisitor.Helpers.cs @@ -27,6 +27,7 @@ private Expression GetNumberExpression(string value, Type targetType) targetType == typeof(decimal) || targetType == typeof(double) || targetType == typeof(float) || + targetType == typeof(object) || targetType == typeof(byte)) { return Expression.Constant(Convert.ChangeType(value, targetType), targetType); @@ -34,9 +35,10 @@ private Expression GetNumberExpression(string value, Type targetType) throw new Exceptions.ValueTypeMismatchException(value, targetType.Name); } - private Expression GetBoolExpression(string value) + private Expression GetBoolExpression(string value, Type targetType) { - return Expression.Constant(Convert.ToBoolean(value), typeof(bool)); + return Expression.Constant(Convert.ToBoolean(value), targetType == null || targetType == typeof(bool) + ? typeof(bool) : targetType ); } private string GetUnquotedEscapeText(string @string) diff --git a/src/Simpleflow/CodeGenerator/SimpleflowCodeVisitor.VisitExpression.Predicate.cs b/src/Simpleflow/CodeGenerator/SimpleflowCodeVisitor.VisitExpression.Predicate.cs index b31aa4e..333ff87 100644 --- a/src/Simpleflow/CodeGenerator/SimpleflowCodeVisitor.VisitExpression.Predicate.cs +++ b/src/Simpleflow/CodeGenerator/SimpleflowCodeVisitor.VisitExpression.Predicate.cs @@ -76,11 +76,21 @@ public override Expression VisitNotExpression([NotNull] SimpleflowParser.NotExpr private Expression InOperatorExpression(Expression left, Expression right) { if (right.Type.GenericTypeArguments.Length == 0 - || right.Type != typeof(List<>).MakeGenericType(right.Type.GenericTypeArguments[0])) + || right.Type.IsAssignableFrom( typeof(IList<>).MakeGenericType(right.Type.GenericTypeArguments[0]))) { throw new Exceptions.SimpleflowException(Resources.Message.InOperatorOnList); } - + + // Try perform automatic conversion TODO Handle nulls + if (right.Type.GenericTypeArguments[0] == typeof(string)) + { + left = ToStringExpression(left); + } + else if (right.Type.GenericTypeArguments[0] != left.Type) + { + left = Expression.Convert(left, right.Type.GenericTypeArguments[0]); + } + return Expression.Call( right, right.Type.GetMethod("Contains", right.Type.GenericTypeArguments), diff --git a/src/Simpleflow/CodeGenerator/SimpleflowCodeVisitor.VisitFunction.cs b/src/Simpleflow/CodeGenerator/SimpleflowCodeVisitor.VisitFunction.cs index de7d16d..25641a7 100644 --- a/src/Simpleflow/CodeGenerator/SimpleflowCodeVisitor.VisitFunction.cs +++ b/src/Simpleflow/CodeGenerator/SimpleflowCodeVisitor.VisitFunction.cs @@ -128,8 +128,9 @@ private void CheckInvalidParameters(ParameterInfo[] actualMethodParameters, Simp private Expression VisitObjectIdentiferAsPerTargetType(SimpleflowParser.ObjectIdentiferExpressionContext objectIdentifier, Type targetType) { var objectIdentieferText = objectIdentifier.GetText(); - - if (objectIdentieferText.Contains(".") + + if ( objectIdentieferText.Contains(".") // accessing property + || objectIdentifier.objectIdentifier()?.identifierIndex()[0]?.index() != null || Variables.Any(v => string.Equals(v.Name, objectIdentieferText, StringComparison.OrdinalIgnoreCase))) { return Visit(objectIdentifier); // regular object identifier used from variables diff --git a/src/Simpleflow/CodeGenerator/SimpleflowCodeVisitor.VisitLiterals.cs b/src/Simpleflow/CodeGenerator/SimpleflowCodeVisitor.VisitLiterals.cs index c540c76..b1b7c50 100644 --- a/src/Simpleflow/CodeGenerator/SimpleflowCodeVisitor.VisitLiterals.cs +++ b/src/Simpleflow/CodeGenerator/SimpleflowCodeVisitor.VisitLiterals.cs @@ -48,9 +48,9 @@ public override Expression VisitBoolLeteral([NotNull] SimpleflowParser.BoolLeter var value = context.GetText(); - if (targetType == null || targetType == typeof(bool)) + if (targetType == null || targetType == typeof(bool) || targetType == typeof(object)) { - return GetBoolExpression(value); + return GetBoolExpression(value, targetType); } throw new ValueTypeMismatchException(value); @@ -62,11 +62,7 @@ public override Expression VisitStringLiteral([NotNull] SimpleflowParser.StringL var value = GetUnquotedEscapeText(context.String().GetText()); - if (targetType == null || targetType == typeof(string)) - { - return Expression.Constant(value); - } - else if (targetType != null && targetType.IsEnum) // Handle enum + if (targetType != null && targetType.IsEnum) // Handle Enum { if (!TryParseEnum(targetType, value, out object result)) { @@ -75,6 +71,11 @@ public override Expression VisitStringLiteral([NotNull] SimpleflowParser.StringL return Expression.Constant(result, targetType); } + if (targetType == null || targetType == typeof(string) || targetType == typeof(object)) + { + return Expression.Constant(value); + } + throw new ValueTypeMismatchException(value); } diff --git a/src/Simpleflow/FunctionRegister.BuiltIn.cs b/src/Simpleflow/FunctionRegister.BuiltIn.cs index 6d466de..11aa335 100644 --- a/src/Simpleflow/FunctionRegister.BuiltIn.cs +++ b/src/Simpleflow/FunctionRegister.BuiltIn.cs @@ -39,6 +39,9 @@ public static FunctionRegister Default .Add("Length", (Func)StringFunctions.Length) .Add("Match", (Func)StringFunctions.Match) .Add("Concat", (Func)StringFunctions.Concat) + + .Add("Str", (Func)DataTypeConversionFunctions.Str) + ; } } diff --git a/src/Simpleflow/Functions/DataTypeConversionFunctions.cs b/src/Simpleflow/Functions/DataTypeConversionFunctions.cs new file mode 100644 index 0000000..5675b4f --- /dev/null +++ b/src/Simpleflow/Functions/DataTypeConversionFunctions.cs @@ -0,0 +1,10 @@ +// Copyright (c) navtech.io. All rights reserved. +// See License in the project root for license information. + +namespace Simpleflow.Functions +{ + internal static class DataTypeConversionFunctions + { + public static string Str(object value) => value?.ToString() ?? string.Empty; + } +} diff --git a/src/Simpleflow/Simpleflow.csproj b/src/Simpleflow/Simpleflow.csproj index cc09abf..ad4438a 100644 --- a/src/Simpleflow/Simpleflow.csproj +++ b/src/Simpleflow/Simpleflow.csproj @@ -2,8 +2,8 @@ net48;netcoreapp3.1;net6.0; PackageIcon.png - 1.0.11 - + 1.0.12 + beta1 README.md diff --git a/test/Simpleflow.Tests/Functions/DataTypeConversionFunctions.cs b/test/Simpleflow.Tests/Functions/DataTypeConversionFunctions.cs new file mode 100644 index 0000000..6e1b5af --- /dev/null +++ b/test/Simpleflow.Tests/Functions/DataTypeConversionFunctions.cs @@ -0,0 +1,196 @@ +// Copyright (c) navtech.io. All rights reserved. +// See License in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Text; +using Xunit; + +namespace Simpleflow.Tests.Functions +{ + public class StringFunctionsTest + { + [Fact] + public void CheckContains() + { + // Arrange + var script = + @" + + let hasValue = $Contains(input: arg.Text, value: ""here"" ) + let hasValue2 = $Contains(input: arg.Text, value: ""no"" ) + + output hasValue + output hasValue2 + "; + + // Act + FlowOutput output = SimpleflowEngine.Run(script, new { Text = "its here" }); + + // Assert + Assert.True((bool)output.Output["hasValue"]); + Assert.False((bool)output.Output["hasValue2"]); + } + + [Fact] + public void CheckStartsWithEndsWith() + { + // Arrange + var script = + @" + + let hasValue = $StartsWith(input: arg.Text, value: ""its"" ) + let hasValue2 = $EndsWith(input: arg.Text, value: ""here"" ) + + output hasValue + output hasValue2 + "; + + // Act + FlowOutput output = SimpleflowEngine.Run(script, new { Text = "its here" }); + + // Assert + Assert.True((bool)output.Output["hasValue"]); + Assert.True((bool)output.Output["hasValue2"]); + } + + [Fact] + public void CheckTrim() + { + // Arrange + var script = + @" + + let trim = $Trim(input: arg.Text, value: "" @"" ) + + output trim + "; + + // Act + FlowOutput output = SimpleflowEngine.Run(script, new { Text = " its here@ " }); + + // Assert + Assert.Equal(expected: "its here", actual: output.Output["trim"]); + } + + [Fact] + public void CheckIndexOf() + { + // Arrange + var script = + @" + + let index = $IndexOf(input: arg.Text, value: ""here"" ) + + output index + "; + + // Act + FlowOutput output = SimpleflowEngine.Run(script, new { Text = " its here@ " }); + + // Assert + Assert.Equal(expected: 5, actual: output.Output["index"]); + } + + [Fact] + public void CheckRegexMatch() + { + // Arrange + var script = + @" + + let matched = $match(input: arg.Text, pattern: ""[0-9]*"" ) + + output matched + "; + + // Act + FlowOutput output = SimpleflowEngine.Run(script, new { Text = " its here 995 " }); + + // Assert + Assert.True((bool)output.Output["matched"]); + } + + [Fact] + public void CheckSubstring() + { + // Arrange + var script = + @" + + let text = $Substring(input: arg.Text, startIndex: 4 ) + let text2 = $Substring(input: arg.Text, startIndex: 4, length: 2 ) + + output text + output text2 + "; + + // Act + FlowOutput output = SimpleflowEngine.Run(script, new { Text = "its here" }); + + // Assert + Assert.Equal(actual: output.Output["text"], expected: "here"); + Assert.Equal(actual: output.Output["text2"], expected: "he"); + + } + + [Fact] + public void CheckStringLength() + { + // Arrange + var script = + @" + + let len = $Length(input: arg.Text) + + output len + "; + + // Act + FlowOutput output = SimpleflowEngine.Run(script, new { Text = "its here" }); + + // Assert + Assert.Equal(actual: output.Output["len"], expected: 8); + } + + [Fact] + public void CheckStringConcat() + { + // Arrange + var script = + @" + + let val = $Concat(value1: arg.Text, value2: "" abc"") + + output val + "; + + // Act + FlowOutput output = SimpleflowEngine.Run(script, new { Text = "its here" }); + + // Assert + Assert.Equal(actual: output.Output["val"], expected: "its here abc"); + } + + [Fact] + public void CheckSubStringAndIndexOf() + { + // Arrange + var script = + @" + + let text = $Substring(input: arg.Text, + startIndex: $IndexOf(input: arg.Text, value: '@') + 1 + ) + output text + "; + + // Act + FlowOutput output = SimpleflowEngine.Run(script, new { Text = " its here@com" }); + + // Assert + Assert.Equal(expected: "com", actual: output.Output["text"]); + } + + } +} diff --git a/test/Simpleflow.Tests/Functions/StringFunctionsTest.cs b/test/Simpleflow.Tests/Functions/StringFunctionsTest.cs index 6e1b5af..239fbb0 100644 --- a/test/Simpleflow.Tests/Functions/StringFunctionsTest.cs +++ b/test/Simpleflow.Tests/Functions/StringFunctionsTest.cs @@ -8,189 +8,33 @@ namespace Simpleflow.Tests.Functions { - public class StringFunctionsTest + public class DataTypeConversionFunctions { [Fact] - public void CheckContains() + public void StrConversion() { // Arrange var script = @" + let v = $str(value: 2) + let v1 = $str(value: false) + let v2 = $str(value: 'as') + let v3 = $str(value: 2.3) - let hasValue = $Contains(input: arg.Text, value: ""here"" ) - let hasValue2 = $Contains(input: arg.Text, value: ""no"" ) - - output hasValue - output hasValue2 - "; - - // Act - FlowOutput output = SimpleflowEngine.Run(script, new { Text = "its here" }); - - // Assert - Assert.True((bool)output.Output["hasValue"]); - Assert.False((bool)output.Output["hasValue2"]); - } - - [Fact] - public void CheckStartsWithEndsWith() - { - // Arrange - var script = - @" - - let hasValue = $StartsWith(input: arg.Text, value: ""its"" ) - let hasValue2 = $EndsWith(input: arg.Text, value: ""here"" ) - - output hasValue - output hasValue2 - "; - - // Act - FlowOutput output = SimpleflowEngine.Run(script, new { Text = "its here" }); - - // Assert - Assert.True((bool)output.Output["hasValue"]); - Assert.True((bool)output.Output["hasValue2"]); - } - - [Fact] - public void CheckTrim() - { - // Arrange - var script = - @" - - let trim = $Trim(input: arg.Text, value: "" @"" ) - - output trim - "; - - // Act - FlowOutput output = SimpleflowEngine.Run(script, new { Text = " its here@ " }); - - // Assert - Assert.Equal(expected: "its here", actual: output.Output["trim"]); - } - - [Fact] - public void CheckIndexOf() - { - // Arrange - var script = - @" - - let index = $IndexOf(input: arg.Text, value: ""here"" ) - - output index - "; - - // Act - FlowOutput output = SimpleflowEngine.Run(script, new { Text = " its here@ " }); - - // Assert - Assert.Equal(expected: 5, actual: output.Output["index"]); - } - - [Fact] - public void CheckRegexMatch() - { - // Arrange - var script = - @" - - let matched = $match(input: arg.Text, pattern: ""[0-9]*"" ) - - output matched + output v + output v1 + output v2 + output v3 "; // Act - FlowOutput output = SimpleflowEngine.Run(script, new { Text = " its here 995 " }); + FlowOutput output = SimpleflowEngine.Run(script, new object()); // Assert - Assert.True((bool)output.Output["matched"]); + Assert.IsType(output.Output["v"]); + Assert.IsType(output.Output["v1"]); + Assert.IsType(output.Output["v2"]); + Assert.IsType(output.Output["v3"]); } - - [Fact] - public void CheckSubstring() - { - // Arrange - var script = - @" - - let text = $Substring(input: arg.Text, startIndex: 4 ) - let text2 = $Substring(input: arg.Text, startIndex: 4, length: 2 ) - - output text - output text2 - "; - - // Act - FlowOutput output = SimpleflowEngine.Run(script, new { Text = "its here" }); - - // Assert - Assert.Equal(actual: output.Output["text"], expected: "here"); - Assert.Equal(actual: output.Output["text2"], expected: "he"); - - } - - [Fact] - public void CheckStringLength() - { - // Arrange - var script = - @" - - let len = $Length(input: arg.Text) - - output len - "; - - // Act - FlowOutput output = SimpleflowEngine.Run(script, new { Text = "its here" }); - - // Assert - Assert.Equal(actual: output.Output["len"], expected: 8); - } - - [Fact] - public void CheckStringConcat() - { - // Arrange - var script = - @" - - let val = $Concat(value1: arg.Text, value2: "" abc"") - - output val - "; - - // Act - FlowOutput output = SimpleflowEngine.Run(script, new { Text = "its here" }); - - // Assert - Assert.Equal(actual: output.Output["val"], expected: "its here abc"); - } - - [Fact] - public void CheckSubStringAndIndexOf() - { - // Arrange - var script = - @" - - let text = $Substring(input: arg.Text, - startIndex: $IndexOf(input: arg.Text, value: '@') + 1 - ) - output text - "; - - // Act - FlowOutput output = SimpleflowEngine.Run(script, new { Text = " its here@com" }); - - // Assert - Assert.Equal(expected: "com", actual: output.Output["text"]); - } - } } diff --git a/test/Simpleflow.Tests/Scripting/PredicateStatementsTest.cs b/test/Simpleflow.Tests/Scripting/PredicateStatementsTest.cs index 6b2986e..16b0db3 100644 --- a/test/Simpleflow.Tests/Scripting/PredicateStatementsTest.cs +++ b/test/Simpleflow.Tests/Scripting/PredicateStatementsTest.cs @@ -21,7 +21,7 @@ public void EqualPredicate() "; // Act - FlowOutput output= SimpleflowEngine.Run(script, new SampleArgument()); + FlowOutput output = SimpleflowEngine.Run(script, new SampleArgument()); // Assert Assert.Equal(actual: output.Messages.Count, expected: 1); @@ -77,11 +77,11 @@ rule when 2 < 5 then Assert.Equal(actual: output.Messages[4], expected: "3!=5"); } - [Fact] + [Fact] public void FunctionInPredicate() { // Arrange - + var script = @" rule when not $match(input: ""abc12"", pattern: ""^[a-zA-z]+$"") then @@ -89,7 +89,7 @@ public void FunctionInPredicate() "; FlowOutput output = SimpleflowEngine.Run(script, new SampleArgument()); - + Assert.Single(output.Errors); } @@ -108,14 +108,21 @@ public void InOperator() rule when not (5 in [2,3]) then message '5notin2,3' + + rule when arg.data in ['new', 'test'] then + message 'arg.data-exists-auto-conversion-object-to-string' "; // Act - FlowOutput output = SimpleflowEngine.Run(script, new object()); + FlowOutput output = SimpleflowEngine.Run(script, new + { + Data = (object)"test" // type casted to object to checking auto conversion feature for 'in' operator + }); // Assert Assert.Equal(actual: output.Messages[0], expected: "5in2,3,5"); Assert.Equal(actual: output.Messages[1], expected: "5notin2,3"); + Assert.Equal(actual: output.Messages[2], expected: "arg.data-exists-auto-conversion-object-to-string"); } [Fact] @@ -165,14 +172,33 @@ public void CheckShortCircuitingAndOperator() message 'got it' "; - FlowOutput output = SimpleflowEngine.Run(script, - new Dictionary { {"test", null } } - , + FlowOutput output = SimpleflowEngine.Run(script, + new Dictionary { { "test", null } } + , new FunctionRegister().Add("exists", (System.Func, string, bool>)Exists)); Assert.Empty(output.Messages); } + + [Fact] + public void CheckShortCircuitingAndOperatorWithFunctions() + { + // Arrange + var script = + @" + rule when $exists(dict: arg, key: 'ContentType') and $str(value: arg['ContentType']) in ['test'] then + message 'got it' + "; + + FlowOutput output = SimpleflowEngine.Run(script, + new Dictionary { { "ContentType", "test" } } + , + new FunctionRegister().Add("exists", (System.Func, string, bool>)Exists)); + + Assert.Equal("got it", output.Messages[0]); + } + public static bool Exists(IDictionary dict, string key) { return dict.ContainsKey(key);