From 07a7feb76412604aa908045bad3d757a835760ce Mon Sep 17 00:00:00 2001 From: eugenealeykin Date: Sun, 12 Dec 2021 13:38:43 +0200 Subject: [PATCH 1/9] separation of concerns --- src/DynamicExpresso.Core/Interpreter.cs | 41 ++++++++++++++- src/DynamicExpresso.Core/LambdaExtensions.cs | 29 +++++++++++ src/DynamicExpresso.Core/ParseResult.cs | 55 ++++++++++++++++++++ 3 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 src/DynamicExpresso.Core/LambdaExtensions.cs create mode 100644 src/DynamicExpresso.Core/ParseResult.cs diff --git a/src/DynamicExpresso.Core/Interpreter.cs b/src/DynamicExpresso.Core/Interpreter.cs index b8fdcff0..51e3eb75 100644 --- a/src/DynamicExpresso.Core/Interpreter.cs +++ b/src/DynamicExpresso.Core/Interpreter.cs @@ -130,7 +130,7 @@ public Interpreter SetDefaultNumberType(DefaultNumberType defaultNumberType) } /// - /// Allows to enable/disable assignment operators. + /// Allows to enable/disable assignment operators. /// For security when expression are generated by the users is more safe to disable assignment operators. /// /// @@ -420,7 +420,7 @@ internal LambdaExpression ParseAsExpression(Type delegateType, string expression public Lambda ParseAs(string expressionText, params string[] parametersNames) { - return ParseAs(typeof(TDelegate), expressionText, parametersNames); + return ParseAs(typeof(TDelegate), expressionText, parametersNames); } internal Lambda ParseAs(Type delegateType, string expressionText, params string[] parametersNames) @@ -493,6 +493,43 @@ private Lambda ParseAsLambda(string expressionText, Type expressionType, Paramet var lambda = new Lambda(expression, arguments); +#if TEST_DetectIdentifiers + AssertDetectIdentifiers(lambda); +#endif + + return lambda; + } + + private ParseResult Parse(string expressionText, IEnumerable parameters = null) + { + return Parse(expressionText, typeof(TReturnType), parameters); + } + + private ParseResult Parse(string expressionText, IEnumerable parameters = null) + { + return Parse(expressionText, typeof(void), parameters); + } + + private ParseResult Parse(string expressionText, Type expressionType, IEnumerable parameters = null) + { + if (parameters == null) + parameters = Enumerable.Empty(); + + var arguments = new ParserArguments( + expressionText, + _settings, + expressionType, + parameters.Select(x => new Parameter(x))); + + var expression = Visitors.Aggregate(Parser.Parse(arguments), (current, visitor) => visitor.Visit(current)); + + var lambda = new ParseResult( + expression: expression, + usedParameters: arguments.UsedParameters.Select(x => x.Expression), + declaredParameters: arguments.DeclaredParameters.Select(x => x.Expression), + types: arguments.UsedTypes, + identifiers: arguments.UsedIdentifiers); + #if TEST_DetectIdentifiers AssertDetectIdentifiers(lambda); #endif diff --git a/src/DynamicExpresso.Core/LambdaExtensions.cs b/src/DynamicExpresso.Core/LambdaExtensions.cs new file mode 100644 index 00000000..59f0e06c --- /dev/null +++ b/src/DynamicExpresso.Core/LambdaExtensions.cs @@ -0,0 +1,29 @@ +using System; +using System.Linq; +using System.Linq.Expressions; + +namespace DynamicExpresso +{ + /// + /// Lambda extensions. + /// + public static class LambdaExtensions + { + /// + /// Compiles lambda with declared parameters. + /// + public static Delegate Compile(this ParseResult parseResult) + { + var lambdaExpression = Expression.Lambda(parseResult.Expression, parseResult.DeclaredParameters.ToArray()); + + return lambdaExpression.Compile(); + } + + public static TDelegate Compile(this ParseResult parseResult) + { + var lambdaExpression = Expression.Lambda(parseResult.Expression, parseResult.DeclaredParameters.ToArray()); + + return lambdaExpression.Compile(); + } + } +} diff --git a/src/DynamicExpresso.Core/ParseResult.cs b/src/DynamicExpresso.Core/ParseResult.cs new file mode 100644 index 00000000..5c4659b0 --- /dev/null +++ b/src/DynamicExpresso.Core/ParseResult.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace DynamicExpresso +{ + /// + /// Represents an expression parse result. + /// + public class ParseResult + { + public ParseResult( + Expression expression, + IEnumerable usedParameters, + IEnumerable declaredParameters, + IEnumerable types, + IEnumerable identifiers) + { + Expression = expression; + UsedParameters = usedParameters; + DeclaredParameters = declaredParameters; + Types = types; + Identifiers = identifiers; + } + + /// + /// Gets the parsed expression. + /// + /// The expression. + public Expression Expression { get; } + + /// + /// Gets the parameters actually used in the expression parsed. + /// + /// The used parameters. + public IEnumerable UsedParameters { get; } + + /// + /// Gets the parameters declared when parsing the expression. + /// + /// The declared parameters. + public IEnumerable DeclaredParameters { get; } + + /// + /// Gets the references types in parsed expression. + /// + /// The references types. + public IEnumerable Types { get; } + + /// + /// Gets the identifiers in parsed expression. + /// + /// The identifiers. + public IEnumerable Identifiers { get; } + } +} From 171908fc04f110450199f51e40bd239752adae7c Mon Sep 17 00:00:00 2001 From: eugenealeykin Date: Sun, 12 Dec 2021 22:32:43 +0200 Subject: [PATCH 2/9] Separation of parse and compile --- src/DynamicExpresso.Core/Interpreter.cs | 194 +++--------------- src/DynamicExpresso.Core/LambdaExtensions.cs | 33 +++ src/DynamicExpresso.Core/ParseResult.cs | 12 ++ src/DynamicExpresso.Core/Parsing/Parser.cs | 17 +- .../CollectionHelperTests.cs | 2 +- test/DynamicExpresso.UnitTest/DynamicTest.cs | 26 +-- .../ExpressionTypeTest.cs | 56 ++--- .../GenerateDelegatesTest.cs | 18 +- .../GenerateLambdaTest.cs | 38 ++-- test/DynamicExpresso.UnitTest/GithubIssues.cs | 51 ++--- .../IdentifiersTest.cs | 8 +- .../LambdaExpressionTest.cs | 4 +- test/DynamicExpresso.UnitTest/LiteralsTest.cs | 66 +++--- .../MemberInvocationTest.cs | 4 +- test/DynamicExpresso.UnitTest/NullableTest.cs | 162 ++++----------- .../DynamicExpresso.UnitTest/OperatorsTest.cs | 44 ++-- test/DynamicExpresso.UnitTest/OtherTests.cs | 70 +++---- 17 files changed, 307 insertions(+), 498 deletions(-) diff --git a/src/DynamicExpresso.Core/Interpreter.cs b/src/DynamicExpresso.Core/Interpreter.cs index 51e3eb75..6c718d71 100644 --- a/src/DynamicExpresso.Core/Interpreter.cs +++ b/src/DynamicExpresso.Core/Interpreter.cs @@ -1,11 +1,10 @@ using DynamicExpresso.Parsing; -using DynamicExpresso.Reflection; using DynamicExpresso.Visitors; using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -using DynamicExpresso.Exceptions; +using DynamicExpresso.Reflection; namespace DynamicExpresso { @@ -342,183 +341,37 @@ public Interpreter Reference(ReferenceType type) #endregion #region Parse - /// - /// Parse a text expression and returns a Lambda class that can be used to invoke it. - /// - /// Expression statement - /// - /// - /// - public Lambda Parse(string expressionText, params Parameter[] parameters) - { - return Parse(expressionText, typeof(void), parameters); - } - - /// - /// Parse a text expression and returns a Lambda class that can be used to invoke it. - /// If the expression cannot be converted to the type specified in the expressionType parameter - /// an exception is throw. - /// - /// Expression statement - /// The expected return type. Use void or object type if there isn't an expected return type. - /// - /// - /// - public Lambda Parse(string expressionText, Type expressionType, params Parameter[] parameters) - { - return ParseAsLambda(expressionText, expressionType, parameters); - } - - [Obsolete("Use ParseAsDelegate(string, params string[])")] - public TDelegate Parse(string expressionText, params string[] parametersNames) - { - return ParseAsDelegate(expressionText, parametersNames); - } - - /// - /// Parse a text expression and convert it into a delegate. - /// - /// Delegate to use - /// Expression statement - /// Names of the parameters. If not specified the parameters names defined inside the delegate are used. - /// - /// - public TDelegate ParseAsDelegate(string expressionText, params string[] parametersNames) - { - var lambda = ParseAs(expressionText, parametersNames); - return lambda.Compile(); - } - - /// - /// Parse a text expression and convert it into a lambda expression. - /// - /// Delegate to use - /// Expression statement - /// Names of the parameters. If not specified the parameters names defined inside the delegate are used. - /// - /// - public Expression ParseAsExpression(string expressionText, params string[] parametersNames) - { - var lambda = ParseAs(expressionText, parametersNames); - return lambda.LambdaExpression(); - } - - internal LambdaExpression ParseAsExpression(Type delegateType, string expressionText, params string[] parametersNames) - { - var delegateInfo = ReflectionExtensions.GetDelegateInfo(delegateType, parametersNames); - - // return type is object means that we have no information beforehand - // => we force it to typeof(void) so that no conversion expression is emitted by the parser - // and the actual expression type is preserved - var returnType = delegateInfo.ReturnType; - if (returnType == typeof(object)) - returnType = typeof(void); - - var lambda = ParseAsLambda(expressionText, returnType, delegateInfo.Parameters); - return lambda.LambdaExpression(delegateType); - } - - public Lambda ParseAs(string expressionText, params string[] parametersNames) - { - return ParseAs(typeof(TDelegate), expressionText, parametersNames); - } - - internal Lambda ParseAs(Type delegateType, string expressionText, params string[] parametersNames) - { - var delegateInfo = ReflectionExtensions.GetDelegateInfo(delegateType, parametersNames); - - return ParseAsLambda(expressionText, delegateInfo.ReturnType, delegateInfo.Parameters); - } - #endregion - - #region Eval - /// - /// Parse and invoke the specified expression. - /// - /// - /// - /// - public object Eval(string expressionText, params Parameter[] parameters) - { - return Eval(expressionText, typeof(void), parameters); - } - - /// - /// Parse and invoke the specified expression. - /// - /// - /// - /// - public T Eval(string expressionText, params Parameter[] parameters) - { - return (T)Eval(expressionText, typeof(T), parameters); - } - /// - /// Parse and invoke the specified expression. - /// - /// - /// The return type of the expression. Use void or object if you don't know the expected return type. - /// - /// - public object Eval(string expressionText, Type expressionType, params Parameter[] parameters) + public ParseResult Parse(string expressionText, params string[] parametersNames) { - return Parse(expressionText, expressionType, parameters).Invoke(parameters); - } - #endregion - - #region Detection - public IdentifiersInfo DetectIdentifiers(string expression) - { - var detector = new Detector(_settings); - - return detector.DetectIdentifiers(expression); - } - #endregion - - #region Private methods - - private Lambda ParseAsLambda(string expressionText, Type expressionType, Parameter[] parameters) - { - var arguments = new ParserArguments( - expressionText, - _settings, - expressionType, - parameters); - - var expression = Parser.Parse(arguments); - - foreach (var visitor in Visitors) - expression = visitor.Visit(expression); - - var lambda = new Lambda(expression, arguments); - -#if TEST_DetectIdentifiers - AssertDetectIdentifiers(lambda); -#endif - - return lambda; - } + var delegateInfo = ReflectionExtensions.GetDelegateInfo(typeof(TDelegate), parametersNames); + var parseResult = Parse( + expressionText, + delegateInfo.ReturnType, + delegateInfo.Parameters.Select(x => x.Expression).ToArray()); - private ParseResult Parse(string expressionText, IEnumerable parameters = null) - { - return Parse(expressionText, typeof(TReturnType), parameters); + return new ParseResult( + expression: parseResult.Expression, + usedParameters: parseResult.UsedParameters, + declaredParameters: parseResult.DeclaredParameters, + types: parseResult.Types, + identifiers: parseResult.Identifiers); } - private ParseResult Parse(string expressionText, IEnumerable parameters = null) + public ParseResult Parse(string expressionText, params ParameterExpression[] parameters) { return Parse(expressionText, typeof(void), parameters); } - private ParseResult Parse(string expressionText, Type expressionType, IEnumerable parameters = null) + public ParseResult Parse(string expressionText, Type expressionReturnType, params ParameterExpression[] parameters) { if (parameters == null) - parameters = Enumerable.Empty(); + parameters = new ParameterExpression[0]; var arguments = new ParserArguments( expressionText, _settings, - expressionType, + expressionReturnType, parameters.Select(x => new Parameter(x))); var expression = Visitors.Aggregate(Parser.Parse(arguments), (current, visitor) => visitor.Visit(current)); @@ -537,6 +390,19 @@ private ParseResult Parse(string expressionText, Type expressionType, IEnumerabl return lambda; } + #endregion + + #region Detection + public IdentifiersInfo DetectIdentifiers(string expression) + { + var detector = new Detector(_settings); + + return detector.DetectIdentifiers(expression); + } + #endregion + + #region Private methods + #if TEST_DetectIdentifiers private void AssertDetectIdentifiers(Lambda lambda) { diff --git a/src/DynamicExpresso.Core/LambdaExtensions.cs b/src/DynamicExpresso.Core/LambdaExtensions.cs index 59f0e06c..429fc1d6 100644 --- a/src/DynamicExpresso.Core/LambdaExtensions.cs +++ b/src/DynamicExpresso.Core/LambdaExtensions.cs @@ -25,5 +25,38 @@ public static TDelegate Compile(this ParseResult parseResult) return lambdaExpression.Compile(); } + + public static TDelegate Compile(this ParseResult parseResult) + { + return Compile((ParseResult) parseResult); + } + + public static Expression AsExpression(this ParseResult parseResult) + { + return Expression.Lambda(parseResult.Expression, parseResult.DeclaredParameters.ToArray()); + } + + public static Expression AsExpression(this ParseResult parseResult) + { + return ((ParseResult) parseResult).AsExpression(); + } + + public static LambdaExpression AsLambdaExpression(this ParseResult parseResult, Type delegateType) + { + return Expression.Lambda(delegateType, parseResult.Expression, parseResult.DeclaredParameters.ToArray()); + } + + public static object Eval(this Interpreter interpreter, string expression, params Parameter[] args) + { + return interpreter + .Parse(expression, args.Select(x => x.Expression).ToArray()) + .Compile() + .DynamicInvoke(args.Select(x => x.Value).ToArray()); + } + + public static TReturnType Eval(this Interpreter interpreter, string expression, params Parameter[] args) + { + return (TReturnType) Eval(interpreter, expression, args); + } } } diff --git a/src/DynamicExpresso.Core/ParseResult.cs b/src/DynamicExpresso.Core/ParseResult.cs index 5c4659b0..033ea456 100644 --- a/src/DynamicExpresso.Core/ParseResult.cs +++ b/src/DynamicExpresso.Core/ParseResult.cs @@ -52,4 +52,16 @@ public ParseResult( /// The identifiers. public IEnumerable Identifiers { get; } } + + public class ParseResult : ParseResult + { + public ParseResult( + Expression expression, + IEnumerable usedParameters, + IEnumerable declaredParameters, + IEnumerable types, + IEnumerable identifiers) : base(expression, usedParameters, declaredParameters, types, identifiers) + { + } + } } diff --git a/src/DynamicExpresso.Core/Parsing/Parser.cs b/src/DynamicExpresso.Core/Parsing/Parser.cs index a76e1ab2..614fb6e9 100644 --- a/src/DynamicExpresso.Core/Parsing/Parser.cs +++ b/src/DynamicExpresso.Core/Parsing/Parser.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; -using System.Runtime.CompilerServices; using System.Text; using DynamicExpresso.Exceptions; using DynamicExpresso.Resources; @@ -965,7 +964,7 @@ private Expression ParseIdentifier() { return ParseTypeKeyword(knownType); } - + // Working context implementation //if (it != null) // return ParseMemberAccess(null, it); @@ -1196,7 +1195,7 @@ private Type ParseKnownType() private bool TryParseKnownType(string name, out Type type) { - // if the type is unknown, we need to restart parsing + // if the type is unknown, we need to restart parsing var originalPos = _token.pos; _arguments.TryGetKnownType(name, out type); @@ -2290,7 +2289,7 @@ private static bool IsWritable(Expression expression) } case ExpressionType.Parameter: return true; - + } return false; @@ -2425,7 +2424,7 @@ private Expression GenerateGreaterThan(Expression left, Expression right) return GenerateBinary(ExpressionType.GreaterThan, left, right); } - + private Expression GenerateGreaterThanEqual(Expression left, Expression right) { @@ -3012,7 +3011,7 @@ private static Expression GenerateNullableTypeConversion(Expression expr) } /// - /// Expression that wraps over an interpreter. This is used when parsing a lambda expression + /// Expression that wraps over an interpreter. This is used when parsing a lambda expression /// definition, because we don't know the parameters type before resolution. /// private class InterpreterExpression : Expression @@ -3051,9 +3050,9 @@ public override Type Type internal LambdaExpression EvalAs(Type delegateType) { - var lambdaExpr = _interpreter.ParseAsExpression(delegateType, _expressionText, _parameters.Select(p => p.Name).ToArray()); - _type = lambdaExpr.Type; - return lambdaExpr; + var parsed = _interpreter.Parse(_expressionText, delegateType, _parameters.Select(p => p.Expression).ToArray()); + _type = parsed.Expression.Type; + return parsed.AsLambdaExpression(delegateType); } internal bool IsCompatibleWithDelegate(Type target) diff --git a/test/DynamicExpresso.UnitTest/CollectionHelperTests.cs b/test/DynamicExpresso.UnitTest/CollectionHelperTests.cs index e97f538a..601ddf32 100644 --- a/test/DynamicExpresso.UnitTest/CollectionHelperTests.cs +++ b/test/DynamicExpresso.UnitTest/CollectionHelperTests.cs @@ -36,7 +36,7 @@ public CollectionHelper() public IEnumerable Where(IEnumerable values, string expression) { - var predicate = _interpreter.ParseAsDelegate>(expression, "x"); + var predicate = _interpreter.Parse>(expression, "x").Compile(); return values.Where(predicate); } diff --git a/test/DynamicExpresso.UnitTest/DynamicTest.cs b/test/DynamicExpresso.UnitTest/DynamicTest.cs index e567fc49..22bb57a1 100644 --- a/test/DynamicExpresso.UnitTest/DynamicTest.cs +++ b/test/DynamicExpresso.UnitTest/DynamicTest.cs @@ -169,7 +169,7 @@ public void Test_With_Dynamic_Object_By_Index_Access() DynamicIndexAccess globals = new DynamicIndexAccess(); Interpreter interpreter = new Interpreter() .SetVariable("Values", new DynamicIndexAccess()); - + Assert.AreEqual(globals.Values["Hello"], interpreter.Eval("Values[\"Hello\"]")); } @@ -307,22 +307,22 @@ public class ClassWithObjectProperties [Test] public void ObjectLateBinding() { - + var classWithObjectProperties = new ClassWithObjectProperties(); - + Assert.Throws(() => { var noLateBindingInterpreter = new Interpreter(); - var noLateBindingDel = noLateBindingInterpreter.ParseAsDelegate>("d.Foo+d.Bar()", new[] { "d" }); + var noLateBindingDel = noLateBindingInterpreter.Parse>("d.Foo+d.Bar()", "d").Compile(); var noLateBindingResult = noLateBindingDel.Invoke(classWithObjectProperties); }); var lateBindingInterpreter = new Interpreter(InterpreterOptions.Default|InterpreterOptions.LateBindObject); - var lateBindingInterpreterDel = lateBindingInterpreter.ParseAsDelegate>("d.Foo+d.Bar()", new[] { "d" }); + var lateBindingInterpreterDel = lateBindingInterpreter.Parse>("d.Foo+d.Bar()", "d").Compile(); var lateBindingResult = lateBindingInterpreterDel.Invoke(classWithObjectProperties); Assert.AreEqual((dynamic)classWithObjectProperties.Foo + (dynamic)classWithObjectProperties.Bar(), lateBindingResult); @@ -333,7 +333,7 @@ public void ObjectLateBinding() Assert.AreEqual((dynamic)classWithObjectProperties.Foo + (dynamic)classWithObjectProperties.Bar(), evalResult); } - + [Test] public void Bitwise_with_dynamic_properties() @@ -352,7 +352,7 @@ public void Bitwise_with_dynamic_properties() Assert.AreEqual(dyn.Foo ^ 500, interpreter.Eval("dyn.Foo ^ 500")); Assert.AreEqual(500 ^ dyn.Foo, interpreter.Eval("500 ^ dyn.Foo")); - + } [Test] @@ -390,13 +390,13 @@ public override bool TryGetMember(GetMemberBinder binder, out object result) } } - public class DynamicIndexAccess : DynamicObject + public class DynamicIndexAccess : DynamicObject { - public dynamic Values - { - get - { - return _values; + public dynamic Values + { + get + { + return _values; } } private readonly IReadOnlyDictionary _values; diff --git a/test/DynamicExpresso.UnitTest/ExpressionTypeTest.cs b/test/DynamicExpresso.UnitTest/ExpressionTypeTest.cs index 82bf097e..fd2b73d2 100644 --- a/test/DynamicExpresso.UnitTest/ExpressionTypeTest.cs +++ b/test/DynamicExpresso.UnitTest/ExpressionTypeTest.cs @@ -11,10 +11,10 @@ public void If_no_expression_type_is_specified_the_return_type_is_inferred() { var target = new Interpreter(); - Assert.AreEqual(typeof(string), target.Parse("\"ciao\"").ReturnType); - Assert.AreEqual(typeof(int), target.Parse("45").ReturnType); - Assert.AreEqual(typeof(double), target.Parse("45.4").ReturnType); - Assert.AreEqual(typeof(object), target.Parse("null").ReturnType); + Assert.AreEqual(typeof(string), target.Parse("\"ciao\"").Expression.Type); + Assert.AreEqual(typeof(int), target.Parse("45").Expression.Type); + Assert.AreEqual(typeof(double), target.Parse("45.4").Expression.Type); + Assert.AreEqual(typeof(object), target.Parse("null").Expression.Type); } [Test] @@ -23,10 +23,10 @@ public void If_expression_type_doesn_t_match_a_conversion_is_performed_when_poss var target = new Interpreter(); var expressionType = typeof(double); - var lambda = target.Parse("213", expressionType); + var parseResult = target.Parse("213", expressionType); - Assert.AreEqual(expressionType, lambda.ReturnType); - Assert.AreEqual((double)213, lambda.Invoke()); + Assert.AreEqual(expressionType, parseResult.Expression.Type); + Assert.AreEqual((double)213, parseResult.Compile().DynamicInvoke()); } [Test] @@ -35,10 +35,10 @@ public void If_expression_type_doesn_t_match_a_conversion_is_performed_eventuall var target = new Interpreter(); var expressionType = typeof(int); - var lambda = target.Parse("213.46", expressionType); + var parseResult = target.Parse("213.46", expressionType); - Assert.AreEqual(expressionType, lambda.ReturnType); - Assert.AreEqual((int)213.46, lambda.Invoke()); + Assert.AreEqual(expressionType, parseResult.Expression.Type); + Assert.AreEqual((int)213.46, parseResult.Compile().DynamicInvoke()); } [Test] @@ -46,13 +46,13 @@ public void Can_convert_a_null_expression_to_any_reference_type() { var target = new Interpreter(); - var lambda = target.Parse("null", typeof(string)); - Assert.AreEqual(typeof(string), lambda.ReturnType); - Assert.IsNull(lambda.Invoke()); + var parseResult = target.Parse("null", typeof(string)); + Assert.AreEqual(typeof(string), parseResult.Expression.Type); + Assert.IsNull(parseResult.Compile().DynamicInvoke()); - lambda = target.Parse("null", typeof(TestReferenceType)); - Assert.AreEqual(typeof(TestReferenceType), lambda.ReturnType); - Assert.IsNull(lambda.Invoke()); + parseResult = target.Parse("null", typeof(TestReferenceType)); + Assert.AreEqual(typeof(TestReferenceType), parseResult.Expression.Type); + Assert.IsNull(parseResult.Compile().DynamicInvoke()); } [Test] @@ -60,13 +60,13 @@ public void Can_convert_a_null_expression_to_any_nullable_type() { var target = new Interpreter(); - var lambda = target.Parse("null", typeof(int?)); - Assert.AreEqual(typeof(int?), lambda.ReturnType); - Assert.IsNull(lambda.Invoke()); + var parseResult = target.Parse("null", typeof(int?)); + Assert.AreEqual(typeof(int?), parseResult.Expression.Type); + Assert.IsNull(parseResult.Compile().DynamicInvoke()); - lambda = target.Parse("null", typeof(DateTime?)); - Assert.AreEqual(typeof(DateTime?), lambda.ReturnType); - Assert.IsNull(lambda.Invoke()); + parseResult = target.Parse("null", typeof(DateTime?)); + Assert.AreEqual(typeof(DateTime?), parseResult.Expression.Type); + Assert.IsNull(parseResult.Compile().DynamicInvoke()); } [Test] @@ -74,13 +74,13 @@ public void A_nullable_type_can_be_a_value_or_null() { var target = new Interpreter(); - var lambda = target.Parse("null", typeof(int?)); - Assert.AreEqual(typeof(int?), lambda.ReturnType); - Assert.IsNull(lambda.Invoke()); + var parseResult = target.Parse("null", typeof(int?)); + Assert.AreEqual(typeof(int?), parseResult.Expression.Type); + Assert.IsNull(parseResult.Compile().DynamicInvoke()); - lambda = target.Parse("4651", typeof(int?)); - Assert.AreEqual(typeof(int?), lambda.ReturnType); - Assert.AreEqual(4651, lambda.Invoke()); + parseResult = target.Parse("4651", typeof(int?)); + Assert.AreEqual(typeof(int?), parseResult.Expression.Type); + Assert.AreEqual(4651, parseResult.Compile().DynamicInvoke()); } [Test] diff --git a/test/DynamicExpresso.UnitTest/GenerateDelegatesTest.cs b/test/DynamicExpresso.UnitTest/GenerateDelegatesTest.cs index b25ef6c8..13f295aa 100644 --- a/test/DynamicExpresso.UnitTest/GenerateDelegatesTest.cs +++ b/test/DynamicExpresso.UnitTest/GenerateDelegatesTest.cs @@ -12,11 +12,11 @@ public void Parse_To_a_Delegate() { var target = new Interpreter(); - var func = target.ParseAsDelegate>("Math.Pow(x, y) + 5", "x", "y"); + var func = target.Parse>("Math.Pow(x, y) + 5", "x", "y").Compile(); Assert.AreEqual(Math.Pow(10, 2) + 5, func(10, 2)); - func = target.ParseAsDelegate>("Math.Pow(x, y) + .5", "x", "y"); + func = target.Parse>("Math.Pow(x, y) + .5", "x", "y").Compile(); Assert.AreEqual(Math.Pow(10, 2) + .5, func(10, 2)); } @@ -25,7 +25,7 @@ public void Parse_To_a_Delegate_With_No_Parameters() { var target = new Interpreter(); - var func = target.ParseAsDelegate>("50"); + var func = target.Parse>("50").Compile(); Assert.AreEqual(50, func()); } @@ -35,7 +35,7 @@ public void Parse_To_a_Delegate_With_One_Parameter() { var target = new Interpreter(); - var func = target.ParseAsDelegate>("arg.Length"); + var func = target.Parse>("arg.Length").Compile(); Assert.AreEqual(4, func("ciao")); Assert.AreEqual(9, func("123456879")); @@ -47,7 +47,7 @@ public void Parse_To_a_Delegate_With_One_Parameter_With_Custom_Name() var target = new Interpreter(); var argumentName = "val"; // if not specified the delegate parameter is used which is "arg" - var func = target.ParseAsDelegate>("val.Length", argumentName); + var func = target.Parse>("val.Length", argumentName).Compile(); Assert.AreEqual(4, func("ciao")); Assert.AreEqual(9, func("123456879")); @@ -58,7 +58,7 @@ public void Parse_To_a_Delegate_With_Two_Parameters() { var target = new Interpreter(); - var func = target.ParseAsDelegate>("arg1 * arg2"); + var func = target.Parse>("arg1 * arg2").Compile(); Assert.AreEqual(6, func(3, 2)); Assert.AreEqual(50, func(5, 10)); @@ -70,7 +70,7 @@ public void Parse_To_a_Delegate_With_Two_Parameters_With_Custom_Name() var target = new Interpreter(); var argumentNames = new [] { "x", "y" }; - var func = target.ParseAsDelegate>("x * y", argumentNames); + var func = target.Parse>("x * y", argumentNames).Compile(); Assert.AreEqual(6, func(3, 2)); Assert.AreEqual(50, func(5, 10)); @@ -81,7 +81,7 @@ public void Parse_To_a_Custom_Delegate() { var target = new Interpreter(); - var func = target.ParseAsDelegate("x + y.Length"); + var func = target.Parse("x + y.Length").Compile(); Assert.AreEqual(7, func(3, "ciao")); Assert.AreEqual(10, func(5, "mondo")); @@ -95,7 +95,7 @@ public void Return_Type_Mismatch_Cause_An_Exception() var target = new Interpreter(); // expected a double but I return a string - Assert.Throws(() => target.ParseAsDelegate>("\"ciao\"")); + Assert.Throws(() => target.Parse>("\"ciao\"")); } } } diff --git a/test/DynamicExpresso.UnitTest/GenerateLambdaTest.cs b/test/DynamicExpresso.UnitTest/GenerateLambdaTest.cs index 52a3c9fc..d6373360 100644 --- a/test/DynamicExpresso.UnitTest/GenerateLambdaTest.cs +++ b/test/DynamicExpresso.UnitTest/GenerateLambdaTest.cs @@ -12,7 +12,7 @@ public void Parse_as_LambdaExpression() { var target = new Interpreter(); - Expression> lambdaExpression = target.ParseAsExpression>("arg + 5"); + var lambdaExpression = target.Parse>("arg + 5").AsExpression(); Assert.AreEqual(15, lambdaExpression.Compile()(10)); } @@ -22,11 +22,11 @@ public void Parse_as_LambdaExpression_with_parameter() { var target = new Interpreter(); - Expression> lambdaExpression = target.ParseAsExpression>("arg + 5"); + var lambdaExpression = target.Parse>("arg + 5").AsExpression(); Assert.AreEqual(15, lambdaExpression.Compile()(10)); - lambdaExpression = target.ParseAsExpression>("arg + .5"); + lambdaExpression = target.Parse>("arg + .5").AsExpression(); Assert.AreEqual(10.5, lambdaExpression.Compile()(10)); } @@ -35,7 +35,7 @@ public void Parse_To_a_Delegate_With_Two_Parameters() { var target = new Interpreter(); - var lambdaExpression = target.ParseAsExpression>("arg1 * arg2"); + var lambdaExpression = target.Parse>("arg1 * arg2").AsExpression(); Assert.AreEqual(6, lambdaExpression.Compile()(3, 2)); Assert.AreEqual(50, lambdaExpression.Compile()(5, 10)); @@ -46,8 +46,8 @@ public void Parse_To_a_Delegate_With_Two_Parameters_With_Custom_Name() { var target = new Interpreter(); - var argumentNames = new string[] { "x", "y" }; - var lambdaExpression = target.ParseAsExpression>("x * y", argumentNames); + var argumentNames = new[] { "x", "y" }; + var lambdaExpression = target.Parse>("x * y", argumentNames).AsExpression(); Assert.AreEqual(6, lambdaExpression.Compile()(3, 2)); Assert.AreEqual(50, lambdaExpression.Compile()(5, 10)); @@ -58,12 +58,12 @@ public void Generate_a_LambdaExpression_From_Lambda() { var target = new Interpreter(); - var lambda = target.Parse("Math.Pow(x, y) + 5", - new Parameter("x", typeof(double)), - new Parameter("y", typeof(double)) + var parseResult = target.Parse("Math.Pow(x, y) + 5", + Expression.Parameter(typeof(double), "x"), + Expression.Parameter(typeof(double), "y") ); - Expression> lambdaExpression = lambda.LambdaExpression>(); + var lambdaExpression = parseResult.AsExpression>(); Assert.AreEqual(Math.Pow(10, 2) + 5, lambdaExpression.Compile()(10, 2)); } @@ -75,11 +75,9 @@ public void Cannot_Generate_a_LambdaExpression_From_Lambda_with_parameters_count // Func delegate has 2 inputs, I just use one - var lambda = target.Parse("x + 5", - new Parameter("x", typeof(double)) - ); + var parseResult = target.Parse("x + 5", Expression.Parameter(typeof(double), "x")); - Assert.Throws(() => lambda.LambdaExpression>()); + Assert.Throws(() => parseResult.AsExpression>()); } [Test] @@ -89,11 +87,9 @@ public void Cannot_Generate_a_LambdaExpression_From_Lambda_with_parameters_type_ // Func delegate takes a string, I pass a double - var lambda = target.Parse("x + 5", - new Parameter("x", typeof(double)) - ); + var parseResult = target.Parse("x + 5", Expression.Parameter(typeof(double), "x")); - Assert.Throws(() => lambda.LambdaExpression>()); + Assert.Throws(() => parseResult.AsExpression>()); } [Test] @@ -103,11 +99,9 @@ public void Cannot_generate_a_Lambda_with_return_type_mismatch() // Func delegate returns a string, I return a double - var lambda = target.Parse("x + 5", - new Parameter("x", typeof(double)) - ); + var parseResult = target.Parse("x + 5", Expression.Parameter(typeof(double), "x")); - Assert.Throws(() => lambda.LambdaExpression>()); + Assert.Throws(() => parseResult.AsExpression>()); } } diff --git a/test/DynamicExpresso.UnitTest/GithubIssues.cs b/test/DynamicExpresso.UnitTest/GithubIssues.cs index ab5626a7..44d629af 100644 --- a/test/DynamicExpresso.UnitTest/GithubIssues.cs +++ b/test/DynamicExpresso.UnitTest/GithubIssues.cs @@ -4,6 +4,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Reflection; // ReSharper disable SpecifyACultureInStringConversionExplicitly @@ -221,7 +222,7 @@ static bool GetGFunction2(string arg = null) Assert.Throws(() => interpreter.Eval("GFunction(arg)")); // there should be an ambiguous call exception, but GFunction1 is used - // because gFunc1.Method.GetParameters()[0].HasDefaultValue == true + // because gFunc1.Method.GetParameters()[0].HasDefaultValue == true // and gFunc2.Method.GetParameters()[0].HasDefaultValue == false Assert.False((bool)interpreter.Eval("GFunction()")); } @@ -250,12 +251,12 @@ public void GitHub_Issue_164_bis() { var interpreter = new Interpreter(); - var lambda = interpreter.Parse("Scope?.ValueInt", new Parameter("Scope", typeof(Scope))); + var lambda = interpreter.Parse("Scope?.ValueInt", Expression.Parameter(typeof(Scope), "Scope")).Compile(); - var result = lambda.Invoke((Scope)null); + var result = lambda.DynamicInvoke((Scope)null); Assert.IsNull(result); - result = lambda.Invoke(new Scope { ValueInt = 5 }); + result = lambda.DynamicInvoke(new Scope { ValueInt = 5 }); Assert.AreEqual(5, result); interpreter.SetVariable("scope", new Scope { ValueInt = 5 }); @@ -280,18 +281,18 @@ public void GitHub_Issue_169() { var interpreter = new Interpreter(); - var lambda = interpreter.Parse("Scope?.Value", new Parameter("Scope", typeof(Scope))); + var lambda = interpreter.Parse("Scope?.Value", Expression.Parameter(typeof(Scope), "Scope")).Compile(); - var result = lambda.Invoke((Scope)null); + var result = lambda.DynamicInvoke((Scope)null); Assert.IsNull(result); - result = lambda.Invoke(new Scope()); + result = lambda.DynamicInvoke(new Scope()); Assert.IsNull(result); - result = lambda.Invoke(new Scope { Value = null }); + result = lambda.DynamicInvoke(new Scope { Value = null }); Assert.IsNull(result); - result = lambda.Invoke(new Scope { Value = 5 }); + result = lambda.DynamicInvoke(new Scope { Value = 5 }); Assert.AreEqual(5, result); } @@ -300,15 +301,15 @@ public void GitHub_Issue_169_bis() { var interpreter = new Interpreter(); - var lambda = interpreter.Parse("Scope?.Arr?[0]", new Parameter("Scope", typeof(Scope))); + var lambda = interpreter.Parse("Scope?.Arr?[0]", Expression.Parameter(typeof(Scope), "Scope")).Compile(); - var result = lambda.Invoke(new Scope()); + var result = lambda.DynamicInvoke(new Scope()); Assert.IsNull(result); - result = lambda.Invoke(new Scope { Arr = null }); + result = lambda.DynamicInvoke(new Scope { Arr = null }); Assert.IsNull(result); - result = lambda.Invoke(new Scope { Arr = new int?[] { 5 } }); + result = lambda.DynamicInvoke(new Scope { Arr = new int?[] { 5 } }); Assert.AreEqual(5, result); } @@ -317,12 +318,12 @@ public void GitHub_Issue_169_ter() { var interpreter = new Interpreter(); - var lambda = interpreter.Parse("Scope?.ArrInt?[0]", new Parameter("Scope", typeof(Scope))); + var lambda = interpreter.Parse("Scope?.ArrInt?[0]", Expression.Parameter(typeof(Scope), "Scope")).Compile(); - var result = lambda.Invoke(new Scope()); + var result = lambda.DynamicInvoke(new Scope()); Assert.IsNull(result); - result = lambda.Invoke(new Scope { ArrInt = new int[] { 5 } }); + result = lambda.DynamicInvoke(new Scope { ArrInt = new int[] { 5 } }); Assert.AreEqual(5, result); interpreter.SetVariable("scope", new Scope { ArrInt = new int[] { 5 } }); @@ -354,17 +355,17 @@ public void GitHub_Issue_197() var interpreterWithoutLambdas = new Interpreter(InterpreterOptions.DefaultCaseInsensitive); var stringExpression = "booleanValue ? someStringValue : \".\""; - var parameters = new List + var parameters = new [] { - new Parameter($"someStringValue", typeof(string), $"E33"), - new Parameter("booleanValue", typeof(bool), true) + Expression.Parameter(typeof(string), "someStringValue"), + Expression.Parameter(typeof(bool), "booleanValue") }; - var expressionWithoutLambdas = interpreterWithoutLambdas.Parse(stringExpression, typeof(void), parameters.ToArray()); - Assert.AreEqual("E33", expressionWithoutLambdas.Invoke(parameters.ToArray())); + var expressionWithoutLambdas = interpreterWithoutLambdas.Parse(stringExpression, typeof(void), parameters.ToArray()).Compile(); + Assert.AreEqual("E33", expressionWithoutLambdas.DynamicInvoke("E33", true)); - var expressionWithLambdas = interpreterWithLambdas.Parse(stringExpression, typeof(void), parameters.ToArray()); - Assert.AreEqual("E33", expressionWithLambdas.Invoke(parameters.ToArray())); + var expressionWithLambdas = interpreterWithLambdas.Parse(stringExpression, typeof(void), parameters.ToArray()).Compile(); + Assert.AreEqual("E33", expressionWithLambdas.DynamicInvoke("E33", true)); } [Test] @@ -374,7 +375,7 @@ public void GitHub_Issue_185() // forcing the return type to object should work // (ie a conversion expression should be emitted from long to object) - var del = interpreter.ParseAsDelegate>("a*2"); + var del = interpreter.Parse>("a*2").Compile(); var result = del(); Assert.AreEqual(246, result); } @@ -383,7 +384,7 @@ public void GitHub_Issue_185() public void GitHub_Issue_185_2() { var interpreter = new Interpreter().SetVariable("a", 123L); - var del = interpreter.ParseAsDelegate>("a*2"); + var del = interpreter.Parse>("a*2").Compile(); var result = del(); Assert.AreEqual(246, result); } diff --git a/test/DynamicExpresso.UnitTest/IdentifiersTest.cs b/test/DynamicExpresso.UnitTest/IdentifiersTest.cs index 1edc1f83..2aced5dc 100644 --- a/test/DynamicExpresso.UnitTest/IdentifiersTest.cs +++ b/test/DynamicExpresso.UnitTest/IdentifiersTest.cs @@ -1,4 +1,5 @@ using System.Linq; +using System.Linq.Expressions; using NUnit.Framework; namespace DynamicExpresso.UnitTest @@ -29,10 +30,11 @@ public void Registered_custom_identifiers_are_saved_inside_the_interpreter() [Test] public void Getting_the_list_of_used_identifiers() { - var target = new Interpreter() - .SetVariable("x", 23); + var target = new Interpreter().SetVariable("x", 23); - var lambda = target.Parse("x > a || true == b", new Parameter("a", 1), new Parameter("b", false)); + var lambda = target.Parse("x > a || true == b", + Expression.Parameter(typeof(int), "a"), + Expression.Parameter(typeof(bool), "b")); Assert.AreEqual(2, lambda.Identifiers.Count()); Assert.AreEqual("x", lambda.Identifiers.ElementAt(0).Name); diff --git a/test/DynamicExpresso.UnitTest/LambdaExpressionTest.cs b/test/DynamicExpresso.UnitTest/LambdaExpressionTest.cs index ba021abb..b55d4dbd 100644 --- a/test/DynamicExpresso.UnitTest/LambdaExpressionTest.cs +++ b/test/DynamicExpresso.UnitTest/LambdaExpressionTest.cs @@ -28,9 +28,9 @@ public void Check_Lambda_Return_Type() var list = new List { "abc", "dfe", "test" }; target.SetVariable("list", list); - var lambda = target.Parse("list.Select(str => str.Length)"); + var parseResult = target.Parse("list.Select(str => str.Length)"); - Assert.AreEqual(typeof(IEnumerable), lambda.ReturnType); + Assert.AreEqual(typeof(IEnumerable), parseResult.Expression.Type); } [Test] diff --git a/test/DynamicExpresso.UnitTest/LiteralsTest.cs b/test/DynamicExpresso.UnitTest/LiteralsTest.cs index 60165aea..de3a7645 100644 --- a/test/DynamicExpresso.UnitTest/LiteralsTest.cs +++ b/test/DynamicExpresso.UnitTest/LiteralsTest.cs @@ -91,7 +91,7 @@ public void Numeric_Literals_DefaultTypes() public void Numeric_Literals_DefaultLong() { var target = new Interpreter(); - + target.SetDefaultNumberType(DefaultNumberType.Long); Assert.IsInstanceOf(typeof(System.Int64), target.Eval("45")); @@ -276,7 +276,7 @@ public void Numeric_Literals_DefaultDecimal() Assert.AreEqual(.201M, target.Eval(".201")); Assert.AreEqual(-.201M, target.Eval("-.201")); Assert.AreEqual(+.201M, target.Eval("+.201")); - + // f suffix (single) Assert.AreEqual(4f, target.Eval("4f")); @@ -529,37 +529,37 @@ public void Should_Understand_ReturnType_Of_Literals() { var target = new Interpreter(); - Assert.AreEqual(typeof(string), target.Parse("\"some string\"").ReturnType); - Assert.AreEqual(typeof(string), target.Parse("\"\"").ReturnType); - Assert.AreEqual(typeof(int), target.Parse("234").ReturnType); - Assert.AreEqual(typeof(int), target.Parse("-234").ReturnType); - Assert.AreEqual(typeof(uint), target.Parse("123u").ReturnType); - Assert.AreEqual(typeof(uint), target.Parse("123U").ReturnType); - Assert.AreEqual(typeof(long), target.Parse("-123l").ReturnType); - Assert.AreEqual(typeof(long), target.Parse("123l").ReturnType); - Assert.AreEqual(typeof(long), target.Parse("123L").ReturnType); - Assert.AreEqual(typeof(ulong), target.Parse("123UL").ReturnType); - Assert.AreEqual(typeof(ulong), target.Parse("123Ul").ReturnType); - Assert.AreEqual(typeof(ulong), target.Parse("123uL").ReturnType); - Assert.AreEqual(typeof(ulong), target.Parse("123ul").ReturnType); - Assert.AreEqual(typeof(ulong), target.Parse("123LU").ReturnType); - Assert.AreEqual(typeof(ulong), target.Parse("123Lu").ReturnType); - Assert.AreEqual(typeof(ulong), target.Parse("123lU").ReturnType); - Assert.AreEqual(typeof(ulong), target.Parse("123lu").ReturnType); - Assert.AreEqual(typeof(double), target.Parse("234.54").ReturnType); - Assert.AreEqual(typeof(double), target.Parse(".9").ReturnType); - Assert.AreEqual(typeof(double), target.Parse("-.9").ReturnType); - Assert.AreEqual(typeof(double), target.Parse("234d").ReturnType); - Assert.AreEqual(typeof(double), target.Parse("234D").ReturnType); - Assert.AreEqual(typeof(float), target.Parse("4.5f").ReturnType); - Assert.AreEqual(typeof(float), target.Parse("4.5F").ReturnType); - Assert.AreEqual(typeof(float), target.Parse(".5f").ReturnType); - Assert.AreEqual(typeof(float), target.Parse(".5F").ReturnType); - Assert.AreEqual(typeof(decimal), target.Parse("234.48m").ReturnType); - Assert.AreEqual(typeof(decimal), target.Parse("234.48M").ReturnType); - Assert.AreEqual(typeof(decimal), target.Parse(".48m").ReturnType); - Assert.AreEqual(typeof(decimal), target.Parse(".48M").ReturnType); - Assert.AreEqual(typeof(object), target.Parse("null").ReturnType); + Assert.AreEqual(typeof(string), target.Parse("\"some string\"").Expression.Type); + Assert.AreEqual(typeof(string), target.Parse("\"\"").Expression.Type); + Assert.AreEqual(typeof(int), target.Parse("234").Expression.Type); + Assert.AreEqual(typeof(int), target.Parse("-234").Expression.Type); + Assert.AreEqual(typeof(uint), target.Parse("123u").Expression.Type); + Assert.AreEqual(typeof(uint), target.Parse("123U").Expression.Type); + Assert.AreEqual(typeof(long), target.Parse("-123l").Expression.Type); + Assert.AreEqual(typeof(long), target.Parse("123l").Expression.Type); + Assert.AreEqual(typeof(long), target.Parse("123L").Expression.Type); + Assert.AreEqual(typeof(ulong), target.Parse("123UL").Expression.Type); + Assert.AreEqual(typeof(ulong), target.Parse("123Ul").Expression.Type); + Assert.AreEqual(typeof(ulong), target.Parse("123uL").Expression.Type); + Assert.AreEqual(typeof(ulong), target.Parse("123ul").Expression.Type); + Assert.AreEqual(typeof(ulong), target.Parse("123LU").Expression.Type); + Assert.AreEqual(typeof(ulong), target.Parse("123Lu").Expression.Type); + Assert.AreEqual(typeof(ulong), target.Parse("123lU").Expression.Type); + Assert.AreEqual(typeof(ulong), target.Parse("123lu").Expression.Type); + Assert.AreEqual(typeof(double), target.Parse("234.54").Expression.Type); + Assert.AreEqual(typeof(double), target.Parse(".9").Expression.Type); + Assert.AreEqual(typeof(double), target.Parse("-.9").Expression.Type); + Assert.AreEqual(typeof(double), target.Parse("234d").Expression.Type); + Assert.AreEqual(typeof(double), target.Parse("234D").Expression.Type); + Assert.AreEqual(typeof(float), target.Parse("4.5f").Expression.Type); + Assert.AreEqual(typeof(float), target.Parse("4.5F").Expression.Type); + Assert.AreEqual(typeof(float), target.Parse(".5f").Expression.Type); + Assert.AreEqual(typeof(float), target.Parse(".5F").Expression.Type); + Assert.AreEqual(typeof(decimal), target.Parse("234.48m").Expression.Type); + Assert.AreEqual(typeof(decimal), target.Parse("234.48M").Expression.Type); + Assert.AreEqual(typeof(decimal), target.Parse(".48m").Expression.Type); + Assert.AreEqual(typeof(decimal), target.Parse(".48M").Expression.Type); + Assert.AreEqual(typeof(object), target.Parse("null").Expression.Type); Assert.AreEqual((45.5).GetType(), target.Eval("45.5").GetType()); Assert.AreEqual((45.8f).GetType(), target.Eval("45.8f").GetType()); diff --git a/test/DynamicExpresso.UnitTest/MemberInvocationTest.cs b/test/DynamicExpresso.UnitTest/MemberInvocationTest.cs index 676570d5..41cba530 100644 --- a/test/DynamicExpresso.UnitTest/MemberInvocationTest.cs +++ b/test/DynamicExpresso.UnitTest/MemberInvocationTest.cs @@ -108,7 +108,7 @@ public void Indexer_Getter_MultiDimensional() target.SetVariable("x", x); var y = new MyTestService(); target.SetVariable("y", y); - + Assert.AreEqual(x[1, 2], target.Eval("x[1, 2]")); Assert.AreEqual(y[y.Today, 2], target.Eval("y[y.Today, 2]")); Assert.AreEqual(y[y.Today], target.Eval("y[y.Today]")); @@ -218,7 +218,7 @@ public void Void_Method() target.Eval("service.VoidMethod()"); Assert.AreEqual(1, service.VoidMethodCalls); - Assert.AreEqual(typeof(void), target.Parse("service.VoidMethod()").ReturnType); + Assert.AreEqual(typeof(void), target.Parse("service.VoidMethod()").Expression.Type); } [Test] diff --git a/test/DynamicExpresso.UnitTest/NullableTest.cs b/test/DynamicExpresso.UnitTest/NullableTest.cs index 3ce74e41..7f355f52 100644 --- a/test/DynamicExpresso.UnitTest/NullableTest.cs +++ b/test/DynamicExpresso.UnitTest/NullableTest.cs @@ -24,28 +24,16 @@ public void NullableInt32_NullableInt32() var expectedReturnType = typeof(Nullable); // Addition - var expected = a + b; - var lambda = interpreter.Parse("a + b"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); + Verify(interpreter, "a + b", a + b); // Subtraction - expected = a - b; - lambda = interpreter.Parse("a - b"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); + Verify(interpreter, "a - b", a - b); // Division - expected = a / b; - lambda = interpreter.Parse("a / b"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); + Verify(interpreter, "a / b", a / b); // Multiplication - expected = a * b; - lambda = interpreter.Parse("a * b"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); + Verify(interpreter, "a * b", a * b); } [Test] @@ -60,28 +48,16 @@ public void NullableInt32_NullableInt32_with_left_null() var expectedReturnType = typeof(Nullable); // Addition - var expected = a + b; - var lambda = interpreter.Parse("a + b"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); + Verify(interpreter, "a + b", a + b); // Subtraction - expected = a - b; - lambda = interpreter.Parse("a - b"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); + Verify(interpreter, "a - b", a - b); // Division - expected = a / b; - lambda = interpreter.Parse("a / b"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); + Verify(interpreter, "a / b", a / b); // Multiplication - expected = a * b; - lambda = interpreter.Parse("a * b"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); + Verify(interpreter, "a * b", a * b); } [Test] @@ -96,28 +72,16 @@ public void NullableInt32_NullableInt32_with_right_null() var expectedReturnType = typeof(Nullable); // Addition - var expected = a + b; - var lambda = interpreter.Parse("a + b"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); + Verify(interpreter, "a + b", a + b); // Subtraction - expected = a - b; - lambda = interpreter.Parse("a - b"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); + Verify(interpreter, "a - b", a - b); // Division - expected = a / b; - lambda = interpreter.Parse("a / b"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); + Verify(interpreter, "a / b", a / b); // Multiplication - expected = a * b; - lambda = interpreter.Parse("a * b"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); + Verify(interpreter, "a * b", a * b); } [Test] @@ -132,28 +96,16 @@ public void NullableDouble_NullableDouble() var expectedReturnType = typeof(Nullable); // Addition - var expected = a + b; - var lambda = interpreter.Parse("a + b"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); + Verify(interpreter, "a + b", a + b); // Subtraction - expected = a - b; - lambda = interpreter.Parse("a - b"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); + Verify(interpreter, "a - b", a - b); // Division - expected = a / b; - lambda = interpreter.Parse("a / b"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); + Verify(interpreter, "a / b", a / b); // Multiplication - expected = a * b; - lambda = interpreter.Parse("a * b"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); + Verify(interpreter, "a * b", a * b); } [Test] @@ -168,28 +120,16 @@ public void Int32_NullableDouble() var expectedReturnType = typeof(Nullable); // Addition - var expected = a + b; - var lambda = interpreter.Parse("a + b"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); + Verify(interpreter, "a + b", a + b); // Subtraction - expected = a - b; - lambda = interpreter.Parse("a - b"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); + Verify(interpreter, "a - b", a - b); // Division - expected = a / b; - lambda = interpreter.Parse("a / b"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); + Verify(interpreter, "a / b", a / b); // Multiplication - expected = a * b; - lambda = interpreter.Parse("a * b"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); + Verify(interpreter, "a * b", a * b); } [Test] @@ -204,28 +144,16 @@ public void NullableInt32_Double() var expectedReturnType = typeof(Nullable); // Addition - var expected = a + b; - var lambda = interpreter.Parse("a + b"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); + Verify(interpreter, "a + b", a + b); // Subtraction - expected = a - b; - lambda = interpreter.Parse("a - b"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); + Verify(interpreter, "a - b", a - b); // Division - expected = a / b; - lambda = interpreter.Parse("a / b"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); + Verify(interpreter, "a / b", a / b); // Multiplication - expected = a * b; - lambda = interpreter.Parse("a * b"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); + Verify(interpreter, "a * b", a * b); } [Test] @@ -241,35 +169,19 @@ public void NullableDateTimeOffset_DatetimeOffset() interpreter.SetVariable("c", c, typeof(DateTimeOffset)); var expectedReturnType = typeof(bool); - var expected = a < b; - var lambda = interpreter.Parse("a < b"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); - - expected = a > b; - lambda = interpreter.Parse("a > b"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); - - expected = a == b; - lambda = interpreter.Parse("a == b"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); - - expected = a != b; - lambda = interpreter.Parse("a != b"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); - - expected = b == c; - lambda = interpreter.Parse("b == b"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); - - expected = b != c; - lambda = interpreter.Parse("b != c"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); + Verify(interpreter, "a < b", a < b); + Verify(interpreter, "a > b", a > b); + Verify(interpreter, "a == b", a == b); + Verify(interpreter, "a != b", a != b); + Verify(interpreter, "b == b", b == b); + Verify(interpreter, "b != c", b != c); + } + + private static void Verify(Interpreter interpreter, string expression, T expected) + { + var parsed = interpreter.Parse(expression); + Assert.AreEqual(expected, parsed.Compile().DynamicInvoke()); + Assert.AreEqual(typeof(T), parsed.Expression.Type); } } } diff --git a/test/DynamicExpresso.UnitTest/OperatorsTest.cs b/test/DynamicExpresso.UnitTest/OperatorsTest.cs index 27b43972..b8dfafc0 100644 --- a/test/DynamicExpresso.UnitTest/OperatorsTest.cs +++ b/test/DynamicExpresso.UnitTest/OperatorsTest.cs @@ -49,8 +49,8 @@ public void Unary_Cast_Operator() target.SetVariable("x", x); Assert.AreEqual((int)x, target.Eval("(int)x")); - Assert.AreEqual(typeof(int), target.Parse("(int)x").ReturnType); - Assert.AreEqual(typeof(object), target.Parse("(object)x").ReturnType); + Assert.AreEqual(typeof(int), target.Parse("(int)x").Expression.Type); + Assert.AreEqual(typeof(object), target.Parse("(object)x").Expression.Type); Assert.AreEqual((double)84 + 9 * 8, target.Eval("(double)84 + 9 *8")); } @@ -125,7 +125,7 @@ public void Is_Operator() // ReSharper disable once ConditionIsAlwaysTrueOrFalse Assert.AreEqual(a is string, target.Eval("a is string")); - Assert.AreEqual(typeof(bool), target.Parse("a is string").ReturnType); + Assert.AreEqual(typeof(bool), target.Parse("a is string").Expression.Type); // ReSharper disable once ConditionIsAlwaysTrueOrFalse Assert.AreEqual(b is string, target.Eval("b is string")); // ReSharper disable once ConditionIsAlwaysTrueOrFalse @@ -145,7 +145,7 @@ public void Is_Operator_Generics() target.Reference(typeof(Tuple<,>)); Assert.AreEqual(true, target.Eval("a is Tuple")); - Assert.AreEqual(typeof(bool), target.Parse("a is Tuple").ReturnType); + Assert.AreEqual(typeof(bool), target.Parse("a is Tuple").Expression.Type); Assert.AreEqual(true, target.Eval("b is Tuple")); } @@ -160,10 +160,10 @@ public void As_Operator() // ReSharper disable once TryCastAlwaysSucceeds Assert.AreEqual(a as string, target.Eval("a as string")); - Assert.AreEqual(typeof(string), target.Parse("a as string").ReturnType); + Assert.AreEqual(typeof(string), target.Parse("a as string").Expression.Type); // ReSharper disable once ExpressionIsAlwaysNull Assert.AreEqual(b as string, target.Eval("b as string")); - Assert.AreEqual(typeof(string), target.Parse("b as string").ReturnType); + Assert.AreEqual(typeof(string), target.Parse("b as string").Expression.Type); } [Test] @@ -196,17 +196,13 @@ public void String_Concatenation_with_type_conversion() [Test] public void String_Concatenation_check_string_method() { - MethodInfo expectedMethod = typeof(string) - .GetMethod(nameof(String.Concat), new[] {typeof(string), typeof(string)}); - - Interpreter interpreter = new Interpreter(); + var expectedMethod = typeof(string).GetMethod(nameof(string.Concat), new[] {typeof(string), typeof(string)}); + var interpreter = new Interpreter(); + var expressionText = "\"ciao \" + 1981"; + var lambda = interpreter.Parse(expressionText); - string expressionText = "\"ciao \" + 1981"; + var methodCallExpression = lambda.Expression as MethodCallExpression; - Lambda lambda = interpreter.Parse(expressionText); - - MethodCallExpression methodCallExpression = lambda.Expression as MethodCallExpression; - Assert.IsNotNull(methodCallExpression); Assert.AreEqual(expectedMethod, methodCallExpression.Method); } @@ -215,15 +211,15 @@ public void String_Concatenation_check_string_method() public void String_Concatenation_with_null() { Interpreter interpreter = new Interpreter(); - + string expressionText = "\"ciao \" + null"; Assert.AreEqual("ciao ", interpreter.Eval(expressionText)); - + Func someFunc = () => null; interpreter.SetFunction("someFunc", someFunc); expressionText = "\"ciao \" + someFunc()"; Assert.AreEqual("ciao ", interpreter.Eval(expressionText)); - + Func someFuncObject = () => null; interpreter.SetFunction("someFuncObject", someFuncObject); expressionText = "\"ciao \" + someFuncObject()"; @@ -232,7 +228,7 @@ public void String_Concatenation_with_null() expressionText = "someFunc() + \"123\" + null + \"678\" + someFuncObject()"; Assert.AreEqual("123678", interpreter.Eval(expressionText)); } - + private class MyClass { public override string ToString() @@ -255,7 +251,7 @@ public void String_Concatenation_with_overridden_ToString() Interpreter interpreter = new Interpreter() .SetVariable("myClass", new MyClass()) .SetVariable("myClassNullToString", new MyClassNullToString()); - + Assert.AreEqual("ciao MyClassStr", interpreter.Eval("\"ciao \" + myClass")); Assert.AreEqual("ciao ", interpreter.Eval("\"ciao \" + myClassNullToString")); } @@ -399,7 +395,7 @@ public void Assignment_Operators_can_be_disabled() Assert.AreEqual(AssignmentOperators.None, target.AssignmentOperators); - Assert.Throws(() => target.Parse("x = 5", new Parameter("x", 0))); + Assert.Throws(() => target.Parse("x = 5", Expression.Parameter(typeof(int), "x"))); } [Test] @@ -543,7 +539,7 @@ public void Implicit_conversion_operator_for_lambda() var target = new Interpreter() .SetVariable("x", new TypeWithImplicitConversion(10)); - var func = target.ParseAsDelegate>("x"); + var func = target.Parse>("x").Compile(); var val = func(); Assert.AreEqual(10, val); @@ -787,7 +783,7 @@ public void Throw_an_exception_if_a_custom_type_doesnt_define_equal_operator() var y = "5"; - var ex = Assert.Throws(() => target.Parse("x == y", new Parameter("y", y))); + var ex = Assert.Throws(() => target.Parse("x == y", Expression.Parameter(typeof(string), "y"))); Assert.IsInstanceOf(ex.InnerException); } @@ -801,7 +797,7 @@ public void Throw_an_exception_if_a_custom_type_doesnt_define_plus_operator() var y = 5; - var ex = Assert.Throws(() => target.Parse("x + y", new Parameter("y", y))); + var ex = Assert.Throws(() => target.Parse("x + y", Expression.Parameter(typeof(string), "y"))); Assert.IsInstanceOf(ex.InnerException); } diff --git a/test/DynamicExpresso.UnitTest/OtherTests.cs b/test/DynamicExpresso.UnitTest/OtherTests.cs index 15e2a06f..8898bccb 100644 --- a/test/DynamicExpresso.UnitTest/OtherTests.cs +++ b/test/DynamicExpresso.UnitTest/OtherTests.cs @@ -23,13 +23,13 @@ public void Empty_Null_Withespace_Expression() var target = new Interpreter(); Assert.AreEqual(null, target.Eval("")); - Assert.AreEqual(typeof(void), target.Parse("").ReturnType); + Assert.AreEqual(typeof(void), target.Parse("").Expression.Type); Assert.AreEqual(null, target.Eval(null)); - Assert.AreEqual(typeof(void), target.Parse(null).ReturnType); + Assert.AreEqual(typeof(void), target.Parse(null).Expression.Type); Assert.AreEqual(null, target.Eval(" \t\t\r\n \t ")); - Assert.AreEqual(typeof(void), target.Parse(" \t\t\r\n \t ").ReturnType); + Assert.AreEqual(typeof(void), target.Parse(" \t\t\r\n \t ").Expression.Type); } [Test] @@ -39,10 +39,7 @@ public void Complex_expression() var x = new MyTestService(); var y = 5; - var parameters = new[] { - new Parameter("x", x.GetType(), x), - new Parameter("y", y.GetType(), y), - }; + var parameters = new[] {new Parameter("x", x.GetType(), x), new Parameter("y", y.GetType(), y)}; Assert.AreEqual(x.AProperty > y && x.HelloWorld().Length == 10, target.Eval("x.AProperty >\t y && \r\n x.HelloWorld().Length == 10", parameters)); Assert.AreEqual(x.AProperty * (4 + 65) / x.AProperty, target.Eval("x.AProperty * (4 + 65) / x.AProperty", parameters)); @@ -58,19 +55,22 @@ public void Parse_An_Expression_And_Invoke_It_With_Different_Parameters() var target = new Interpreter() .SetVariable("service", service); - var func = target.Parse("x > 4 ? service.VoidMethod() : service.VoidMethod2()", - new Parameter("x", typeof(int))); + var parseResult = target.Parse( + "x > 4 ? service.VoidMethod() : service.VoidMethod2()", + Expression.Parameter(typeof(int), "x")); - Assert.AreEqual(typeof(void), func.ReturnType); + var func = parseResult.Compile(); + + Assert.AreEqual(typeof(void), parseResult.Expression.Type); Assert.AreEqual(0, service.VoidMethodCalled); Assert.AreEqual(0, service.VoidMethod2Called); - func.Invoke(new Parameter("x", 5)); + func.DynamicInvoke(5); Assert.AreEqual(1, service.VoidMethodCalled); Assert.AreEqual(0, service.VoidMethod2Called); - func.Invoke(new Parameter("x", 2)); + func.DynamicInvoke(2); Assert.AreEqual(1, service.VoidMethodCalled); Assert.AreEqual(1, service.VoidMethod2Called); } @@ -82,13 +82,10 @@ public void Should_Understand_ReturnType_Of_expressions() var x = new MyTestService(); var y = 5; - var parameters = new[] { - new Parameter("x", x.GetType(), x), - new Parameter("y", y.GetType(), y), - }; + var parameters = new[] {Expression.Parameter(x.GetType(), "x"), Expression.Parameter(y.GetType(), "y"),}; - Assert.AreEqual(typeof(bool), target.Parse("x.AProperty > y && x.HelloWorld().Length == 10", parameters).ReturnType); - Assert.AreEqual(typeof(int), target.Parse("x.AProperty * (4 + 65) / x.AProperty", parameters).ReturnType); + Assert.AreEqual(typeof(bool), target.Parse("x.AProperty > y && x.HelloWorld().Length == 10", parameters).Expression.Type); + Assert.AreEqual(typeof(int), target.Parse("x.AProperty * (4 + 65) / x.AProperty", parameters).Expression.Type); } [Test] @@ -97,24 +94,22 @@ public void Execute_the_same_function_multiple_times() var target = new Interpreter(); var functionX = target.Parse("Math.Pow(x, y) + 5", - new Parameter("x", typeof(double)), - new Parameter("y", typeof(double))); - - Assert.AreEqual(Math.Pow(15, 12) + 5, functionX.Invoke(15, 12)); - Assert.AreEqual(Math.Pow(5, 1) + 5, functionX.Invoke(5, 1)); - Assert.AreEqual(Math.Pow(11, 8) + 5, functionX.Invoke(11, 8)); - Assert.AreEqual(Math.Pow(3, 4) + 5, functionX.Invoke(new Parameter("x", 3.0), - new Parameter("y", 4.0))); - Assert.AreEqual(Math.Pow(9, 2) + 5, functionX.Invoke(new Parameter("x", 9.0), - new Parameter("y", 2.0))); - Assert.AreEqual(Math.Pow(1, 3) + 5, functionX.Invoke(new Parameter("x", 1.0), - new Parameter("y", 3.0))); + Expression.Parameter(typeof(double), "x"), + Expression.Parameter(typeof(double), "y")) + .Compile(); + + Assert.AreEqual(Math.Pow(15, 12) + 5, functionX.DynamicInvoke(15, 12)); + Assert.AreEqual(Math.Pow(5, 1) + 5, functionX.DynamicInvoke(5, 1)); + Assert.AreEqual(Math.Pow(11, 8) + 5, functionX.DynamicInvoke(11, 8)); + Assert.AreEqual(Math.Pow(3, 4) + 5, functionX.DynamicInvoke(3.0, 4.0)); + Assert.AreEqual(Math.Pow(9, 2) + 5, functionX.DynamicInvoke(9.0, 2.0)); + Assert.AreEqual(Math.Pow(1, 3) + 5, functionX.DynamicInvoke(1.0, 3.0)); } [Test] public void Linq_Where() { - var customers = new List { + var customers = new List { new Customer() { Name = "David", Age = 31, Gender = 'M' }, new Customer() { Name = "Mary", Age = 29, Gender = 'F' }, new Customer() { Name = "Jack", Age = 2, Gender = 'M' }, @@ -122,10 +117,10 @@ public void Linq_Where() new Customer() { Name = "Moses", Age = 120, Gender = 'M' }, }; - string whereExpression = "customer.Age > 18 && customer.Gender == 'F'"; + var whereExpression = "customer.Age > 18 && customer.Gender == 'F'"; var interpreter = new Interpreter(); - Func dynamicWhere = interpreter.ParseAsDelegate>(whereExpression, "customer"); + var dynamicWhere = interpreter.Parse>(whereExpression, "customer").Compile(); Assert.AreEqual(1, customers.Where(dynamicWhere).Count()); } @@ -135,8 +130,7 @@ public void Linq_Where2() { var prices = new [] { 5, 8, 6, 2 }; - var whereFunction = new Interpreter() - .ParseAsDelegate>("arg > 5"); + var whereFunction = new Interpreter().Parse>("arg > 5").Compile(); Assert.AreEqual(2, prices.Where(whereFunction).Count()); } @@ -144,7 +138,7 @@ public void Linq_Where2() [Test] public void Linq_Queryable_Expression_Where() { - IQueryable customers = (new List { + var customers = (new List { new Customer() { Name = "David", Age = 31, Gender = 'M' }, new Customer() { Name = "Mary", Age = 29, Gender = 'F' }, new Customer() { Name = "Jack", Age = 2, Gender = 'M' }, @@ -152,10 +146,10 @@ public void Linq_Queryable_Expression_Where() new Customer() { Name = "Moses", Age = 120, Gender = 'M' }, }).AsQueryable(); - string whereExpression = "customer.Age > 18 && customer.Gender == 'F'"; + var whereExpression = "customer.Age > 18 && customer.Gender == 'F'"; var interpreter = new Interpreter(); - Expression> expression = interpreter.ParseAsExpression>(whereExpression, "customer"); + var expression = interpreter.Parse>(whereExpression, "customer").AsExpression(); Assert.AreEqual(1, customers.Where(expression).Count()); } From e779787c8815718b68ad092fb4c827e927a134c8 Mon Sep 17 00:00:00 2001 From: eugenealeykin Date: Sun, 12 Dec 2021 23:51:55 +0200 Subject: [PATCH 3/9] fixed tests --- src/DynamicExpresso.Core/LambdaExtensions.cs | 20 ++++- test/DynamicExpresso.UnitTest/DynamicTest.cs | 5 +- test/DynamicExpresso.UnitTest/NullableTest.cs | 36 ++++---- .../DynamicExpresso.UnitTest/OperatorsTest.cs | 4 +- .../ParametersTest.cs | 88 ++++++++----------- test/DynamicExpresso.UnitTest/TypesTest.cs | 5 +- .../DynamicExpresso.UnitTest/VariablesTest.cs | 18 ++-- test/DynamicExpresso.UnitTest/VisitorsTest.cs | 7 +- 8 files changed, 88 insertions(+), 95 deletions(-) diff --git a/src/DynamicExpresso.Core/LambdaExtensions.cs b/src/DynamicExpresso.Core/LambdaExtensions.cs index 429fc1d6..665fb530 100644 --- a/src/DynamicExpresso.Core/LambdaExtensions.cs +++ b/src/DynamicExpresso.Core/LambdaExtensions.cs @@ -1,6 +1,8 @@ using System; using System.Linq; using System.Linq.Expressions; +using System.Reflection; +using System.Runtime.ExceptionServices; namespace DynamicExpresso { @@ -48,10 +50,20 @@ public static LambdaExpression AsLambdaExpression(this ParseResult parseResult, public static object Eval(this Interpreter interpreter, string expression, params Parameter[] args) { - return interpreter - .Parse(expression, args.Select(x => x.Expression).ToArray()) - .Compile() - .DynamicInvoke(args.Select(x => x.Value).ToArray()); + try + { + return interpreter + .Parse(expression, args.Select(x => x.Expression).ToArray()) + .Compile() + .DynamicInvoke(args.Select(x => x.Value).ToArray()); + } + catch (TargetInvocationException exc) + { + if (exc.InnerException != null) + ExceptionDispatchInfo.Capture(exc.InnerException).Throw(); + + throw; + } } public static TReturnType Eval(this Interpreter interpreter, string expression, params Parameter[] args) diff --git a/test/DynamicExpresso.UnitTest/DynamicTest.cs b/test/DynamicExpresso.UnitTest/DynamicTest.cs index 22bb57a1..e492f06e 100644 --- a/test/DynamicExpresso.UnitTest/DynamicTest.cs +++ b/test/DynamicExpresso.UnitTest/DynamicTest.cs @@ -5,6 +5,7 @@ using System.Linq.Expressions; using System.Collections.Generic; using DynamicExpresso.Exceptions; +using DynamicExpresso; namespace DynamicExpresso.UnitTest { @@ -70,7 +71,7 @@ public void Invoke_Method_of_an_ExpandoObject() var interpreter = new Interpreter() .SetVariable("dyn", dyn); - Assert.AreEqual(dyn.Foo(), interpreter.Eval("dyn.Foo()")); + Assert.AreEqual(dyn.Foo(), LambdaExtensions.Eval(interpreter, "dyn.Foo()")); } [Test] @@ -83,7 +84,7 @@ public void Invoke_Method_of_a_nested_ExpandoObject() var interpreter = new Interpreter() .SetVariable("dyn", dyn); - Assert.AreEqual(dyn.Sub.Foo(), interpreter.Eval("dyn.Sub.Foo()")); + Assert.AreEqual(dyn.Sub.Foo(), LambdaExtensions.Eval(interpreter, "dyn.Sub.Foo()")); } [Test] diff --git a/test/DynamicExpresso.UnitTest/NullableTest.cs b/test/DynamicExpresso.UnitTest/NullableTest.cs index 7f355f52..051629b3 100644 --- a/test/DynamicExpresso.UnitTest/NullableTest.cs +++ b/test/DynamicExpresso.UnitTest/NullableTest.cs @@ -21,19 +21,18 @@ public void NullableInt32_NullableInt32() var interpreter = new Interpreter(); interpreter.SetVariable("a", a, typeof(Nullable)); interpreter.SetVariable("b", b, typeof(Nullable)); - var expectedReturnType = typeof(Nullable); // Addition - Verify(interpreter, "a + b", a + b); + Verify(interpreter, "a + b", (Nullable) a + b); // Subtraction - Verify(interpreter, "a - b", a - b); + Verify(interpreter, "a - b", (Nullable) a - b); // Division - Verify(interpreter, "a / b", a / b); + Verify(interpreter, "a / b", (Nullable) a / b); // Multiplication - Verify(interpreter, "a * b", a * b); + Verify(interpreter, "a * b", (Nullable) a * b); } [Test] @@ -93,19 +92,18 @@ public void NullableDouble_NullableDouble() var interpreter = new Interpreter(); interpreter.SetVariable("a", a, typeof(Nullable)); interpreter.SetVariable("b", b, typeof(Nullable)); - var expectedReturnType = typeof(Nullable); // Addition - Verify(interpreter, "a + b", a + b); + Verify(interpreter, "a + b", (Nullable) a + b); // Subtraction - Verify(interpreter, "a - b", a - b); + Verify(interpreter, "a - b", (Nullable) a - b); // Division - Verify(interpreter, "a / b", a / b); + Verify(interpreter, "a / b", (Nullable) a / b); // Multiplication - Verify(interpreter, "a * b", a * b); + Verify(interpreter, "a * b", (Nullable) a * b); } [Test] @@ -117,19 +115,18 @@ public void Int32_NullableDouble() var interpreter = new Interpreter(); interpreter.SetVariable("a", a, typeof(Int32)); interpreter.SetVariable("b", b, typeof(Nullable)); - var expectedReturnType = typeof(Nullable); // Addition - Verify(interpreter, "a + b", a + b); + Verify(interpreter, "a + b", (Nullable) a + b); // Subtraction - Verify(interpreter, "a - b", a - b); + Verify(interpreter, "a - b", (Nullable) a - b); // Division - Verify(interpreter, "a / b", a / b); + Verify(interpreter, "a / b", (Nullable) a / b); // Multiplication - Verify(interpreter, "a * b", a * b); + Verify(interpreter, "a * b", (Nullable) a * b); } [Test] @@ -141,19 +138,18 @@ public void NullableInt32_Double() var interpreter = new Interpreter(); interpreter.SetVariable("a", a, typeof(Nullable)); interpreter.SetVariable("b", b, typeof(Double)); - var expectedReturnType = typeof(Nullable); // Addition - Verify(interpreter, "a + b", a + b); + Verify(interpreter, "a + b", (Nullable) a + b); // Subtraction - Verify(interpreter, "a - b", a - b); + Verify(interpreter, "a - b", (Nullable) a - b); // Division - Verify(interpreter, "a / b", a / b); + Verify(interpreter, "a / b", (Nullable) a / b); // Multiplication - Verify(interpreter, "a * b", a * b); + Verify(interpreter, "a * b", (Nullable) a * b); } [Test] diff --git a/test/DynamicExpresso.UnitTest/OperatorsTest.cs b/test/DynamicExpresso.UnitTest/OperatorsTest.cs index b8dfafc0..c26f2c5d 100644 --- a/test/DynamicExpresso.UnitTest/OperatorsTest.cs +++ b/test/DynamicExpresso.UnitTest/OperatorsTest.cs @@ -795,9 +795,7 @@ public void Throw_an_exception_if_a_custom_type_doesnt_define_plus_operator() var x = new TypeWithoutOverloadedBinaryOperators(3); target.SetVariable("x", x); - var y = 5; - - var ex = Assert.Throws(() => target.Parse("x + y", Expression.Parameter(typeof(string), "y"))); + var ex = Assert.Throws(() => target.Parse("x + y", Expression.Parameter(typeof(int), "y"))); Assert.IsInstanceOf(ex.InnerException); } diff --git a/test/DynamicExpresso.UnitTest/ParametersTest.cs b/test/DynamicExpresso.UnitTest/ParametersTest.cs index 040c2197..0e7276d9 100644 --- a/test/DynamicExpresso.UnitTest/ParametersTest.cs +++ b/test/DynamicExpresso.UnitTest/ParametersTest.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.Reflection; using System.Linq; +using System.Linq.Expressions; namespace DynamicExpresso.UnitTest { @@ -35,6 +36,7 @@ public void Parameters_orders_is_not_important_for_eval() Assert.AreEqual("AB", target.Eval("A + B", parameters)); } + /* [Test] public void Parameters_orders_can_be_different_between_parse_and_invoke() { @@ -48,18 +50,16 @@ public void Parameters_orders_can_be_different_between_parse_and_invoke() var lambda = target.Parse("A + B", parameters); Assert.AreEqual("AB", lambda.Invoke(parameters.Reverse())); - } + }*/ [Test] public void Expression_Without_Parameters() { var target = new Interpreter(); - var parameters = new Parameter[0]; - - var exp = target.Parse("10+5", parameters); + var func = target.Parse("10+5").Compile(); - Assert.AreEqual(15, exp.Invoke()); + Assert.AreEqual(15, func.DynamicInvoke()); } [Test] @@ -67,29 +67,22 @@ public void Parameters_Mismatch() { var target = new Interpreter(); - var parameters = new[] { - new Parameter("x", 23), - new Parameter("y", 7) - }; - - var exp = target.Parse("x + y", parameters); + var exp = target.Parse( + "x + y", + Expression.Parameter(typeof(int), "x"), + Expression.Parameter(typeof(int), "y")) + .Compile(); - var parametersMismatch = new[] { - new Parameter("x", 546) - }; - - Assert.Throws(() => exp.Invoke(parametersMismatch)); + Assert.Throws(() => exp.DynamicInvoke(546)); } + /* [Test] public void Invoke_the_lambda_using_different_parameters_values() { var target = new Interpreter(); - var parameters = new[] { - new Parameter("x", typeof(int)), - new Parameter("y", typeof(int)) - }; + var parameters = new[] {Expression.Parameter(typeof(int), "x"), Expression.Parameter(typeof(int), "y")}; var myFunc = target.Parse("x + y", parameters); @@ -106,22 +99,19 @@ public void Invoke_the_lambda_using_different_parameters_values() }; Assert.AreEqual(58, myFunc.Invoke(parameters2)); - } + }*/ [Test] public void Different_parameters_values_With_Args() { var target = new Interpreter(); - var parameters = new[] { - new Parameter("x", typeof(int)), - new Parameter("y", typeof(int)) - }; + var parameters = new[] {Expression.Parameter(typeof(int), "x"), Expression.Parameter(typeof(int), "y")}; - var myFunc = target.Parse("x + y", parameters); + var myFunc = target.Parse("x + y", parameters).Compile(); - Assert.AreEqual(30, myFunc.Invoke(23, 7)); - Assert.AreEqual(30, myFunc.Invoke(32, -2)); + Assert.AreEqual(30, myFunc.DynamicInvoke(23, 7)); + Assert.AreEqual(30, myFunc.DynamicInvoke(32, -2)); } [Test] @@ -173,6 +163,7 @@ public void Parameters_can_be_Case_insensitive() Assert.AreEqual(x, target.Eval("X", parameters)); } + /* [Test] public void Parameters_cannot_be_parsed_with_one_case_and_invoked_in_another_case() { @@ -195,7 +186,7 @@ public void Parameters_can_be_parsed_with_one_case_and_invoked_in_another_case_w var lambda = target.Parse("x", new Parameter("x", x.GetType())); Assert.AreEqual(x, lambda.Invoke(new Parameter("X", x))); - } + }*/ [Test] public void Complex_parameters() @@ -281,11 +272,12 @@ public void When_parsing_an_expression_only_the_actually_used_parameters_should_ { var target = new Interpreter(); - var parameters = new[] { - new Parameter("x", 23), - new Parameter("y", 7), - new Parameter("z", 54), - }; + var parameters = new[] + { + Expression.Parameter(typeof(int), "x"), + Expression.Parameter(typeof(int), "y"), + Expression.Parameter(typeof(int), "z"), + }; var lambda = target.Parse("x + y", parameters); @@ -305,10 +297,7 @@ public void Using_the_same_parameters_multiple_times_doesnt_produce_multiple_par { var target = new Interpreter(); - var parameters = new[] { - new Parameter("x", 23), - new Parameter("y", 7), - }; + var parameters = new[] {Expression.Parameter(typeof(int), "x"), Expression.Parameter(typeof(int), "y"),}; var lambda = target.Parse("x * y + x * y", parameters); @@ -322,37 +311,32 @@ public void When_lambda_is_invoked_input_parameters_must_follow_in_the_same_orde { var target = new Interpreter(); - var parameters = new[]{ - new Parameter("x", typeof(int)), - new Parameter("y", typeof(int)) - }; + var parameters = new[] {Expression.Parameter(typeof(int), "x"), Expression.Parameter(typeof(int), "y")}; - var lambda = target.Parse("y-x", parameters); + var lambda = target.Parse("y-x", parameters).Compile(); - Assert.AreEqual(4, lambda.Invoke(1, 5)); + Assert.AreEqual(4, lambda.DynamicInvoke(1, 5)); } + /* [Test] public void When_lambda_is_invoked_I_can_omit_parameters_not_used() { var target = new Interpreter(); - var parameters = new[]{ - new Parameter("x", typeof(int)), - new Parameter("y", typeof(int)) - }; + var parameters = new[] {Expression.Parameter(typeof(int), "x"), Expression.Parameter(typeof(int), "y"),}; - var lambda = target.Parse("y+5", parameters); + var lambda = target.Parse("y+5", parameters).Compile(); - Assert.AreEqual(7, lambda.Invoke(new Parameter("y", 2))); - } + Assert.AreEqual(7, lambda.DynamicInvoke(2)); + }*/ [Test] public void When_parsing_an_expression_to_a_delegate_the_delegate_parameters_are_respected_also_if_the_expression_doesnt_use_it() { var target = new Interpreter(); - var myDelegate = target.ParseAsDelegate("x + y"); + var myDelegate = target.Parse("x + y").Compile(); // parameter 'z' is not used but the delegate accept it in any case without problem Assert.AreEqual(3, myDelegate(1, 2, 123123)); diff --git a/test/DynamicExpresso.UnitTest/TypesTest.cs b/test/DynamicExpresso.UnitTest/TypesTest.cs index 2f1f41d6..01d84f19 100644 --- a/test/DynamicExpresso.UnitTest/TypesTest.cs +++ b/test/DynamicExpresso.UnitTest/TypesTest.cs @@ -2,6 +2,7 @@ using NUnit.Framework; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using DynamicExpresso.Exceptions; namespace DynamicExpresso.UnitTest @@ -111,7 +112,7 @@ public void Custom_Type_Alias() var target = new Interpreter() .Reference(typeof(MyDataContract), "DC"); - Assert.AreEqual(typeof(MyDataContract), target.Parse("new DC(\"davide\")").ReturnType); + Assert.AreEqual(typeof(MyDataContract), target.Parse("new DC(\"davide\")").Expression.Type); } [Test] @@ -139,7 +140,7 @@ public void Getting_the_list_of_used_types() { var target = new Interpreter(); - var lambda = target.Parse("Math.Max(a, typeof(string).Name.Length)", new Parameter("a", 1)); + var lambda = target.Parse("Math.Max(a, typeof(string).Name.Length)", Expression.Parameter(typeof(int), "a")); Assert.AreEqual(2, lambda.Types.Count()); Assert.AreEqual("Math", lambda.Types.ElementAt(0).Name); diff --git a/test/DynamicExpresso.UnitTest/VariablesTest.cs b/test/DynamicExpresso.UnitTest/VariablesTest.cs index 3b70e146..557bc2b8 100644 --- a/test/DynamicExpresso.UnitTest/VariablesTest.cs +++ b/test/DynamicExpresso.UnitTest/VariablesTest.cs @@ -44,7 +44,7 @@ public void Assign_and_use_variables() .SetVariable("myk", 23); Assert.AreEqual(23, target.Eval("myk")); - Assert.AreEqual(typeof(int), target.Parse("myk").ReturnType); + Assert.AreEqual(typeof(int), target.Parse("myk").Expression.Type); } [Test] @@ -80,7 +80,7 @@ public void Variables_can_be_overwritten() Assert.AreEqual(3489, target.Eval("myk")); - Assert.AreEqual(typeof(int), target.Parse("myk").ReturnType); + Assert.AreEqual(typeof(int), target.Parse("myk").Expression.Type); } [Test] @@ -95,7 +95,7 @@ public void Variables_can_be_overwritten_in_a_case_insensitive_setting() Assert.AreEqual(3489, target.Eval("myk")); - Assert.AreEqual(typeof(int), target.Parse("myk").ReturnType); + Assert.AreEqual(typeof(int), target.Parse("myk").Expression.Type); } [Test] @@ -106,7 +106,7 @@ public void Null_Variables() Assert.AreEqual(null, target.Eval("myk")); Assert.AreEqual(true, target.Eval("myk == null")); - Assert.AreEqual(typeof(object), target.Parse("myk").ReturnType); + Assert.AreEqual(typeof(object), target.Parse("myk").Expression.Type); } [Test] @@ -117,7 +117,7 @@ public void Null_Variables_With_Type_Specified() Assert.AreEqual(null, target.Eval("myk")); Assert.AreEqual(true, target.Eval("myk == null")); - Assert.AreEqual(typeof(string), target.Parse("myk").ReturnType); + Assert.AreEqual(typeof(string), target.Parse("myk").Expression.Type); } [Test] @@ -187,7 +187,7 @@ public void Keywords_with_ambiguous_delegates() // ambiguous call: null can either be a string or an object // note: if there's no ambiguous exception, it means that the resolution - // lifted the parameters from the string overload, which prevented the int? overload + // lifted the parameters from the string overload, which prevented the int? overload // from being considered Assert.Throws(() => interpreter.Eval("MyFunc(null)")); @@ -205,8 +205,8 @@ public void Keywords_with_non_ambiguous_delegates() interpreter.SetFunction("MyFunc", ambiguous1); interpreter.SetFunction("MyFunc", ambiguous2); - // there should be no ambiguous exception: int can implicitly be converted to double, - // but there's a perfect match + // there should be no ambiguous exception: int can implicitly be converted to double, + // but there's a perfect match Assert.AreEqual("integer", interpreter.Eval("MyFunc(5)")); } @@ -215,7 +215,7 @@ public void Set_function_With_Object_Params() { var target = new Interpreter(); - // import static method with params array + // import static method with params array var methodInfo = typeof(VariablesTest).GetMethod("Sum", BindingFlags.Static | BindingFlags.NonPublic); var types = methodInfo.GetParameters().Select(p => p.ParameterType).Concat(new[] { methodInfo.ReturnType }); var del = methodInfo.CreateDelegate(Expression.GetDelegateType(types.ToArray())); diff --git a/test/DynamicExpresso.UnitTest/VisitorsTest.cs b/test/DynamicExpresso.UnitTest/VisitorsTest.cs index 2d272367..27fe5024 100644 --- a/test/DynamicExpresso.UnitTest/VisitorsTest.cs +++ b/test/DynamicExpresso.UnitTest/VisitorsTest.cs @@ -1,4 +1,5 @@ -using DynamicExpresso.Exceptions; +using System.Linq.Expressions; +using DynamicExpresso.Exceptions; using NUnit.Framework; namespace DynamicExpresso.UnitTest @@ -14,8 +15,8 @@ public void By_default_reflection_is_not_permitted() Assert.Throws(() => target.Parse("typeof(double).GetMethods()")); Assert.Throws(() => target.Parse("typeof(double).Assembly")); - Assert.Throws(() => target.Parse("x.GetType().GetMethods()", new Parameter("x", typeof(X)))); - Assert.Throws(() => target.Parse("x.GetType().Assembly", new Parameter("x", typeof(X)))); + Assert.Throws(() => target.Parse("x.GetType().GetMethods()", Expression.Parameter(typeof(X), "x"))); + Assert.Throws(() => target.Parse("x.GetType().Assembly", Expression.Parameter(typeof(X), "x"))); } [Test] From 8b9fcec82524d4baf0e7150a5f47c017e58fde95 Mon Sep 17 00:00:00 2001 From: eugenealeykin Date: Tue, 14 Dec 2021 11:59:30 +0200 Subject: [PATCH 4/9] backward compatibility --- .../ExpressionInterpreter.cs | 280 ++++++++++++++ src/DynamicExpresso.Core/Interpreter.cs | 337 ++++++++--------- .../InterpreterSettings.cs | 344 ++++++++++++++++++ src/DynamicExpresso.Core/LambdaExtensions.cs | 39 +- src/DynamicExpresso.Core/Parameter.cs | 5 + src/DynamicExpresso.Core/Parsing/Parser.cs | 17 +- .../Parsing/ParserSettings.cs | 2 +- .../CollectionHelperTests.cs | 2 +- test/DynamicExpresso.UnitTest/DynamicTest.cs | 31 +- .../ExpressionTypeTest.cs | 56 +-- .../GenerateDelegatesTest.cs | 18 +- .../GenerateLambdaTest.cs | 38 +- test/DynamicExpresso.UnitTest/GithubIssues.cs | 51 ++- .../IdentifiersTest.cs | 8 +- .../LambdaExpressionTest.cs | 4 +- test/DynamicExpresso.UnitTest/LiteralsTest.cs | 66 ++-- .../MemberInvocationTest.cs | 4 +- test/DynamicExpresso.UnitTest/NullableTest.cs | 179 ++++++--- .../DynamicExpresso.UnitTest/OperatorsTest.cs | 46 ++- test/DynamicExpresso.UnitTest/OtherTests.cs | 70 ++-- .../ParametersTest.cs | 88 +++-- test/DynamicExpresso.UnitTest/TypesTest.cs | 5 +- .../DynamicExpresso.UnitTest/VariablesTest.cs | 18 +- test/DynamicExpresso.UnitTest/VisitorsTest.cs | 7 +- 24 files changed, 1249 insertions(+), 466 deletions(-) create mode 100644 src/DynamicExpresso.Core/ExpressionInterpreter.cs create mode 100644 src/DynamicExpresso.Core/InterpreterSettings.cs diff --git a/src/DynamicExpresso.Core/ExpressionInterpreter.cs b/src/DynamicExpresso.Core/ExpressionInterpreter.cs new file mode 100644 index 00000000..ce9748e2 --- /dev/null +++ b/src/DynamicExpresso.Core/ExpressionInterpreter.cs @@ -0,0 +1,280 @@ +using DynamicExpresso.Parsing; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using DynamicExpresso.Reflection; + +namespace DynamicExpresso +{ + /// + /// Class used to parse and compile a text expression into an Expression or a Delegate that can be invoked. Expression are written using a subset of C# syntax. + /// Only get properties, Parse and Eval methods are thread safe. + /// + public class ExpressionInterpreter + { + private readonly InterpreterSettings _settings; + + #region Constructors + + /// + /// Creates a new ExpressionInterpreter using InterpreterOptions.Default. + /// + public ExpressionInterpreter() : this(InterpreterOptions.Default) + { + } + + /// + /// Creates a new ExpressionInterpreter using the specified options. + /// + /// + public ExpressionInterpreter(InterpreterOptions options) + { + _settings = new InterpreterSettings(options); + } + + /// + /// Create a new ExpressionInterpreter with the settings copied from another interpreter + /// + internal ExpressionInterpreter(ParserSettings settings) + { + _settings = new InterpreterSettings(settings); + } + + #endregion + + #region Properties + + public bool CaseInsensitive { get { return _settings.CaseInsensitive; } } + + /// + /// Gets a list of registeres types. Add types by using the Reference method. + /// + public IEnumerable ReferencedTypes { get { return _settings.ReferencedTypes; } } + + /// + /// Gets a list of known identifiers. Add identifiers using SetVariable, SetFunction or SetExpression methods. + /// + public IEnumerable Identifiers { get { return _settings.Identifiers; } } + + /// + /// Gets the available assignment operators. + /// + public AssignmentOperators AssignmentOperators { get { return _settings.AssignmentOperators; } } + + #endregion + + #region Options + + /// + /// Allow to set de default numeric type when no suffix is specified (Int by default, Double if real number) + /// + /// + /// + public ExpressionInterpreter SetDefaultNumberType(DefaultNumberType defaultNumberType) + { + _settings.SetDefaultNumberType(defaultNumberType); + return this; + } + + /// + /// Allows to enable/disable assignment operators. + /// For security when expression are generated by the users is more safe to disable assignment operators. + /// + /// + /// + public ExpressionInterpreter EnableAssignment(AssignmentOperators assignmentOperators) + { + _settings.EnableAssignment(assignmentOperators); + return this; + } + + #endregion + + #region Visitors + + public ISet Visitors { get { return _settings.Visitors; } } + + /// + /// Enable reflection expression (like x.GetType().GetMethod() or typeof(double).Assembly) by removing the DisableReflectionVisitor. + /// + /// + public ExpressionInterpreter EnableReflection() + { + _settings.EnableReflection(); + return this; + } + + #endregion + + #region Register identifiers + + /// + /// Allow the specified function delegate to be called from a parsed expression. + /// + /// + /// + /// + public ExpressionInterpreter SetFunction(string name, Delegate value) + { + _settings.SetFunction(name, value); + return this; + } + + /// + /// Allow the specified variable to be used in a parsed expression. + /// + /// + /// + /// + public ExpressionInterpreter SetVariable(string name, object value) + { + _settings.SetVariable(name, value); + return this; + } + + /// + /// Allow the specified variable to be used in a parsed expression. + /// + /// + /// + /// + public ExpressionInterpreter SetVariable(string name, T value) + { + _settings.SetVariable(name, value); + return this; + } + + /// + /// Allow the specified variable to be used in a parsed expression. + /// + /// + /// + /// + /// + public ExpressionInterpreter SetVariable(string name, object value, Type type) + { + _settings.SetVariable(name, value, type); + return this; + } + + /// + /// Allow the specified Expression to be used in a parsed expression. + /// Basically add the specified expression as a known identifier. + /// + /// + /// + /// + public ExpressionInterpreter SetExpression(string name, Expression expression) + { + _settings.SetVariable(name, expression); + return this; + } + + /// + /// Allow the specified list of identifiers to be used in a parsed expression. + /// Basically add the specified expressions as a known identifier. + /// + /// + /// + public ExpressionInterpreter SetIdentifiers(IEnumerable identifiers) + { + _settings.SetIdentifiers(identifiers); + return this; + } + + /// + /// Allow the specified identifier to be used in a parsed expression. + /// Basically add the specified expression as a known identifier. + /// + /// + /// + public ExpressionInterpreter SetIdentifier(Identifier identifier) + { + _settings.SetIdentifier(identifier); + return this; + } + + #endregion + + #region Parse + + public ParseResult Parse(string expressionText, params string[] parametersNames) + { + var delegateInfo = ReflectionExtensions.GetDelegateInfo(typeof(TDelegate), parametersNames); + var parseResult = Parse( + expressionText, + delegateInfo.ReturnType, + delegateInfo.Parameters.Select(x => x.Expression).ToArray()); + + return new ParseResult( + expression: parseResult.Expression, + usedParameters: parseResult.UsedParameters, + declaredParameters: parseResult.DeclaredParameters, + types: parseResult.Types, + identifiers: parseResult.Identifiers); + } + + public ParseResult Parse(string expressionText, params ParameterExpression[] parameters) + { + return Parse(expressionText, typeof(void), parameters); + } + + public ParseResult Parse(string expressionText, Type expressionReturnType, params ParameterExpression[] parameters) + { + if (parameters == null) + parameters = new ParameterExpression[0]; + + var arguments = new ParserArguments( + expressionText, + _settings.ParserSettings, + expressionReturnType, + parameters.Select(x => new Parameter(x))); + + var expression = _settings.Visitors.Aggregate(Parser.Parse(arguments), (current, visitor) => visitor.Visit(current)); + + var lambda = new ParseResult( + expression: expression, + usedParameters: arguments.UsedParameters.Select(x => x.Expression), + declaredParameters: arguments.DeclaredParameters.Select(x => x.Expression), + types: arguments.UsedTypes, + identifiers: arguments.UsedIdentifiers); + +#if TEST_DetectIdentifiers + AssertDetectIdentifiers(lambda); +#endif + + return lambda; + } + + #endregion + + #region Detection + + public IdentifiersInfo DetectIdentifiers(string expression) + { + var detector = new Detector(_settings.ParserSettings); + + return detector.DetectIdentifiers(expression); + } + + #endregion + + #region Private methods + +#if TEST_DetectIdentifiers + private void AssertDetectIdentifiers(Lambda lambda) + { + var info = DetectIdentifiers(lambda.ExpressionText); + + if (info.Identifiers.Count() != lambda.Identifiers.Count()) + throw new Exception("Detected identifiers doesn't match actual identifiers"); + if (info.Types.Count() != lambda.Types.Count()) + throw new Exception("Detected types doesn't match actual types"); + if (info.UnknownIdentifiers.Count() != lambda.UsedParameters.Count()) + throw new Exception("Detected unknown identifiers doesn't match actual parameters"); + } +#endif + #endregion + } +} diff --git a/src/DynamicExpresso.Core/Interpreter.cs b/src/DynamicExpresso.Core/Interpreter.cs index 6c718d71..a7e2b700 100644 --- a/src/DynamicExpresso.Core/Interpreter.cs +++ b/src/DynamicExpresso.Core/Interpreter.cs @@ -1,10 +1,9 @@ using DynamicExpresso.Parsing; -using DynamicExpresso.Visitors; +using DynamicExpresso.Reflection; using System; using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; -using DynamicExpresso.Reflection; +using DynamicExpresso.Exceptions; namespace DynamicExpresso { @@ -14,15 +13,14 @@ namespace DynamicExpresso /// public class Interpreter { - private readonly ParserSettings _settings; - private readonly ISet _visitors = new HashSet(); + private readonly InterpreterSettings _settings; #region Constructors + /// /// Creates a new Interpreter using InterpreterOptions.Default. /// - public Interpreter() - : this(InterpreterOptions.Default) + public Interpreter() : this(InterpreterOptions.Default) { } @@ -32,34 +30,7 @@ public Interpreter() /// public Interpreter(InterpreterOptions options) { - var caseInsensitive = options.HasFlag(InterpreterOptions.CaseInsensitive); - - var lateBindObject = options.HasFlag(InterpreterOptions.LateBindObject); - - _settings = new ParserSettings(caseInsensitive, lateBindObject); - - if ((options & InterpreterOptions.SystemKeywords) == InterpreterOptions.SystemKeywords) - { - SetIdentifiers(LanguageConstants.Literals); - } - - if ((options & InterpreterOptions.PrimitiveTypes) == InterpreterOptions.PrimitiveTypes) - { - Reference(LanguageConstants.PrimitiveTypes); - Reference(LanguageConstants.CSharpPrimitiveTypes); - } - - if ((options & InterpreterOptions.CommonTypes) == InterpreterOptions.CommonTypes) - { - Reference(LanguageConstants.CommonTypes); - } - - if ((options & InterpreterOptions.LambdaExpressions) == InterpreterOptions.LambdaExpressions) - { - _settings.LambdaExpressions = true; - } - - _visitors.Add(new DisableReflectionVisitor()); + _settings = new InterpreterSettings(options); } /// @@ -67,52 +38,30 @@ public Interpreter(InterpreterOptions options) /// internal Interpreter(ParserSettings settings) { - _settings = settings; + _settings = new InterpreterSettings(settings); } + #endregion #region Properties - public bool CaseInsensitive - { - get - { - return _settings.CaseInsensitive; - } - } + + public bool CaseInsensitive { get { return _settings.CaseInsensitive; } } /// /// Gets a list of registeres types. Add types by using the Reference method. /// - public IEnumerable ReferencedTypes - { - get - { - return _settings.KnownTypes - .Select(p => p.Value) - .ToList(); - } - } + public IEnumerable ReferencedTypes { get { return _settings.ReferencedTypes; } } /// /// Gets a list of known identifiers. Add identifiers using SetVariable, SetFunction or SetExpression methods. /// - public IEnumerable Identifiers - { - get - { - return _settings.Identifiers - .Select(p => p.Value) - .ToList(); - } - } + public IEnumerable Identifiers { get { return _settings.Identifiers; } } /// /// Gets the available assignment operators. /// - public AssignmentOperators AssignmentOperators - { - get { return _settings.AssignmentOperators; } - } + public AssignmentOperators AssignmentOperators { get { return _settings.AssignmentOperators; } } + #endregion #region Options @@ -124,7 +73,7 @@ public AssignmentOperators AssignmentOperators /// public Interpreter SetDefaultNumberType(DefaultNumberType defaultNumberType) { - _settings.DefaultNumberType = defaultNumberType; + _settings.SetDefaultNumberType(defaultNumberType); return this; } @@ -136,17 +85,14 @@ public Interpreter SetDefaultNumberType(DefaultNumberType defaultNumberType) /// public Interpreter EnableAssignment(AssignmentOperators assignmentOperators) { - _settings.AssignmentOperators = assignmentOperators; - + _settings.EnableAssignment(assignmentOperators); return this; } #endregion #region Visitors - public ISet Visitors - { - get { return _visitors; } - } + + public ISet Visitors { get { return _settings.Visitors; } } /// /// Enable reflection expression (like x.GetType().GetMethod() or typeof(double).Assembly) by removing the DisableReflectionVisitor. @@ -154,15 +100,14 @@ public ISet Visitors /// public Interpreter EnableReflection() { - var visitor = Visitors.FirstOrDefault(p => p is DisableReflectionVisitor); - if (visitor != null) - Visitors.Remove(visitor); - + _settings.EnableReflection(); return this; } + #endregion #region Register identifiers + /// /// Allow the specified function delegate to be called from a parsed expression. /// @@ -171,18 +116,7 @@ public Interpreter EnableReflection() /// public Interpreter SetFunction(string name, Delegate value) { - if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentNullException(nameof(name)); - - if (_settings.Identifiers.TryGetValue(name, out var identifier) && identifier is FunctionIdentifier fIdentifier) - { - fIdentifier.AddOverload(value); - } - else - { - SetIdentifier(new FunctionIdentifier(name, value)); - } - + _settings.SetFunction(name, value); return this; } @@ -194,10 +128,8 @@ public Interpreter SetFunction(string name, Delegate value) /// public Interpreter SetVariable(string name, object value) { - if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentNullException(nameof(name)); - - return SetExpression(name, Expression.Constant(value)); + _settings.SetVariable(name, value); + return this; } /// @@ -208,7 +140,8 @@ public Interpreter SetVariable(string name, object value) /// public Interpreter SetVariable(string name, T value) { - return SetVariable(name, value, typeof(T)); + _settings.SetVariable(name, value); + return this; } /// @@ -220,12 +153,8 @@ public Interpreter SetVariable(string name, T value) /// public Interpreter SetVariable(string name, object value, Type type) { - if (type == null) - throw new ArgumentNullException(nameof(type)); - if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentNullException(nameof(name)); - - return SetExpression(name, Expression.Constant(value, type)); + _settings.SetVariable(name, value, type); + return this; } /// @@ -237,7 +166,8 @@ public Interpreter SetVariable(string name, object value, Type type) /// public Interpreter SetExpression(string name, Expression expression) { - return SetIdentifier(new Identifier(name, expression)); + _settings.SetExpression(name, expression); + return this; } /// @@ -248,9 +178,7 @@ public Interpreter SetExpression(string name, Expression expression) /// public Interpreter SetIdentifiers(IEnumerable identifiers) { - foreach (var i in identifiers) - SetIdentifier(i); - + _settings.SetIdentifiers(identifiers); return this; } @@ -262,14 +190,7 @@ public Interpreter SetIdentifiers(IEnumerable identifiers) /// public Interpreter SetIdentifier(Identifier identifier) { - if (identifier == null) - throw new ArgumentNullException(nameof(identifier)); - - if (LanguageConstants.ReservedKeywords.Contains(identifier.Name)) - throw new InvalidOperationException($"{identifier.Name} is a reserved word"); - - _settings.Identifiers[identifier.Name] = identifier; - + _settings.SetIdentifier(identifier); return this; } #endregion @@ -283,10 +204,8 @@ public Interpreter SetIdentifier(Identifier identifier) /// public Interpreter Reference(Type type) { - if (type == null) - throw new ArgumentNullException(nameof(type)); - - return Reference(type, type.Name); + _settings.Reference(type); + return this; } /// @@ -297,12 +216,7 @@ public Interpreter Reference(Type type) /// public Interpreter Reference(IEnumerable types) { - if (types == null) - throw new ArgumentNullException(nameof(types)); - - foreach (var t in types) - Reference(t); - + _settings.Reference(types); return this; } @@ -315,7 +229,8 @@ public Interpreter Reference(IEnumerable types) /// public Interpreter Reference(Type type, string typeName) { - return Reference(new ReferenceType(typeName, type)); + _settings.Reference(type, typeName); + return this; } /// @@ -326,76 +241,142 @@ public Interpreter Reference(Type type, string typeName) /// public Interpreter Reference(ReferenceType type) { - if (type == null) - throw new ArgumentNullException(nameof(type)); - - _settings.KnownTypes[type.Name] = type; - - foreach (var extensionMethod in type.ExtensionMethods) - { - _settings.ExtensionMethods.Add(extensionMethod); - } - + _settings.Reference(type); return this; } #endregion #region Parse - public ParseResult Parse(string expressionText, params string[] parametersNames) + /// + /// Parse a text expression and returns a Lambda class that can be used to invoke it. + /// + /// Expression statement + /// + /// + /// + public Lambda Parse(string expressionText, params Parameter[] parameters) { - var delegateInfo = ReflectionExtensions.GetDelegateInfo(typeof(TDelegate), parametersNames); - var parseResult = Parse( - expressionText, - delegateInfo.ReturnType, - delegateInfo.Parameters.Select(x => x.Expression).ToArray()); - - return new ParseResult( - expression: parseResult.Expression, - usedParameters: parseResult.UsedParameters, - declaredParameters: parseResult.DeclaredParameters, - types: parseResult.Types, - identifiers: parseResult.Identifiers); + return Parse(expressionText, typeof(void), parameters); } - public ParseResult Parse(string expressionText, params ParameterExpression[] parameters) + /// + /// Parse a text expression and returns a Lambda class that can be used to invoke it. + /// If the expression cannot be converted to the type specified in the expressionType parameter + /// an exception is throw. + /// + /// Expression statement + /// The expected return type. Use void or object type if there isn't an expected return type. + /// + /// + /// + public Lambda Parse(string expressionText, Type expressionType, params Parameter[] parameters) { - return Parse(expressionText, typeof(void), parameters); + return ParseAsLambda(expressionText, expressionType, parameters); } - public ParseResult Parse(string expressionText, Type expressionReturnType, params ParameterExpression[] parameters) + [Obsolete("Use ParseAsDelegate(string, params string[])")] + public TDelegate Parse(string expressionText, params string[] parametersNames) { - if (parameters == null) - parameters = new ParameterExpression[0]; + return ParseAsDelegate(expressionText, parametersNames); + } - var arguments = new ParserArguments( - expressionText, - _settings, - expressionReturnType, - parameters.Select(x => new Parameter(x))); + /// + /// Parse a text expression and convert it into a delegate. + /// + /// Delegate to use + /// Expression statement + /// Names of the parameters. If not specified the parameters names defined inside the delegate are used. + /// + /// + public TDelegate ParseAsDelegate(string expressionText, params string[] parametersNames) + { + var lambda = ParseAs(expressionText, parametersNames); + return lambda.Compile(); + } - var expression = Visitors.Aggregate(Parser.Parse(arguments), (current, visitor) => visitor.Visit(current)); + /// + /// Parse a text expression and convert it into a lambda expression. + /// + /// Delegate to use + /// Expression statement + /// Names of the parameters. If not specified the parameters names defined inside the delegate are used. + /// + /// + public Expression ParseAsExpression(string expressionText, params string[] parametersNames) + { + var lambda = ParseAs(expressionText, parametersNames); + return lambda.LambdaExpression(); + } - var lambda = new ParseResult( - expression: expression, - usedParameters: arguments.UsedParameters.Select(x => x.Expression), - declaredParameters: arguments.DeclaredParameters.Select(x => x.Expression), - types: arguments.UsedTypes, - identifiers: arguments.UsedIdentifiers); + internal LambdaExpression ParseAsExpression(Type delegateType, string expressionText, params string[] parametersNames) + { + var delegateInfo = ReflectionExtensions.GetDelegateInfo(delegateType, parametersNames); -#if TEST_DetectIdentifiers - AssertDetectIdentifiers(lambda); -#endif + // return type is object means that we have no information beforehand + // => we force it to typeof(void) so that no conversion expression is emitted by the parser + // and the actual expression type is preserved + var returnType = delegateInfo.ReturnType; + if (returnType == typeof(object)) + returnType = typeof(void); - return lambda; + var lambda = ParseAsLambda(expressionText, returnType, delegateInfo.Parameters); + return lambda.LambdaExpression(delegateType); } + public Lambda ParseAs(string expressionText, params string[] parametersNames) + { + return ParseAs(typeof(TDelegate), expressionText, parametersNames); + } + + internal Lambda ParseAs(Type delegateType, string expressionText, params string[] parametersNames) + { + var delegateInfo = ReflectionExtensions.GetDelegateInfo(delegateType, parametersNames); + + return ParseAsLambda(expressionText, delegateInfo.ReturnType, delegateInfo.Parameters); + } + #endregion + + #region Eval + /// + /// Parse and invoke the specified expression. + /// + /// + /// + /// + public object Eval(string expressionText, params Parameter[] parameters) + { + return Eval(expressionText, typeof(void), parameters); + } + + /// + /// Parse and invoke the specified expression. + /// + /// + /// + /// + public T Eval(string expressionText, params Parameter[] parameters) + { + return (T)Eval(expressionText, typeof(T), parameters); + } + + /// + /// Parse and invoke the specified expression. + /// + /// + /// The return type of the expression. Use void or object if you don't know the expected return type. + /// + /// + public object Eval(string expressionText, Type expressionType, params Parameter[] parameters) + { + return Parse(expressionText, expressionType, parameters).Invoke(parameters); + } #endregion #region Detection public IdentifiersInfo DetectIdentifiers(string expression) { - var detector = new Detector(_settings); + var detector = new Detector(_settings.ParserSettings); return detector.DetectIdentifiers(expression); } @@ -403,6 +384,28 @@ public IdentifiersInfo DetectIdentifiers(string expression) #region Private methods + private Lambda ParseAsLambda(string expressionText, Type expressionType, Parameter[] parameters) + { + var arguments = new ParserArguments( + expressionText, + _settings.ParserSettings, + expressionType, + parameters); + + var expression = Parser.Parse(arguments); + + foreach (var visitor in Visitors) + expression = visitor.Visit(expression); + + var lambda = new Lambda(expression, arguments); + +#if TEST_DetectIdentifiers + AssertDetectIdentifiers(lambda); +#endif + + return lambda; + } + #if TEST_DetectIdentifiers private void AssertDetectIdentifiers(Lambda lambda) { diff --git a/src/DynamicExpresso.Core/InterpreterSettings.cs b/src/DynamicExpresso.Core/InterpreterSettings.cs new file mode 100644 index 00000000..f0ffbb44 --- /dev/null +++ b/src/DynamicExpresso.Core/InterpreterSettings.cs @@ -0,0 +1,344 @@ +using DynamicExpresso.Parsing; +using DynamicExpresso.Visitors; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; + +namespace DynamicExpresso +{ + /// + /// Interpreter settings + /// + public class InterpreterSettings + { + public ParserSettings ParserSettings { get; } + + public HashSet Visitors { get; } = new HashSet(); + + #region Constructors + + /// + /// Creates a new InterpreterSettings using InterpreterOptions.Default. + /// + public InterpreterSettings() : this(InterpreterOptions.Default) + { + } + + /// + /// Creates a new InterpreterSettings using the specified options. + /// + /// + public InterpreterSettings(InterpreterOptions options) + { + var caseInsensitive = options.HasFlag(InterpreterOptions.CaseInsensitive); + + var lateBindObject = options.HasFlag(InterpreterOptions.LateBindObject); + + ParserSettings = new ParserSettings(caseInsensitive, lateBindObject); + + if ((options & InterpreterOptions.SystemKeywords) == InterpreterOptions.SystemKeywords) + { + SetIdentifiers(LanguageConstants.Literals); + } + + if ((options & InterpreterOptions.PrimitiveTypes) == InterpreterOptions.PrimitiveTypes) + { + Reference(LanguageConstants.PrimitiveTypes); + Reference(LanguageConstants.CSharpPrimitiveTypes); + } + + if ((options & InterpreterOptions.CommonTypes) == InterpreterOptions.CommonTypes) + { + Reference(LanguageConstants.CommonTypes); + } + + if ((options & InterpreterOptions.LambdaExpressions) == InterpreterOptions.LambdaExpressions) + { + ParserSettings.LambdaExpressions = true; + } + + Visitors.Add(new DisableReflectionVisitor()); + } + + /// + /// Create a new interpreter with the settings copied from another interpreter + /// + internal InterpreterSettings(ParserSettings settings) + { + ParserSettings = settings; + } + + #endregion + + #region Properties + + public bool CaseInsensitive + { + get + { + return ParserSettings.CaseInsensitive; + } + } + + /// + /// Gets a list of registeres types. Add types by using the Reference method. + /// + public IEnumerable ReferencedTypes + { + get + { + return ParserSettings.KnownTypes + .Select(p => p.Value) + .ToList(); + } + } + + /// + /// Gets a list of known identifiers. Add identifiers using SetVariable, SetFunction or SetExpression methods. + /// + public IEnumerable Identifiers + { + get + { + return ParserSettings.Identifiers + .Select(p => p.Value) + .ToList(); + } + } + + /// + /// Gets the available assignment operators. + /// + public AssignmentOperators AssignmentOperators + { + get { return ParserSettings.AssignmentOperators; } + } + + #endregion + + #region Options + + /// + /// Allow to set de default numeric type when no suffix is specified (Int by default, Double if real number) + /// + /// + /// + public InterpreterSettings SetDefaultNumberType(DefaultNumberType defaultNumberType) + { + ParserSettings.DefaultNumberType = defaultNumberType; + return this; + } + + /// + /// Allows to enable/disable assignment operators. + /// For security when expression are generated by the users is more safe to disable assignment operators. + /// + /// + /// + public InterpreterSettings EnableAssignment(AssignmentOperators assignmentOperators) + { + ParserSettings.AssignmentOperators = assignmentOperators; + return this; + } + + #endregion + + #region Visitors + + /// + /// Enable reflection expression (like x.GetType().GetMethod() or typeof(double).Assembly) by removing the DisableReflectionVisitor. + /// + /// + public InterpreterSettings EnableReflection() + { + var visitor = Visitors.FirstOrDefault(p => p is DisableReflectionVisitor); + if (visitor != null) + Visitors.Remove(visitor); + + return this; + } + + #endregion + + #region Register identifiers + + /// + /// Allow the specified function delegate to be called from a parsed expression. + /// + /// + /// + /// + public InterpreterSettings SetFunction(string name, Delegate value) + { + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentNullException(nameof(name)); + + if (ParserSettings.Identifiers.TryGetValue(name, out var identifier) && identifier is FunctionIdentifier fIdentifier) + { + fIdentifier.AddOverload(value); + } + else + { + SetIdentifier(new FunctionIdentifier(name, value)); + } + + return this; + } + + /// + /// Allow the specified variable to be used in a parsed expression. + /// + /// + /// + /// + public InterpreterSettings SetVariable(string name, object value) + { + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentNullException(nameof(name)); + + return SetExpression(name, Expression.Constant(value)); + } + + /// + /// Allow the specified variable to be used in a parsed expression. + /// + /// + /// + /// + public InterpreterSettings SetVariable(string name, T value) + { + return SetVariable(name, value, typeof(T)); + } + + /// + /// Allow the specified variable to be used in a parsed expression. + /// + /// + /// + /// + /// + public InterpreterSettings SetVariable(string name, object value, Type type) + { + if (type == null) + throw new ArgumentNullException(nameof(type)); + + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentNullException(nameof(name)); + + return SetExpression(name, Expression.Constant(value, type)); + } + + /// + /// Allow the specified Expression to be used in a parsed expression. + /// Basically add the specified expression as a known identifier. + /// + /// + /// + /// + public InterpreterSettings SetExpression(string name, Expression expression) + { + return SetIdentifier(new Identifier(name, expression)); + } + + /// + /// Allow the specified list of identifiers to be used in a parsed expression. + /// Basically add the specified expressions as a known identifier. + /// + /// + /// + public InterpreterSettings SetIdentifiers(IEnumerable identifiers) + { + foreach (var i in identifiers) + SetIdentifier(i); + + return this; + } + + /// + /// Allow the specified identifier to be used in a parsed expression. + /// Basically add the specified expression as a known identifier. + /// + /// + /// + public InterpreterSettings SetIdentifier(Identifier identifier) + { + if (identifier == null) + throw new ArgumentNullException(nameof(identifier)); + + if (LanguageConstants.ReservedKeywords.Contains(identifier.Name)) + throw new InvalidOperationException($"{identifier.Name} is a reserved word"); + + ParserSettings.Identifiers[identifier.Name] = identifier; + return this; + } + #endregion + + #region Register referenced types + /// + /// Allow the specified type to be used inside an expression. The type will be available using its name. + /// If the type contains method extensions methods they will be available inside expressions. + /// + /// + /// + public InterpreterSettings Reference(Type type) + { + if (type == null) + throw new ArgumentNullException(nameof(type)); + + return Reference(type, type.Name); + } + + /// + /// Allow the specified type to be used inside an expression. + /// See Reference(Type, string) method. + /// + /// + /// + public InterpreterSettings Reference(IEnumerable types) + { + if (types == null) + throw new ArgumentNullException(nameof(types)); + + foreach (var t in types) + Reference(t); + + return this; + } + + /// + /// Allow the specified type to be used inside an expression by using a custom alias. + /// If the type contains extensions methods they will be available inside expressions. + /// + /// + /// Public name that must be used in the expression. + /// + public InterpreterSettings Reference(Type type, string typeName) + { + return Reference(new ReferenceType(typeName, type)); + } + + /// + /// Allow the specified type to be used inside an expression by using a custom alias. + /// If the type contains extensions methods they will be available inside expressions. + /// + /// + /// + public InterpreterSettings Reference(ReferenceType type) + { + if (type == null) + throw new ArgumentNullException(nameof(type)); + + ParserSettings.KnownTypes[type.Name] = type; + + foreach (var extensionMethod in type.ExtensionMethods) + { + ParserSettings.ExtensionMethods.Add(extensionMethod); + } + + return this; + } + + #endregion + } +} diff --git a/src/DynamicExpresso.Core/LambdaExtensions.cs b/src/DynamicExpresso.Core/LambdaExtensions.cs index 665fb530..25a34b1f 100644 --- a/src/DynamicExpresso.Core/LambdaExtensions.cs +++ b/src/DynamicExpresso.Core/LambdaExtensions.cs @@ -48,12 +48,42 @@ public static LambdaExpression AsLambdaExpression(this ParseResult parseResult, return Expression.Lambda(delegateType, parseResult.Expression, parseResult.DeclaredParameters.ToArray()); } - public static object Eval(this Interpreter interpreter, string expression, params Parameter[] args) + public static object Eval(this ExpressionInterpreter interpreter, string expression, + Expression> a1) + => interpreter.Eval(expression, a1.Value()); + + public static object Eval(this ExpressionInterpreter interpreter, string expression, + Expression> a1, + Expression> a2) + => interpreter.Eval(expression, a1.Value(), a2.Value()); + + public static object Eval(this ExpressionInterpreter interpreter, string expression, + Expression> a1, + Expression> a2, + Expression> a3) + => interpreter.Eval(expression, a1.Value(), a2.Value(), a3.Value()); + + public static object Eval(this ExpressionInterpreter interpreter, string expression, + Expression> a1, + Expression> a2, + Expression> a3, + Expression> a4) + => interpreter.Eval(expression, a1.Value(), a2.Value(), a3.Value(), a4.Value()); + + private static Parameter Value(this Expression> parameter) + { + return new Parameter( + parameter.Parameters.First().Name, + parameter.ReturnType, + ((ConstantExpression)parameter.Body).Value); + } + + public static object Eval(this ExpressionInterpreter interpreter, string expression, params Parameter[] args) { try { return interpreter - .Parse(expression, args.Select(x => x.Expression).ToArray()) + .Parse(expression, args.Select(x => Expression.Parameter(x.Type, x.Name)).ToArray()) .Compile() .DynamicInvoke(args.Select(x => x.Value).ToArray()); } @@ -65,10 +95,5 @@ public static object Eval(this Interpreter interpreter, string expression, param throw; } } - - public static TReturnType Eval(this Interpreter interpreter, string expression, params Parameter[] args) - { - return (TReturnType) Eval(interpreter, expression, args); - } } } diff --git a/src/DynamicExpresso.Core/Parameter.cs b/src/DynamicExpresso.Core/Parameter.cs index 03b0c4e6..5792d696 100644 --- a/src/DynamicExpresso.Core/Parameter.cs +++ b/src/DynamicExpresso.Core/Parameter.cs @@ -38,6 +38,11 @@ public Parameter(string name, Type type, object value = null) Expression = System.Linq.Expressions.Expression.Parameter(type, name); } + public static Parameter Is(string name, T value) + { + return new Parameter(name, typeof(T), value); + } + public string Name { get; private set; } public Type Type { get; private set; } public object Value { get; private set; } diff --git a/src/DynamicExpresso.Core/Parsing/Parser.cs b/src/DynamicExpresso.Core/Parsing/Parser.cs index cd6e9515..ac10beff 100644 --- a/src/DynamicExpresso.Core/Parsing/Parser.cs +++ b/src/DynamicExpresso.Core/Parsing/Parser.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; +using System.Runtime.CompilerServices; using System.Text; using DynamicExpresso.Exceptions; using DynamicExpresso.Resources; @@ -1018,7 +1019,7 @@ private Expression ParseIdentifier() { return ParseTypeKeyword(knownType); } - + // Working context implementation //if (it != null) // return ParseMemberAccess(null, it); @@ -1249,7 +1250,7 @@ private Type ParseKnownType() private bool TryParseKnownType(string name, out Type type) { - // if the type is unknown, we need to restart parsing + // if the type is unknown, we need to restart parsing var originalPos = _token.pos; _arguments.TryGetKnownType(name, out type); @@ -2354,7 +2355,7 @@ private static bool IsWritable(Expression expression) } case ExpressionType.Parameter: return true; - + } return false; @@ -2489,7 +2490,7 @@ private Expression GenerateGreaterThan(Expression left, Expression right) return GenerateBinary(ExpressionType.GreaterThan, left, right); } - + private Expression GenerateGreaterThanEqual(Expression left, Expression right) { @@ -3082,7 +3083,7 @@ private static Expression GenerateNullableTypeConversion(Expression expr) } /// - /// Expression that wraps over an interpreter. This is used when parsing a lambda expression + /// Expression that wraps over an interpreter. This is used when parsing a lambda expression /// definition, because we don't know the parameters type before resolution. /// private class InterpreterExpression : Expression @@ -3121,9 +3122,9 @@ public override Type Type internal LambdaExpression EvalAs(Type delegateType) { - var parsed = _interpreter.Parse(_expressionText, delegateType, _parameters.Select(p => p.Expression).ToArray()); - _type = parsed.Expression.Type; - return parsed.AsLambdaExpression(delegateType); + var lambdaExpr = _interpreter.ParseAsExpression(delegateType, _expressionText, _parameters.Select(p => p.Name).ToArray()); + _type = lambdaExpr.Type; + return lambdaExpr; } internal bool IsCompatibleWithDelegate(Type target) diff --git a/src/DynamicExpresso.Core/Parsing/ParserSettings.cs b/src/DynamicExpresso.Core/Parsing/ParserSettings.cs index 042f14fc..7f4c4a1b 100644 --- a/src/DynamicExpresso.Core/Parsing/ParserSettings.cs +++ b/src/DynamicExpresso.Core/Parsing/ParserSettings.cs @@ -4,7 +4,7 @@ namespace DynamicExpresso.Parsing { - internal class ParserSettings + public class ParserSettings { private readonly Dictionary _identifiers; private readonly Dictionary _knownTypes; diff --git a/test/DynamicExpresso.UnitTest/CollectionHelperTests.cs b/test/DynamicExpresso.UnitTest/CollectionHelperTests.cs index 601ddf32..e97f538a 100644 --- a/test/DynamicExpresso.UnitTest/CollectionHelperTests.cs +++ b/test/DynamicExpresso.UnitTest/CollectionHelperTests.cs @@ -36,7 +36,7 @@ public CollectionHelper() public IEnumerable Where(IEnumerable values, string expression) { - var predicate = _interpreter.Parse>(expression, "x").Compile(); + var predicate = _interpreter.ParseAsDelegate>(expression, "x"); return values.Where(predicate); } diff --git a/test/DynamicExpresso.UnitTest/DynamicTest.cs b/test/DynamicExpresso.UnitTest/DynamicTest.cs index e492f06e..e567fc49 100644 --- a/test/DynamicExpresso.UnitTest/DynamicTest.cs +++ b/test/DynamicExpresso.UnitTest/DynamicTest.cs @@ -5,7 +5,6 @@ using System.Linq.Expressions; using System.Collections.Generic; using DynamicExpresso.Exceptions; -using DynamicExpresso; namespace DynamicExpresso.UnitTest { @@ -71,7 +70,7 @@ public void Invoke_Method_of_an_ExpandoObject() var interpreter = new Interpreter() .SetVariable("dyn", dyn); - Assert.AreEqual(dyn.Foo(), LambdaExtensions.Eval(interpreter, "dyn.Foo()")); + Assert.AreEqual(dyn.Foo(), interpreter.Eval("dyn.Foo()")); } [Test] @@ -84,7 +83,7 @@ public void Invoke_Method_of_a_nested_ExpandoObject() var interpreter = new Interpreter() .SetVariable("dyn", dyn); - Assert.AreEqual(dyn.Sub.Foo(), LambdaExtensions.Eval(interpreter, "dyn.Sub.Foo()")); + Assert.AreEqual(dyn.Sub.Foo(), interpreter.Eval("dyn.Sub.Foo()")); } [Test] @@ -170,7 +169,7 @@ public void Test_With_Dynamic_Object_By_Index_Access() DynamicIndexAccess globals = new DynamicIndexAccess(); Interpreter interpreter = new Interpreter() .SetVariable("Values", new DynamicIndexAccess()); - + Assert.AreEqual(globals.Values["Hello"], interpreter.Eval("Values[\"Hello\"]")); } @@ -308,22 +307,22 @@ public class ClassWithObjectProperties [Test] public void ObjectLateBinding() { - + var classWithObjectProperties = new ClassWithObjectProperties(); - + Assert.Throws(() => { var noLateBindingInterpreter = new Interpreter(); - var noLateBindingDel = noLateBindingInterpreter.Parse>("d.Foo+d.Bar()", "d").Compile(); + var noLateBindingDel = noLateBindingInterpreter.ParseAsDelegate>("d.Foo+d.Bar()", new[] { "d" }); var noLateBindingResult = noLateBindingDel.Invoke(classWithObjectProperties); }); var lateBindingInterpreter = new Interpreter(InterpreterOptions.Default|InterpreterOptions.LateBindObject); - var lateBindingInterpreterDel = lateBindingInterpreter.Parse>("d.Foo+d.Bar()", "d").Compile(); + var lateBindingInterpreterDel = lateBindingInterpreter.ParseAsDelegate>("d.Foo+d.Bar()", new[] { "d" }); var lateBindingResult = lateBindingInterpreterDel.Invoke(classWithObjectProperties); Assert.AreEqual((dynamic)classWithObjectProperties.Foo + (dynamic)classWithObjectProperties.Bar(), lateBindingResult); @@ -334,7 +333,7 @@ public void ObjectLateBinding() Assert.AreEqual((dynamic)classWithObjectProperties.Foo + (dynamic)classWithObjectProperties.Bar(), evalResult); } - + [Test] public void Bitwise_with_dynamic_properties() @@ -353,7 +352,7 @@ public void Bitwise_with_dynamic_properties() Assert.AreEqual(dyn.Foo ^ 500, interpreter.Eval("dyn.Foo ^ 500")); Assert.AreEqual(500 ^ dyn.Foo, interpreter.Eval("500 ^ dyn.Foo")); - + } [Test] @@ -391,13 +390,13 @@ public override bool TryGetMember(GetMemberBinder binder, out object result) } } - public class DynamicIndexAccess : DynamicObject + public class DynamicIndexAccess : DynamicObject { - public dynamic Values - { - get - { - return _values; + public dynamic Values + { + get + { + return _values; } } private readonly IReadOnlyDictionary _values; diff --git a/test/DynamicExpresso.UnitTest/ExpressionTypeTest.cs b/test/DynamicExpresso.UnitTest/ExpressionTypeTest.cs index fd2b73d2..82bf097e 100644 --- a/test/DynamicExpresso.UnitTest/ExpressionTypeTest.cs +++ b/test/DynamicExpresso.UnitTest/ExpressionTypeTest.cs @@ -11,10 +11,10 @@ public void If_no_expression_type_is_specified_the_return_type_is_inferred() { var target = new Interpreter(); - Assert.AreEqual(typeof(string), target.Parse("\"ciao\"").Expression.Type); - Assert.AreEqual(typeof(int), target.Parse("45").Expression.Type); - Assert.AreEqual(typeof(double), target.Parse("45.4").Expression.Type); - Assert.AreEqual(typeof(object), target.Parse("null").Expression.Type); + Assert.AreEqual(typeof(string), target.Parse("\"ciao\"").ReturnType); + Assert.AreEqual(typeof(int), target.Parse("45").ReturnType); + Assert.AreEqual(typeof(double), target.Parse("45.4").ReturnType); + Assert.AreEqual(typeof(object), target.Parse("null").ReturnType); } [Test] @@ -23,10 +23,10 @@ public void If_expression_type_doesn_t_match_a_conversion_is_performed_when_poss var target = new Interpreter(); var expressionType = typeof(double); - var parseResult = target.Parse("213", expressionType); + var lambda = target.Parse("213", expressionType); - Assert.AreEqual(expressionType, parseResult.Expression.Type); - Assert.AreEqual((double)213, parseResult.Compile().DynamicInvoke()); + Assert.AreEqual(expressionType, lambda.ReturnType); + Assert.AreEqual((double)213, lambda.Invoke()); } [Test] @@ -35,10 +35,10 @@ public void If_expression_type_doesn_t_match_a_conversion_is_performed_eventuall var target = new Interpreter(); var expressionType = typeof(int); - var parseResult = target.Parse("213.46", expressionType); + var lambda = target.Parse("213.46", expressionType); - Assert.AreEqual(expressionType, parseResult.Expression.Type); - Assert.AreEqual((int)213.46, parseResult.Compile().DynamicInvoke()); + Assert.AreEqual(expressionType, lambda.ReturnType); + Assert.AreEqual((int)213.46, lambda.Invoke()); } [Test] @@ -46,13 +46,13 @@ public void Can_convert_a_null_expression_to_any_reference_type() { var target = new Interpreter(); - var parseResult = target.Parse("null", typeof(string)); - Assert.AreEqual(typeof(string), parseResult.Expression.Type); - Assert.IsNull(parseResult.Compile().DynamicInvoke()); + var lambda = target.Parse("null", typeof(string)); + Assert.AreEqual(typeof(string), lambda.ReturnType); + Assert.IsNull(lambda.Invoke()); - parseResult = target.Parse("null", typeof(TestReferenceType)); - Assert.AreEqual(typeof(TestReferenceType), parseResult.Expression.Type); - Assert.IsNull(parseResult.Compile().DynamicInvoke()); + lambda = target.Parse("null", typeof(TestReferenceType)); + Assert.AreEqual(typeof(TestReferenceType), lambda.ReturnType); + Assert.IsNull(lambda.Invoke()); } [Test] @@ -60,13 +60,13 @@ public void Can_convert_a_null_expression_to_any_nullable_type() { var target = new Interpreter(); - var parseResult = target.Parse("null", typeof(int?)); - Assert.AreEqual(typeof(int?), parseResult.Expression.Type); - Assert.IsNull(parseResult.Compile().DynamicInvoke()); + var lambda = target.Parse("null", typeof(int?)); + Assert.AreEqual(typeof(int?), lambda.ReturnType); + Assert.IsNull(lambda.Invoke()); - parseResult = target.Parse("null", typeof(DateTime?)); - Assert.AreEqual(typeof(DateTime?), parseResult.Expression.Type); - Assert.IsNull(parseResult.Compile().DynamicInvoke()); + lambda = target.Parse("null", typeof(DateTime?)); + Assert.AreEqual(typeof(DateTime?), lambda.ReturnType); + Assert.IsNull(lambda.Invoke()); } [Test] @@ -74,13 +74,13 @@ public void A_nullable_type_can_be_a_value_or_null() { var target = new Interpreter(); - var parseResult = target.Parse("null", typeof(int?)); - Assert.AreEqual(typeof(int?), parseResult.Expression.Type); - Assert.IsNull(parseResult.Compile().DynamicInvoke()); + var lambda = target.Parse("null", typeof(int?)); + Assert.AreEqual(typeof(int?), lambda.ReturnType); + Assert.IsNull(lambda.Invoke()); - parseResult = target.Parse("4651", typeof(int?)); - Assert.AreEqual(typeof(int?), parseResult.Expression.Type); - Assert.AreEqual(4651, parseResult.Compile().DynamicInvoke()); + lambda = target.Parse("4651", typeof(int?)); + Assert.AreEqual(typeof(int?), lambda.ReturnType); + Assert.AreEqual(4651, lambda.Invoke()); } [Test] diff --git a/test/DynamicExpresso.UnitTest/GenerateDelegatesTest.cs b/test/DynamicExpresso.UnitTest/GenerateDelegatesTest.cs index 13f295aa..b25ef6c8 100644 --- a/test/DynamicExpresso.UnitTest/GenerateDelegatesTest.cs +++ b/test/DynamicExpresso.UnitTest/GenerateDelegatesTest.cs @@ -12,11 +12,11 @@ public void Parse_To_a_Delegate() { var target = new Interpreter(); - var func = target.Parse>("Math.Pow(x, y) + 5", "x", "y").Compile(); + var func = target.ParseAsDelegate>("Math.Pow(x, y) + 5", "x", "y"); Assert.AreEqual(Math.Pow(10, 2) + 5, func(10, 2)); - func = target.Parse>("Math.Pow(x, y) + .5", "x", "y").Compile(); + func = target.ParseAsDelegate>("Math.Pow(x, y) + .5", "x", "y"); Assert.AreEqual(Math.Pow(10, 2) + .5, func(10, 2)); } @@ -25,7 +25,7 @@ public void Parse_To_a_Delegate_With_No_Parameters() { var target = new Interpreter(); - var func = target.Parse>("50").Compile(); + var func = target.ParseAsDelegate>("50"); Assert.AreEqual(50, func()); } @@ -35,7 +35,7 @@ public void Parse_To_a_Delegate_With_One_Parameter() { var target = new Interpreter(); - var func = target.Parse>("arg.Length").Compile(); + var func = target.ParseAsDelegate>("arg.Length"); Assert.AreEqual(4, func("ciao")); Assert.AreEqual(9, func("123456879")); @@ -47,7 +47,7 @@ public void Parse_To_a_Delegate_With_One_Parameter_With_Custom_Name() var target = new Interpreter(); var argumentName = "val"; // if not specified the delegate parameter is used which is "arg" - var func = target.Parse>("val.Length", argumentName).Compile(); + var func = target.ParseAsDelegate>("val.Length", argumentName); Assert.AreEqual(4, func("ciao")); Assert.AreEqual(9, func("123456879")); @@ -58,7 +58,7 @@ public void Parse_To_a_Delegate_With_Two_Parameters() { var target = new Interpreter(); - var func = target.Parse>("arg1 * arg2").Compile(); + var func = target.ParseAsDelegate>("arg1 * arg2"); Assert.AreEqual(6, func(3, 2)); Assert.AreEqual(50, func(5, 10)); @@ -70,7 +70,7 @@ public void Parse_To_a_Delegate_With_Two_Parameters_With_Custom_Name() var target = new Interpreter(); var argumentNames = new [] { "x", "y" }; - var func = target.Parse>("x * y", argumentNames).Compile(); + var func = target.ParseAsDelegate>("x * y", argumentNames); Assert.AreEqual(6, func(3, 2)); Assert.AreEqual(50, func(5, 10)); @@ -81,7 +81,7 @@ public void Parse_To_a_Custom_Delegate() { var target = new Interpreter(); - var func = target.Parse("x + y.Length").Compile(); + var func = target.ParseAsDelegate("x + y.Length"); Assert.AreEqual(7, func(3, "ciao")); Assert.AreEqual(10, func(5, "mondo")); @@ -95,7 +95,7 @@ public void Return_Type_Mismatch_Cause_An_Exception() var target = new Interpreter(); // expected a double but I return a string - Assert.Throws(() => target.Parse>("\"ciao\"")); + Assert.Throws(() => target.ParseAsDelegate>("\"ciao\"")); } } } diff --git a/test/DynamicExpresso.UnitTest/GenerateLambdaTest.cs b/test/DynamicExpresso.UnitTest/GenerateLambdaTest.cs index d6373360..52a3c9fc 100644 --- a/test/DynamicExpresso.UnitTest/GenerateLambdaTest.cs +++ b/test/DynamicExpresso.UnitTest/GenerateLambdaTest.cs @@ -12,7 +12,7 @@ public void Parse_as_LambdaExpression() { var target = new Interpreter(); - var lambdaExpression = target.Parse>("arg + 5").AsExpression(); + Expression> lambdaExpression = target.ParseAsExpression>("arg + 5"); Assert.AreEqual(15, lambdaExpression.Compile()(10)); } @@ -22,11 +22,11 @@ public void Parse_as_LambdaExpression_with_parameter() { var target = new Interpreter(); - var lambdaExpression = target.Parse>("arg + 5").AsExpression(); + Expression> lambdaExpression = target.ParseAsExpression>("arg + 5"); Assert.AreEqual(15, lambdaExpression.Compile()(10)); - lambdaExpression = target.Parse>("arg + .5").AsExpression(); + lambdaExpression = target.ParseAsExpression>("arg + .5"); Assert.AreEqual(10.5, lambdaExpression.Compile()(10)); } @@ -35,7 +35,7 @@ public void Parse_To_a_Delegate_With_Two_Parameters() { var target = new Interpreter(); - var lambdaExpression = target.Parse>("arg1 * arg2").AsExpression(); + var lambdaExpression = target.ParseAsExpression>("arg1 * arg2"); Assert.AreEqual(6, lambdaExpression.Compile()(3, 2)); Assert.AreEqual(50, lambdaExpression.Compile()(5, 10)); @@ -46,8 +46,8 @@ public void Parse_To_a_Delegate_With_Two_Parameters_With_Custom_Name() { var target = new Interpreter(); - var argumentNames = new[] { "x", "y" }; - var lambdaExpression = target.Parse>("x * y", argumentNames).AsExpression(); + var argumentNames = new string[] { "x", "y" }; + var lambdaExpression = target.ParseAsExpression>("x * y", argumentNames); Assert.AreEqual(6, lambdaExpression.Compile()(3, 2)); Assert.AreEqual(50, lambdaExpression.Compile()(5, 10)); @@ -58,12 +58,12 @@ public void Generate_a_LambdaExpression_From_Lambda() { var target = new Interpreter(); - var parseResult = target.Parse("Math.Pow(x, y) + 5", - Expression.Parameter(typeof(double), "x"), - Expression.Parameter(typeof(double), "y") + var lambda = target.Parse("Math.Pow(x, y) + 5", + new Parameter("x", typeof(double)), + new Parameter("y", typeof(double)) ); - var lambdaExpression = parseResult.AsExpression>(); + Expression> lambdaExpression = lambda.LambdaExpression>(); Assert.AreEqual(Math.Pow(10, 2) + 5, lambdaExpression.Compile()(10, 2)); } @@ -75,9 +75,11 @@ public void Cannot_Generate_a_LambdaExpression_From_Lambda_with_parameters_count // Func delegate has 2 inputs, I just use one - var parseResult = target.Parse("x + 5", Expression.Parameter(typeof(double), "x")); + var lambda = target.Parse("x + 5", + new Parameter("x", typeof(double)) + ); - Assert.Throws(() => parseResult.AsExpression>()); + Assert.Throws(() => lambda.LambdaExpression>()); } [Test] @@ -87,9 +89,11 @@ public void Cannot_Generate_a_LambdaExpression_From_Lambda_with_parameters_type_ // Func delegate takes a string, I pass a double - var parseResult = target.Parse("x + 5", Expression.Parameter(typeof(double), "x")); + var lambda = target.Parse("x + 5", + new Parameter("x", typeof(double)) + ); - Assert.Throws(() => parseResult.AsExpression>()); + Assert.Throws(() => lambda.LambdaExpression>()); } [Test] @@ -99,9 +103,11 @@ public void Cannot_generate_a_Lambda_with_return_type_mismatch() // Func delegate returns a string, I return a double - var parseResult = target.Parse("x + 5", Expression.Parameter(typeof(double), "x")); + var lambda = target.Parse("x + 5", + new Parameter("x", typeof(double)) + ); - Assert.Throws(() => parseResult.AsExpression>()); + Assert.Throws(() => lambda.LambdaExpression>()); } } diff --git a/test/DynamicExpresso.UnitTest/GithubIssues.cs b/test/DynamicExpresso.UnitTest/GithubIssues.cs index 5f651b9c..27ea76a3 100644 --- a/test/DynamicExpresso.UnitTest/GithubIssues.cs +++ b/test/DynamicExpresso.UnitTest/GithubIssues.cs @@ -4,7 +4,6 @@ using System.Collections; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; using System.Reflection; // ReSharper disable SpecifyACultureInStringConversionExplicitly @@ -222,7 +221,7 @@ static bool GetGFunction2(string arg = null) Assert.Throws(() => interpreter.Eval("GFunction(arg)")); // there should be an ambiguous call exception, but GFunction1 is used - // because gFunc1.Method.GetParameters()[0].HasDefaultValue == true + // because gFunc1.Method.GetParameters()[0].HasDefaultValue == true // and gFunc2.Method.GetParameters()[0].HasDefaultValue == false Assert.False((bool)interpreter.Eval("GFunction()")); } @@ -251,12 +250,12 @@ public void GitHub_Issue_164_bis() { var interpreter = new Interpreter(); - var lambda = interpreter.Parse("Scope?.ValueInt", Expression.Parameter(typeof(Scope), "Scope")).Compile(); + var lambda = interpreter.Parse("Scope?.ValueInt", new Parameter("Scope", typeof(Scope))); - var result = lambda.DynamicInvoke((Scope)null); + var result = lambda.Invoke((Scope)null); Assert.IsNull(result); - result = lambda.DynamicInvoke(new Scope { ValueInt = 5 }); + result = lambda.Invoke(new Scope { ValueInt = 5 }); Assert.AreEqual(5, result); var scope = new Scope { Value = 5 }; @@ -281,18 +280,18 @@ public void GitHub_Issue_169() { var interpreter = new Interpreter(); - var lambda = interpreter.Parse("Scope?.Value", Expression.Parameter(typeof(Scope), "Scope")).Compile(); + var lambda = interpreter.Parse("Scope?.Value", new Parameter("Scope", typeof(Scope))); - var result = lambda.DynamicInvoke((Scope)null); + var result = lambda.Invoke((Scope)null); Assert.IsNull(result); - result = lambda.DynamicInvoke(new Scope()); + result = lambda.Invoke(new Scope()); Assert.IsNull(result); - result = lambda.DynamicInvoke(new Scope { Value = null }); + result = lambda.Invoke(new Scope { Value = null }); Assert.IsNull(result); - result = lambda.DynamicInvoke(new Scope { Value = 5 }); + result = lambda.Invoke(new Scope { Value = 5 }); Assert.AreEqual(5, result); } @@ -301,15 +300,15 @@ public void GitHub_Issue_169_bis() { var interpreter = new Interpreter(); - var lambda = interpreter.Parse("Scope?.Arr?[0]", Expression.Parameter(typeof(Scope), "Scope")).Compile(); + var lambda = interpreter.Parse("Scope?.Arr?[0]", new Parameter("Scope", typeof(Scope))); - var result = lambda.DynamicInvoke(new Scope()); + var result = lambda.Invoke(new Scope()); Assert.IsNull(result); - result = lambda.DynamicInvoke(new Scope { Arr = null }); + result = lambda.Invoke(new Scope { Arr = null }); Assert.IsNull(result); - result = lambda.DynamicInvoke(new Scope { Arr = new int?[] { 5 } }); + result = lambda.Invoke(new Scope { Arr = new int?[] { 5 } }); Assert.AreEqual(5, result); } @@ -318,12 +317,12 @@ public void GitHub_Issue_169_ter() { var interpreter = new Interpreter(); - var lambda = interpreter.Parse("Scope?.ArrInt?[0]", Expression.Parameter(typeof(Scope), "Scope")).Compile(); + var lambda = interpreter.Parse("Scope?.ArrInt?[0]", new Parameter("Scope", typeof(Scope))); - var result = lambda.DynamicInvoke(new Scope()); + var result = lambda.Invoke(new Scope()); Assert.IsNull(result); - result = lambda.DynamicInvoke(new Scope { ArrInt = new int[] { 5 } }); + result = lambda.Invoke(new Scope { ArrInt = new int[] { 5 } }); Assert.AreEqual(5, result); interpreter.SetVariable("scope", new Scope { ArrInt = new int[] { 5 } }); @@ -355,17 +354,17 @@ public void GitHub_Issue_197() var interpreterWithoutLambdas = new Interpreter(InterpreterOptions.DefaultCaseInsensitive); var stringExpression = "booleanValue ? someStringValue : \".\""; - var parameters = new [] + var parameters = new List { - Expression.Parameter(typeof(string), "someStringValue"), - Expression.Parameter(typeof(bool), "booleanValue") + new Parameter($"someStringValue", typeof(string), $"E33"), + new Parameter("booleanValue", typeof(bool), true) }; - var expressionWithoutLambdas = interpreterWithoutLambdas.Parse(stringExpression, typeof(void), parameters.ToArray()).Compile(); - Assert.AreEqual("E33", expressionWithoutLambdas.DynamicInvoke("E33", true)); + var expressionWithoutLambdas = interpreterWithoutLambdas.Parse(stringExpression, typeof(void), parameters.ToArray()); + Assert.AreEqual("E33", expressionWithoutLambdas.Invoke(parameters.ToArray())); - var expressionWithLambdas = interpreterWithLambdas.Parse(stringExpression, typeof(void), parameters.ToArray()).Compile(); - Assert.AreEqual("E33", expressionWithLambdas.DynamicInvoke("E33", true)); + var expressionWithLambdas = interpreterWithLambdas.Parse(stringExpression, typeof(void), parameters.ToArray()); + Assert.AreEqual("E33", expressionWithLambdas.Invoke(parameters.ToArray())); } [Test] @@ -375,7 +374,7 @@ public void GitHub_Issue_185() // forcing the return type to object should work // (ie a conversion expression should be emitted from long to object) - var del = interpreter.Parse>("a*2").Compile(); + var del = interpreter.ParseAsDelegate>("a*2"); var result = del(); Assert.AreEqual(246, result); } @@ -384,7 +383,7 @@ public void GitHub_Issue_185() public void GitHub_Issue_185_2() { var interpreter = new Interpreter().SetVariable("a", 123L); - var del = interpreter.Parse>("a*2").Compile(); + var del = interpreter.ParseAsDelegate>("a*2"); var result = del(); Assert.AreEqual(246, result); } diff --git a/test/DynamicExpresso.UnitTest/IdentifiersTest.cs b/test/DynamicExpresso.UnitTest/IdentifiersTest.cs index 2aced5dc..1edc1f83 100644 --- a/test/DynamicExpresso.UnitTest/IdentifiersTest.cs +++ b/test/DynamicExpresso.UnitTest/IdentifiersTest.cs @@ -1,5 +1,4 @@ using System.Linq; -using System.Linq.Expressions; using NUnit.Framework; namespace DynamicExpresso.UnitTest @@ -30,11 +29,10 @@ public void Registered_custom_identifiers_are_saved_inside_the_interpreter() [Test] public void Getting_the_list_of_used_identifiers() { - var target = new Interpreter().SetVariable("x", 23); + var target = new Interpreter() + .SetVariable("x", 23); - var lambda = target.Parse("x > a || true == b", - Expression.Parameter(typeof(int), "a"), - Expression.Parameter(typeof(bool), "b")); + var lambda = target.Parse("x > a || true == b", new Parameter("a", 1), new Parameter("b", false)); Assert.AreEqual(2, lambda.Identifiers.Count()); Assert.AreEqual("x", lambda.Identifiers.ElementAt(0).Name); diff --git a/test/DynamicExpresso.UnitTest/LambdaExpressionTest.cs b/test/DynamicExpresso.UnitTest/LambdaExpressionTest.cs index b55d4dbd..ba021abb 100644 --- a/test/DynamicExpresso.UnitTest/LambdaExpressionTest.cs +++ b/test/DynamicExpresso.UnitTest/LambdaExpressionTest.cs @@ -28,9 +28,9 @@ public void Check_Lambda_Return_Type() var list = new List { "abc", "dfe", "test" }; target.SetVariable("list", list); - var parseResult = target.Parse("list.Select(str => str.Length)"); + var lambda = target.Parse("list.Select(str => str.Length)"); - Assert.AreEqual(typeof(IEnumerable), parseResult.Expression.Type); + Assert.AreEqual(typeof(IEnumerable), lambda.ReturnType); } [Test] diff --git a/test/DynamicExpresso.UnitTest/LiteralsTest.cs b/test/DynamicExpresso.UnitTest/LiteralsTest.cs index de3a7645..60165aea 100644 --- a/test/DynamicExpresso.UnitTest/LiteralsTest.cs +++ b/test/DynamicExpresso.UnitTest/LiteralsTest.cs @@ -91,7 +91,7 @@ public void Numeric_Literals_DefaultTypes() public void Numeric_Literals_DefaultLong() { var target = new Interpreter(); - + target.SetDefaultNumberType(DefaultNumberType.Long); Assert.IsInstanceOf(typeof(System.Int64), target.Eval("45")); @@ -276,7 +276,7 @@ public void Numeric_Literals_DefaultDecimal() Assert.AreEqual(.201M, target.Eval(".201")); Assert.AreEqual(-.201M, target.Eval("-.201")); Assert.AreEqual(+.201M, target.Eval("+.201")); - + // f suffix (single) Assert.AreEqual(4f, target.Eval("4f")); @@ -529,37 +529,37 @@ public void Should_Understand_ReturnType_Of_Literals() { var target = new Interpreter(); - Assert.AreEqual(typeof(string), target.Parse("\"some string\"").Expression.Type); - Assert.AreEqual(typeof(string), target.Parse("\"\"").Expression.Type); - Assert.AreEqual(typeof(int), target.Parse("234").Expression.Type); - Assert.AreEqual(typeof(int), target.Parse("-234").Expression.Type); - Assert.AreEqual(typeof(uint), target.Parse("123u").Expression.Type); - Assert.AreEqual(typeof(uint), target.Parse("123U").Expression.Type); - Assert.AreEqual(typeof(long), target.Parse("-123l").Expression.Type); - Assert.AreEqual(typeof(long), target.Parse("123l").Expression.Type); - Assert.AreEqual(typeof(long), target.Parse("123L").Expression.Type); - Assert.AreEqual(typeof(ulong), target.Parse("123UL").Expression.Type); - Assert.AreEqual(typeof(ulong), target.Parse("123Ul").Expression.Type); - Assert.AreEqual(typeof(ulong), target.Parse("123uL").Expression.Type); - Assert.AreEqual(typeof(ulong), target.Parse("123ul").Expression.Type); - Assert.AreEqual(typeof(ulong), target.Parse("123LU").Expression.Type); - Assert.AreEqual(typeof(ulong), target.Parse("123Lu").Expression.Type); - Assert.AreEqual(typeof(ulong), target.Parse("123lU").Expression.Type); - Assert.AreEqual(typeof(ulong), target.Parse("123lu").Expression.Type); - Assert.AreEqual(typeof(double), target.Parse("234.54").Expression.Type); - Assert.AreEqual(typeof(double), target.Parse(".9").Expression.Type); - Assert.AreEqual(typeof(double), target.Parse("-.9").Expression.Type); - Assert.AreEqual(typeof(double), target.Parse("234d").Expression.Type); - Assert.AreEqual(typeof(double), target.Parse("234D").Expression.Type); - Assert.AreEqual(typeof(float), target.Parse("4.5f").Expression.Type); - Assert.AreEqual(typeof(float), target.Parse("4.5F").Expression.Type); - Assert.AreEqual(typeof(float), target.Parse(".5f").Expression.Type); - Assert.AreEqual(typeof(float), target.Parse(".5F").Expression.Type); - Assert.AreEqual(typeof(decimal), target.Parse("234.48m").Expression.Type); - Assert.AreEqual(typeof(decimal), target.Parse("234.48M").Expression.Type); - Assert.AreEqual(typeof(decimal), target.Parse(".48m").Expression.Type); - Assert.AreEqual(typeof(decimal), target.Parse(".48M").Expression.Type); - Assert.AreEqual(typeof(object), target.Parse("null").Expression.Type); + Assert.AreEqual(typeof(string), target.Parse("\"some string\"").ReturnType); + Assert.AreEqual(typeof(string), target.Parse("\"\"").ReturnType); + Assert.AreEqual(typeof(int), target.Parse("234").ReturnType); + Assert.AreEqual(typeof(int), target.Parse("-234").ReturnType); + Assert.AreEqual(typeof(uint), target.Parse("123u").ReturnType); + Assert.AreEqual(typeof(uint), target.Parse("123U").ReturnType); + Assert.AreEqual(typeof(long), target.Parse("-123l").ReturnType); + Assert.AreEqual(typeof(long), target.Parse("123l").ReturnType); + Assert.AreEqual(typeof(long), target.Parse("123L").ReturnType); + Assert.AreEqual(typeof(ulong), target.Parse("123UL").ReturnType); + Assert.AreEqual(typeof(ulong), target.Parse("123Ul").ReturnType); + Assert.AreEqual(typeof(ulong), target.Parse("123uL").ReturnType); + Assert.AreEqual(typeof(ulong), target.Parse("123ul").ReturnType); + Assert.AreEqual(typeof(ulong), target.Parse("123LU").ReturnType); + Assert.AreEqual(typeof(ulong), target.Parse("123Lu").ReturnType); + Assert.AreEqual(typeof(ulong), target.Parse("123lU").ReturnType); + Assert.AreEqual(typeof(ulong), target.Parse("123lu").ReturnType); + Assert.AreEqual(typeof(double), target.Parse("234.54").ReturnType); + Assert.AreEqual(typeof(double), target.Parse(".9").ReturnType); + Assert.AreEqual(typeof(double), target.Parse("-.9").ReturnType); + Assert.AreEqual(typeof(double), target.Parse("234d").ReturnType); + Assert.AreEqual(typeof(double), target.Parse("234D").ReturnType); + Assert.AreEqual(typeof(float), target.Parse("4.5f").ReturnType); + Assert.AreEqual(typeof(float), target.Parse("4.5F").ReturnType); + Assert.AreEqual(typeof(float), target.Parse(".5f").ReturnType); + Assert.AreEqual(typeof(float), target.Parse(".5F").ReturnType); + Assert.AreEqual(typeof(decimal), target.Parse("234.48m").ReturnType); + Assert.AreEqual(typeof(decimal), target.Parse("234.48M").ReturnType); + Assert.AreEqual(typeof(decimal), target.Parse(".48m").ReturnType); + Assert.AreEqual(typeof(decimal), target.Parse(".48M").ReturnType); + Assert.AreEqual(typeof(object), target.Parse("null").ReturnType); Assert.AreEqual((45.5).GetType(), target.Eval("45.5").GetType()); Assert.AreEqual((45.8f).GetType(), target.Eval("45.8f").GetType()); diff --git a/test/DynamicExpresso.UnitTest/MemberInvocationTest.cs b/test/DynamicExpresso.UnitTest/MemberInvocationTest.cs index 41cba530..676570d5 100644 --- a/test/DynamicExpresso.UnitTest/MemberInvocationTest.cs +++ b/test/DynamicExpresso.UnitTest/MemberInvocationTest.cs @@ -108,7 +108,7 @@ public void Indexer_Getter_MultiDimensional() target.SetVariable("x", x); var y = new MyTestService(); target.SetVariable("y", y); - + Assert.AreEqual(x[1, 2], target.Eval("x[1, 2]")); Assert.AreEqual(y[y.Today, 2], target.Eval("y[y.Today, 2]")); Assert.AreEqual(y[y.Today], target.Eval("y[y.Today]")); @@ -218,7 +218,7 @@ public void Void_Method() target.Eval("service.VoidMethod()"); Assert.AreEqual(1, service.VoidMethodCalls); - Assert.AreEqual(typeof(void), target.Parse("service.VoidMethod()").Expression.Type); + Assert.AreEqual(typeof(void), target.Parse("service.VoidMethod()").ReturnType); } [Test] diff --git a/test/DynamicExpresso.UnitTest/NullableTest.cs b/test/DynamicExpresso.UnitTest/NullableTest.cs index 8ea5b3f3..7348f827 100644 --- a/test/DynamicExpresso.UnitTest/NullableTest.cs +++ b/test/DynamicExpresso.UnitTest/NullableTest.cs @@ -21,18 +21,31 @@ public void NullableInt32_NullableInt32() var interpreter = new Interpreter(); interpreter.SetVariable("a", a, typeof(Nullable)); interpreter.SetVariable("b", b, typeof(Nullable)); + var expectedReturnType = typeof(Nullable); // Addition - Verify(interpreter, "a + b", (Nullable) a + b); + var expected = a + b; + var lambda = interpreter.Parse("a + b"); + Assert.AreEqual(expected, lambda.Invoke()); + Assert.AreEqual(expectedReturnType, lambda.ReturnType); // Subtraction - Verify(interpreter, "a - b", (Nullable) a - b); + expected = a - b; + lambda = interpreter.Parse("a - b"); + Assert.AreEqual(expected, lambda.Invoke()); + Assert.AreEqual(expectedReturnType, lambda.ReturnType); // Division - Verify(interpreter, "a / b", (Nullable) a / b); + expected = a / b; + lambda = interpreter.Parse("a / b"); + Assert.AreEqual(expected, lambda.Invoke()); + Assert.AreEqual(expectedReturnType, lambda.ReturnType); // Multiplication - Verify(interpreter, "a * b", (Nullable) a * b); + expected = a * b; + lambda = interpreter.Parse("a * b"); + Assert.AreEqual(expected, lambda.Invoke()); + Assert.AreEqual(expectedReturnType, lambda.ReturnType); } [Test] @@ -47,16 +60,28 @@ public void NullableInt32_NullableInt32_with_left_null() var expectedReturnType = typeof(Nullable); // Addition - Verify(interpreter, "a + b", a + b); + var expected = a + b; + var lambda = interpreter.Parse("a + b"); + Assert.AreEqual(expected, lambda.Invoke()); + Assert.AreEqual(expectedReturnType, lambda.ReturnType); // Subtraction - Verify(interpreter, "a - b", a - b); + expected = a - b; + lambda = interpreter.Parse("a - b"); + Assert.AreEqual(expected, lambda.Invoke()); + Assert.AreEqual(expectedReturnType, lambda.ReturnType); // Division - Verify(interpreter, "a / b", a / b); + expected = a / b; + lambda = interpreter.Parse("a / b"); + Assert.AreEqual(expected, lambda.Invoke()); + Assert.AreEqual(expectedReturnType, lambda.ReturnType); // Multiplication - Verify(interpreter, "a * b", a * b); + expected = a * b; + lambda = interpreter.Parse("a * b"); + Assert.AreEqual(expected, lambda.Invoke()); + Assert.AreEqual(expectedReturnType, lambda.ReturnType); } [Test] @@ -71,16 +96,28 @@ public void NullableInt32_NullableInt32_with_right_null() var expectedReturnType = typeof(Nullable); // Addition - Verify(interpreter, "a + b", a + b); + var expected = a + b; + var lambda = interpreter.Parse("a + b"); + Assert.AreEqual(expected, lambda.Invoke()); + Assert.AreEqual(expectedReturnType, lambda.ReturnType); // Subtraction - Verify(interpreter, "a - b", a - b); + expected = a - b; + lambda = interpreter.Parse("a - b"); + Assert.AreEqual(expected, lambda.Invoke()); + Assert.AreEqual(expectedReturnType, lambda.ReturnType); // Division - Verify(interpreter, "a / b", a / b); + expected = a / b; + lambda = interpreter.Parse("a / b"); + Assert.AreEqual(expected, lambda.Invoke()); + Assert.AreEqual(expectedReturnType, lambda.ReturnType); // Multiplication - Verify(interpreter, "a * b", a * b); + expected = a * b; + lambda = interpreter.Parse("a * b"); + Assert.AreEqual(expected, lambda.Invoke()); + Assert.AreEqual(expectedReturnType, lambda.ReturnType); } [Test] @@ -92,18 +129,31 @@ public void NullableDouble_NullableDouble() var interpreter = new Interpreter(); interpreter.SetVariable("a", a, typeof(Nullable)); interpreter.SetVariable("b", b, typeof(Nullable)); + var expectedReturnType = typeof(Nullable); // Addition - Verify(interpreter, "a + b", (Nullable) a + b); + var expected = a + b; + var lambda = interpreter.Parse("a + b"); + Assert.AreEqual(expected, lambda.Invoke()); + Assert.AreEqual(expectedReturnType, lambda.ReturnType); // Subtraction - Verify(interpreter, "a - b", (Nullable) a - b); + expected = a - b; + lambda = interpreter.Parse("a - b"); + Assert.AreEqual(expected, lambda.Invoke()); + Assert.AreEqual(expectedReturnType, lambda.ReturnType); // Division - Verify(interpreter, "a / b", (Nullable) a / b); + expected = a / b; + lambda = interpreter.Parse("a / b"); + Assert.AreEqual(expected, lambda.Invoke()); + Assert.AreEqual(expectedReturnType, lambda.ReturnType); // Multiplication - Verify(interpreter, "a * b", (Nullable) a * b); + expected = a * b; + lambda = interpreter.Parse("a * b"); + Assert.AreEqual(expected, lambda.Invoke()); + Assert.AreEqual(expectedReturnType, lambda.ReturnType); } [Test] @@ -115,18 +165,31 @@ public void Int32_NullableDouble() var interpreter = new Interpreter(); interpreter.SetVariable("a", a, typeof(Int32)); interpreter.SetVariable("b", b, typeof(Nullable)); + var expectedReturnType = typeof(Nullable); // Addition - Verify(interpreter, "a + b", (Nullable) a + b); + var expected = a + b; + var lambda = interpreter.Parse("a + b"); + Assert.AreEqual(expected, lambda.Invoke()); + Assert.AreEqual(expectedReturnType, lambda.ReturnType); // Subtraction - Verify(interpreter, "a - b", (Nullable) a - b); + expected = a - b; + lambda = interpreter.Parse("a - b"); + Assert.AreEqual(expected, lambda.Invoke()); + Assert.AreEqual(expectedReturnType, lambda.ReturnType); // Division - Verify(interpreter, "a / b", (Nullable) a / b); + expected = a / b; + lambda = interpreter.Parse("a / b"); + Assert.AreEqual(expected, lambda.Invoke()); + Assert.AreEqual(expectedReturnType, lambda.ReturnType); // Multiplication - Verify(interpreter, "a * b", (Nullable) a * b); + expected = a * b; + lambda = interpreter.Parse("a * b"); + Assert.AreEqual(expected, lambda.Invoke()); + Assert.AreEqual(expectedReturnType, lambda.ReturnType); } [Test] @@ -138,18 +201,31 @@ public void NullableInt32_Double() var interpreter = new Interpreter(); interpreter.SetVariable("a", a, typeof(Nullable)); interpreter.SetVariable("b", b, typeof(Double)); + var expectedReturnType = typeof(Nullable); // Addition - Verify(interpreter, "a + b", (Nullable) a + b); + var expected = a + b; + var lambda = interpreter.Parse("a + b"); + Assert.AreEqual(expected, lambda.Invoke()); + Assert.AreEqual(expectedReturnType, lambda.ReturnType); // Subtraction - Verify(interpreter, "a - b", (Nullable) a - b); + expected = a - b; + lambda = interpreter.Parse("a - b"); + Assert.AreEqual(expected, lambda.Invoke()); + Assert.AreEqual(expectedReturnType, lambda.ReturnType); // Division - Verify(interpreter, "a / b", (Nullable) a / b); + expected = a / b; + lambda = interpreter.Parse("a / b"); + Assert.AreEqual(expected, lambda.Invoke()); + Assert.AreEqual(expectedReturnType, lambda.ReturnType); // Multiplication - Verify(interpreter, "a * b", (Nullable) a * b); + expected = a * b; + lambda = interpreter.Parse("a * b"); + Assert.AreEqual(expected, lambda.Invoke()); + Assert.AreEqual(expectedReturnType, lambda.ReturnType); } [Test] @@ -165,24 +241,45 @@ public void NullableDateTimeOffset_DatetimeOffset() interpreter.SetVariable("c", c, typeof(DateTimeOffset)); var expectedReturnType = typeof(bool); - Verify(interpreter, "a < b", a < b); - Verify(interpreter, "a > b", a > b); - Verify(interpreter, "a == b", a == b); - Verify(interpreter, "a != b", a != b); - Verify(interpreter, "b == b", b == b); - Verify(interpreter, "b != c", b != c); - Verify(interpreter, "a - b", a - b); - - b = null; + var expected = a < b; + var lambda = interpreter.Parse("a < b"); + Assert.AreEqual(expected, lambda.Invoke()); + Assert.AreEqual(expectedReturnType, lambda.ReturnType); + + expected = a > b; + lambda = interpreter.Parse("a > b"); + Assert.AreEqual(expected, lambda.Invoke()); + Assert.AreEqual(expectedReturnType, lambda.ReturnType); + + expected = a == b; + lambda = interpreter.Parse("a == b"); + Assert.AreEqual(expected, lambda.Invoke()); + Assert.AreEqual(expectedReturnType, lambda.ReturnType); + + expected = a != b; + lambda = interpreter.Parse("a != b"); + Assert.AreEqual(expected, lambda.Invoke()); + Assert.AreEqual(expectedReturnType, lambda.ReturnType); + + expected = b == c; + lambda = interpreter.Parse("b == b"); + Assert.AreEqual(expected, lambda.Invoke()); + Assert.AreEqual(expectedReturnType, lambda.ReturnType); + + expected = b != c; + lambda = interpreter.Parse("b != c"); + Assert.AreEqual(expected, lambda.Invoke()); + Assert.AreEqual(expectedReturnType, lambda.ReturnType); + + lambda = interpreter.Parse("a - b"); + Assert.AreEqual(a - b, lambda.Invoke()); + Assert.AreEqual(typeof(TimeSpan?), lambda.ReturnType); + + b = null; interpreter.SetVariable("b", b, typeof(DateTimeOffset?)); - Verify(interpreter, "a - b", (TimeSpan?) null); - } - - private static void Verify(Interpreter interpreter, string expression, T expected) - { - var parsed = interpreter.Parse(expression); - Assert.AreEqual(expected, parsed.Compile().DynamicInvoke()); - Assert.AreEqual(typeof(T), parsed.Expression.Type); + lambda = interpreter.Parse("a - b"); + Assert.AreEqual(a - b, lambda.Invoke()); + Assert.AreEqual(typeof(TimeSpan?), lambda.ReturnType); } } } diff --git a/test/DynamicExpresso.UnitTest/OperatorsTest.cs b/test/DynamicExpresso.UnitTest/OperatorsTest.cs index 92b651d8..5ccf527e 100644 --- a/test/DynamicExpresso.UnitTest/OperatorsTest.cs +++ b/test/DynamicExpresso.UnitTest/OperatorsTest.cs @@ -131,8 +131,8 @@ public void Unary_Cast_Operator() target.SetVariable("x", x); Assert.AreEqual((int)x, target.Eval("(int)x")); - Assert.AreEqual(typeof(int), target.Parse("(int)x").Expression.Type); - Assert.AreEqual(typeof(object), target.Parse("(object)x").Expression.Type); + Assert.AreEqual(typeof(int), target.Parse("(int)x").ReturnType); + Assert.AreEqual(typeof(object), target.Parse("(object)x").ReturnType); Assert.AreEqual((double)84 + 9 * 8, target.Eval("(double)84 + 9 *8")); } @@ -207,7 +207,7 @@ public void Is_Operator() // ReSharper disable once ConditionIsAlwaysTrueOrFalse Assert.AreEqual(a is string, target.Eval("a is string")); - Assert.AreEqual(typeof(bool), target.Parse("a is string").Expression.Type); + Assert.AreEqual(typeof(bool), target.Parse("a is string").ReturnType); // ReSharper disable once ConditionIsAlwaysTrueOrFalse Assert.AreEqual(b is string, target.Eval("b is string")); // ReSharper disable once ConditionIsAlwaysTrueOrFalse @@ -227,7 +227,7 @@ public void Is_Operator_Generics() target.Reference(typeof(Tuple<,>)); Assert.AreEqual(true, target.Eval("a is Tuple")); - Assert.AreEqual(typeof(bool), target.Parse("a is Tuple").Expression.Type); + Assert.AreEqual(typeof(bool), target.Parse("a is Tuple").ReturnType); Assert.AreEqual(true, target.Eval("b is Tuple")); } @@ -242,10 +242,10 @@ public void As_Operator() // ReSharper disable once TryCastAlwaysSucceeds Assert.AreEqual(a as string, target.Eval("a as string")); - Assert.AreEqual(typeof(string), target.Parse("a as string").Expression.Type); + Assert.AreEqual(typeof(string), target.Parse("a as string").ReturnType); // ReSharper disable once ExpressionIsAlwaysNull Assert.AreEqual(b as string, target.Eval("b as string")); - Assert.AreEqual(typeof(string), target.Parse("b as string").Expression.Type); + Assert.AreEqual(typeof(string), target.Parse("b as string").ReturnType); } [Test] @@ -278,13 +278,17 @@ public void String_Concatenation_with_type_conversion() [Test] public void String_Concatenation_check_string_method() { - var expectedMethod = typeof(string).GetMethod(nameof(string.Concat), new[] {typeof(string), typeof(string)}); - var interpreter = new Interpreter(); - var expressionText = "\"ciao \" + 1981"; - var lambda = interpreter.Parse(expressionText); + MethodInfo expectedMethod = typeof(string) + .GetMethod(nameof(String.Concat), new[] {typeof(string), typeof(string)}); + + Interpreter interpreter = new Interpreter(); - var methodCallExpression = lambda.Expression as MethodCallExpression; + string expressionText = "\"ciao \" + 1981"; + Lambda lambda = interpreter.Parse(expressionText); + + MethodCallExpression methodCallExpression = lambda.Expression as MethodCallExpression; + Assert.IsNotNull(methodCallExpression); Assert.AreEqual(expectedMethod, methodCallExpression.Method); } @@ -293,15 +297,15 @@ public void String_Concatenation_check_string_method() public void String_Concatenation_with_null() { Interpreter interpreter = new Interpreter(); - + string expressionText = "\"ciao \" + null"; Assert.AreEqual("ciao ", interpreter.Eval(expressionText)); - + Func someFunc = () => null; interpreter.SetFunction("someFunc", someFunc); expressionText = "\"ciao \" + someFunc()"; Assert.AreEqual("ciao ", interpreter.Eval(expressionText)); - + Func someFuncObject = () => null; interpreter.SetFunction("someFuncObject", someFuncObject); expressionText = "\"ciao \" + someFuncObject()"; @@ -310,7 +314,7 @@ public void String_Concatenation_with_null() expressionText = "someFunc() + \"123\" + null + \"678\" + someFuncObject()"; Assert.AreEqual("123678", interpreter.Eval(expressionText)); } - + private class MyClass { public override string ToString() @@ -333,7 +337,7 @@ public void String_Concatenation_with_overridden_ToString() Interpreter interpreter = new Interpreter() .SetVariable("myClass", new MyClass()) .SetVariable("myClassNullToString", new MyClassNullToString()); - + Assert.AreEqual("ciao MyClassStr", interpreter.Eval("\"ciao \" + myClass")); Assert.AreEqual("ciao ", interpreter.Eval("\"ciao \" + myClassNullToString")); } @@ -477,7 +481,7 @@ public void Assignment_Operators_can_be_disabled() Assert.AreEqual(AssignmentOperators.None, target.AssignmentOperators); - Assert.Throws(() => target.Parse("x = 5", Expression.Parameter(typeof(int), "x"))); + Assert.Throws(() => target.Parse("x = 5", new Parameter("x", 0))); } [Test] @@ -621,7 +625,7 @@ public void Implicit_conversion_operator_for_lambda() var target = new Interpreter() .SetVariable("x", new TypeWithImplicitConversion(10)); - var func = target.Parse>("x").Compile(); + var func = target.ParseAsDelegate>("x"); var val = func(); Assert.AreEqual(10, val); @@ -865,7 +869,7 @@ public void Throw_an_exception_if_a_custom_type_doesnt_define_equal_operator() var y = "5"; - var ex = Assert.Throws(() => target.Parse("x == y", Expression.Parameter(typeof(string), "y"))); + var ex = Assert.Throws(() => target.Parse("x == y", new Parameter("y", y))); Assert.IsInstanceOf(ex.InnerException); } @@ -877,7 +881,9 @@ public void Throw_an_exception_if_a_custom_type_doesnt_define_plus_operator() var x = new TypeWithoutOverloadedBinaryOperators(3); target.SetVariable("x", x); - var ex = Assert.Throws(() => target.Parse("x + y", Expression.Parameter(typeof(int), "y"))); + var y = 5; + + var ex = Assert.Throws(() => target.Parse("x + y", new Parameter("y", y))); Assert.IsInstanceOf(ex.InnerException); } diff --git a/test/DynamicExpresso.UnitTest/OtherTests.cs b/test/DynamicExpresso.UnitTest/OtherTests.cs index 8898bccb..15e2a06f 100644 --- a/test/DynamicExpresso.UnitTest/OtherTests.cs +++ b/test/DynamicExpresso.UnitTest/OtherTests.cs @@ -23,13 +23,13 @@ public void Empty_Null_Withespace_Expression() var target = new Interpreter(); Assert.AreEqual(null, target.Eval("")); - Assert.AreEqual(typeof(void), target.Parse("").Expression.Type); + Assert.AreEqual(typeof(void), target.Parse("").ReturnType); Assert.AreEqual(null, target.Eval(null)); - Assert.AreEqual(typeof(void), target.Parse(null).Expression.Type); + Assert.AreEqual(typeof(void), target.Parse(null).ReturnType); Assert.AreEqual(null, target.Eval(" \t\t\r\n \t ")); - Assert.AreEqual(typeof(void), target.Parse(" \t\t\r\n \t ").Expression.Type); + Assert.AreEqual(typeof(void), target.Parse(" \t\t\r\n \t ").ReturnType); } [Test] @@ -39,7 +39,10 @@ public void Complex_expression() var x = new MyTestService(); var y = 5; - var parameters = new[] {new Parameter("x", x.GetType(), x), new Parameter("y", y.GetType(), y)}; + var parameters = new[] { + new Parameter("x", x.GetType(), x), + new Parameter("y", y.GetType(), y), + }; Assert.AreEqual(x.AProperty > y && x.HelloWorld().Length == 10, target.Eval("x.AProperty >\t y && \r\n x.HelloWorld().Length == 10", parameters)); Assert.AreEqual(x.AProperty * (4 + 65) / x.AProperty, target.Eval("x.AProperty * (4 + 65) / x.AProperty", parameters)); @@ -55,22 +58,19 @@ public void Parse_An_Expression_And_Invoke_It_With_Different_Parameters() var target = new Interpreter() .SetVariable("service", service); - var parseResult = target.Parse( - "x > 4 ? service.VoidMethod() : service.VoidMethod2()", - Expression.Parameter(typeof(int), "x")); + var func = target.Parse("x > 4 ? service.VoidMethod() : service.VoidMethod2()", + new Parameter("x", typeof(int))); - var func = parseResult.Compile(); - - Assert.AreEqual(typeof(void), parseResult.Expression.Type); + Assert.AreEqual(typeof(void), func.ReturnType); Assert.AreEqual(0, service.VoidMethodCalled); Assert.AreEqual(0, service.VoidMethod2Called); - func.DynamicInvoke(5); + func.Invoke(new Parameter("x", 5)); Assert.AreEqual(1, service.VoidMethodCalled); Assert.AreEqual(0, service.VoidMethod2Called); - func.DynamicInvoke(2); + func.Invoke(new Parameter("x", 2)); Assert.AreEqual(1, service.VoidMethodCalled); Assert.AreEqual(1, service.VoidMethod2Called); } @@ -82,10 +82,13 @@ public void Should_Understand_ReturnType_Of_expressions() var x = new MyTestService(); var y = 5; - var parameters = new[] {Expression.Parameter(x.GetType(), "x"), Expression.Parameter(y.GetType(), "y"),}; + var parameters = new[] { + new Parameter("x", x.GetType(), x), + new Parameter("y", y.GetType(), y), + }; - Assert.AreEqual(typeof(bool), target.Parse("x.AProperty > y && x.HelloWorld().Length == 10", parameters).Expression.Type); - Assert.AreEqual(typeof(int), target.Parse("x.AProperty * (4 + 65) / x.AProperty", parameters).Expression.Type); + Assert.AreEqual(typeof(bool), target.Parse("x.AProperty > y && x.HelloWorld().Length == 10", parameters).ReturnType); + Assert.AreEqual(typeof(int), target.Parse("x.AProperty * (4 + 65) / x.AProperty", parameters).ReturnType); } [Test] @@ -94,22 +97,24 @@ public void Execute_the_same_function_multiple_times() var target = new Interpreter(); var functionX = target.Parse("Math.Pow(x, y) + 5", - Expression.Parameter(typeof(double), "x"), - Expression.Parameter(typeof(double), "y")) - .Compile(); - - Assert.AreEqual(Math.Pow(15, 12) + 5, functionX.DynamicInvoke(15, 12)); - Assert.AreEqual(Math.Pow(5, 1) + 5, functionX.DynamicInvoke(5, 1)); - Assert.AreEqual(Math.Pow(11, 8) + 5, functionX.DynamicInvoke(11, 8)); - Assert.AreEqual(Math.Pow(3, 4) + 5, functionX.DynamicInvoke(3.0, 4.0)); - Assert.AreEqual(Math.Pow(9, 2) + 5, functionX.DynamicInvoke(9.0, 2.0)); - Assert.AreEqual(Math.Pow(1, 3) + 5, functionX.DynamicInvoke(1.0, 3.0)); + new Parameter("x", typeof(double)), + new Parameter("y", typeof(double))); + + Assert.AreEqual(Math.Pow(15, 12) + 5, functionX.Invoke(15, 12)); + Assert.AreEqual(Math.Pow(5, 1) + 5, functionX.Invoke(5, 1)); + Assert.AreEqual(Math.Pow(11, 8) + 5, functionX.Invoke(11, 8)); + Assert.AreEqual(Math.Pow(3, 4) + 5, functionX.Invoke(new Parameter("x", 3.0), + new Parameter("y", 4.0))); + Assert.AreEqual(Math.Pow(9, 2) + 5, functionX.Invoke(new Parameter("x", 9.0), + new Parameter("y", 2.0))); + Assert.AreEqual(Math.Pow(1, 3) + 5, functionX.Invoke(new Parameter("x", 1.0), + new Parameter("y", 3.0))); } [Test] public void Linq_Where() { - var customers = new List { + var customers = new List { new Customer() { Name = "David", Age = 31, Gender = 'M' }, new Customer() { Name = "Mary", Age = 29, Gender = 'F' }, new Customer() { Name = "Jack", Age = 2, Gender = 'M' }, @@ -117,10 +122,10 @@ public void Linq_Where() new Customer() { Name = "Moses", Age = 120, Gender = 'M' }, }; - var whereExpression = "customer.Age > 18 && customer.Gender == 'F'"; + string whereExpression = "customer.Age > 18 && customer.Gender == 'F'"; var interpreter = new Interpreter(); - var dynamicWhere = interpreter.Parse>(whereExpression, "customer").Compile(); + Func dynamicWhere = interpreter.ParseAsDelegate>(whereExpression, "customer"); Assert.AreEqual(1, customers.Where(dynamicWhere).Count()); } @@ -130,7 +135,8 @@ public void Linq_Where2() { var prices = new [] { 5, 8, 6, 2 }; - var whereFunction = new Interpreter().Parse>("arg > 5").Compile(); + var whereFunction = new Interpreter() + .ParseAsDelegate>("arg > 5"); Assert.AreEqual(2, prices.Where(whereFunction).Count()); } @@ -138,7 +144,7 @@ public void Linq_Where2() [Test] public void Linq_Queryable_Expression_Where() { - var customers = (new List { + IQueryable customers = (new List { new Customer() { Name = "David", Age = 31, Gender = 'M' }, new Customer() { Name = "Mary", Age = 29, Gender = 'F' }, new Customer() { Name = "Jack", Age = 2, Gender = 'M' }, @@ -146,10 +152,10 @@ public void Linq_Queryable_Expression_Where() new Customer() { Name = "Moses", Age = 120, Gender = 'M' }, }).AsQueryable(); - var whereExpression = "customer.Age > 18 && customer.Gender == 'F'"; + string whereExpression = "customer.Age > 18 && customer.Gender == 'F'"; var interpreter = new Interpreter(); - var expression = interpreter.Parse>(whereExpression, "customer").AsExpression(); + Expression> expression = interpreter.ParseAsExpression>(whereExpression, "customer"); Assert.AreEqual(1, customers.Where(expression).Count()); } diff --git a/test/DynamicExpresso.UnitTest/ParametersTest.cs b/test/DynamicExpresso.UnitTest/ParametersTest.cs index 0e7276d9..040c2197 100644 --- a/test/DynamicExpresso.UnitTest/ParametersTest.cs +++ b/test/DynamicExpresso.UnitTest/ParametersTest.cs @@ -3,7 +3,6 @@ using System.Globalization; using System.Reflection; using System.Linq; -using System.Linq.Expressions; namespace DynamicExpresso.UnitTest { @@ -36,7 +35,6 @@ public void Parameters_orders_is_not_important_for_eval() Assert.AreEqual("AB", target.Eval("A + B", parameters)); } - /* [Test] public void Parameters_orders_can_be_different_between_parse_and_invoke() { @@ -50,16 +48,18 @@ public void Parameters_orders_can_be_different_between_parse_and_invoke() var lambda = target.Parse("A + B", parameters); Assert.AreEqual("AB", lambda.Invoke(parameters.Reverse())); - }*/ + } [Test] public void Expression_Without_Parameters() { var target = new Interpreter(); - var func = target.Parse("10+5").Compile(); + var parameters = new Parameter[0]; + + var exp = target.Parse("10+5", parameters); - Assert.AreEqual(15, func.DynamicInvoke()); + Assert.AreEqual(15, exp.Invoke()); } [Test] @@ -67,22 +67,29 @@ public void Parameters_Mismatch() { var target = new Interpreter(); - var exp = target.Parse( - "x + y", - Expression.Parameter(typeof(int), "x"), - Expression.Parameter(typeof(int), "y")) - .Compile(); + var parameters = new[] { + new Parameter("x", 23), + new Parameter("y", 7) + }; + + var exp = target.Parse("x + y", parameters); - Assert.Throws(() => exp.DynamicInvoke(546)); + var parametersMismatch = new[] { + new Parameter("x", 546) + }; + + Assert.Throws(() => exp.Invoke(parametersMismatch)); } - /* [Test] public void Invoke_the_lambda_using_different_parameters_values() { var target = new Interpreter(); - var parameters = new[] {Expression.Parameter(typeof(int), "x"), Expression.Parameter(typeof(int), "y")}; + var parameters = new[] { + new Parameter("x", typeof(int)), + new Parameter("y", typeof(int)) + }; var myFunc = target.Parse("x + y", parameters); @@ -99,19 +106,22 @@ public void Invoke_the_lambda_using_different_parameters_values() }; Assert.AreEqual(58, myFunc.Invoke(parameters2)); - }*/ + } [Test] public void Different_parameters_values_With_Args() { var target = new Interpreter(); - var parameters = new[] {Expression.Parameter(typeof(int), "x"), Expression.Parameter(typeof(int), "y")}; + var parameters = new[] { + new Parameter("x", typeof(int)), + new Parameter("y", typeof(int)) + }; - var myFunc = target.Parse("x + y", parameters).Compile(); + var myFunc = target.Parse("x + y", parameters); - Assert.AreEqual(30, myFunc.DynamicInvoke(23, 7)); - Assert.AreEqual(30, myFunc.DynamicInvoke(32, -2)); + Assert.AreEqual(30, myFunc.Invoke(23, 7)); + Assert.AreEqual(30, myFunc.Invoke(32, -2)); } [Test] @@ -163,7 +173,6 @@ public void Parameters_can_be_Case_insensitive() Assert.AreEqual(x, target.Eval("X", parameters)); } - /* [Test] public void Parameters_cannot_be_parsed_with_one_case_and_invoked_in_another_case() { @@ -186,7 +195,7 @@ public void Parameters_can_be_parsed_with_one_case_and_invoked_in_another_case_w var lambda = target.Parse("x", new Parameter("x", x.GetType())); Assert.AreEqual(x, lambda.Invoke(new Parameter("X", x))); - }*/ + } [Test] public void Complex_parameters() @@ -272,12 +281,11 @@ public void When_parsing_an_expression_only_the_actually_used_parameters_should_ { var target = new Interpreter(); - var parameters = new[] - { - Expression.Parameter(typeof(int), "x"), - Expression.Parameter(typeof(int), "y"), - Expression.Parameter(typeof(int), "z"), - }; + var parameters = new[] { + new Parameter("x", 23), + new Parameter("y", 7), + new Parameter("z", 54), + }; var lambda = target.Parse("x + y", parameters); @@ -297,7 +305,10 @@ public void Using_the_same_parameters_multiple_times_doesnt_produce_multiple_par { var target = new Interpreter(); - var parameters = new[] {Expression.Parameter(typeof(int), "x"), Expression.Parameter(typeof(int), "y"),}; + var parameters = new[] { + new Parameter("x", 23), + new Parameter("y", 7), + }; var lambda = target.Parse("x * y + x * y", parameters); @@ -311,32 +322,37 @@ public void When_lambda_is_invoked_input_parameters_must_follow_in_the_same_orde { var target = new Interpreter(); - var parameters = new[] {Expression.Parameter(typeof(int), "x"), Expression.Parameter(typeof(int), "y")}; + var parameters = new[]{ + new Parameter("x", typeof(int)), + new Parameter("y", typeof(int)) + }; - var lambda = target.Parse("y-x", parameters).Compile(); + var lambda = target.Parse("y-x", parameters); - Assert.AreEqual(4, lambda.DynamicInvoke(1, 5)); + Assert.AreEqual(4, lambda.Invoke(1, 5)); } - /* [Test] public void When_lambda_is_invoked_I_can_omit_parameters_not_used() { var target = new Interpreter(); - var parameters = new[] {Expression.Parameter(typeof(int), "x"), Expression.Parameter(typeof(int), "y"),}; + var parameters = new[]{ + new Parameter("x", typeof(int)), + new Parameter("y", typeof(int)) + }; - var lambda = target.Parse("y+5", parameters).Compile(); + var lambda = target.Parse("y+5", parameters); - Assert.AreEqual(7, lambda.DynamicInvoke(2)); - }*/ + Assert.AreEqual(7, lambda.Invoke(new Parameter("y", 2))); + } [Test] public void When_parsing_an_expression_to_a_delegate_the_delegate_parameters_are_respected_also_if_the_expression_doesnt_use_it() { var target = new Interpreter(); - var myDelegate = target.Parse("x + y").Compile(); + var myDelegate = target.ParseAsDelegate("x + y"); // parameter 'z' is not used but the delegate accept it in any case without problem Assert.AreEqual(3, myDelegate(1, 2, 123123)); diff --git a/test/DynamicExpresso.UnitTest/TypesTest.cs b/test/DynamicExpresso.UnitTest/TypesTest.cs index 01d84f19..2f1f41d6 100644 --- a/test/DynamicExpresso.UnitTest/TypesTest.cs +++ b/test/DynamicExpresso.UnitTest/TypesTest.cs @@ -2,7 +2,6 @@ using NUnit.Framework; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; using DynamicExpresso.Exceptions; namespace DynamicExpresso.UnitTest @@ -112,7 +111,7 @@ public void Custom_Type_Alias() var target = new Interpreter() .Reference(typeof(MyDataContract), "DC"); - Assert.AreEqual(typeof(MyDataContract), target.Parse("new DC(\"davide\")").Expression.Type); + Assert.AreEqual(typeof(MyDataContract), target.Parse("new DC(\"davide\")").ReturnType); } [Test] @@ -140,7 +139,7 @@ public void Getting_the_list_of_used_types() { var target = new Interpreter(); - var lambda = target.Parse("Math.Max(a, typeof(string).Name.Length)", Expression.Parameter(typeof(int), "a")); + var lambda = target.Parse("Math.Max(a, typeof(string).Name.Length)", new Parameter("a", 1)); Assert.AreEqual(2, lambda.Types.Count()); Assert.AreEqual("Math", lambda.Types.ElementAt(0).Name); diff --git a/test/DynamicExpresso.UnitTest/VariablesTest.cs b/test/DynamicExpresso.UnitTest/VariablesTest.cs index 557bc2b8..3b70e146 100644 --- a/test/DynamicExpresso.UnitTest/VariablesTest.cs +++ b/test/DynamicExpresso.UnitTest/VariablesTest.cs @@ -44,7 +44,7 @@ public void Assign_and_use_variables() .SetVariable("myk", 23); Assert.AreEqual(23, target.Eval("myk")); - Assert.AreEqual(typeof(int), target.Parse("myk").Expression.Type); + Assert.AreEqual(typeof(int), target.Parse("myk").ReturnType); } [Test] @@ -80,7 +80,7 @@ public void Variables_can_be_overwritten() Assert.AreEqual(3489, target.Eval("myk")); - Assert.AreEqual(typeof(int), target.Parse("myk").Expression.Type); + Assert.AreEqual(typeof(int), target.Parse("myk").ReturnType); } [Test] @@ -95,7 +95,7 @@ public void Variables_can_be_overwritten_in_a_case_insensitive_setting() Assert.AreEqual(3489, target.Eval("myk")); - Assert.AreEqual(typeof(int), target.Parse("myk").Expression.Type); + Assert.AreEqual(typeof(int), target.Parse("myk").ReturnType); } [Test] @@ -106,7 +106,7 @@ public void Null_Variables() Assert.AreEqual(null, target.Eval("myk")); Assert.AreEqual(true, target.Eval("myk == null")); - Assert.AreEqual(typeof(object), target.Parse("myk").Expression.Type); + Assert.AreEqual(typeof(object), target.Parse("myk").ReturnType); } [Test] @@ -117,7 +117,7 @@ public void Null_Variables_With_Type_Specified() Assert.AreEqual(null, target.Eval("myk")); Assert.AreEqual(true, target.Eval("myk == null")); - Assert.AreEqual(typeof(string), target.Parse("myk").Expression.Type); + Assert.AreEqual(typeof(string), target.Parse("myk").ReturnType); } [Test] @@ -187,7 +187,7 @@ public void Keywords_with_ambiguous_delegates() // ambiguous call: null can either be a string or an object // note: if there's no ambiguous exception, it means that the resolution - // lifted the parameters from the string overload, which prevented the int? overload + // lifted the parameters from the string overload, which prevented the int? overload // from being considered Assert.Throws(() => interpreter.Eval("MyFunc(null)")); @@ -205,8 +205,8 @@ public void Keywords_with_non_ambiguous_delegates() interpreter.SetFunction("MyFunc", ambiguous1); interpreter.SetFunction("MyFunc", ambiguous2); - // there should be no ambiguous exception: int can implicitly be converted to double, - // but there's a perfect match + // there should be no ambiguous exception: int can implicitly be converted to double, + // but there's a perfect match Assert.AreEqual("integer", interpreter.Eval("MyFunc(5)")); } @@ -215,7 +215,7 @@ public void Set_function_With_Object_Params() { var target = new Interpreter(); - // import static method with params array + // import static method with params array var methodInfo = typeof(VariablesTest).GetMethod("Sum", BindingFlags.Static | BindingFlags.NonPublic); var types = methodInfo.GetParameters().Select(p => p.ParameterType).Concat(new[] { methodInfo.ReturnType }); var del = methodInfo.CreateDelegate(Expression.GetDelegateType(types.ToArray())); diff --git a/test/DynamicExpresso.UnitTest/VisitorsTest.cs b/test/DynamicExpresso.UnitTest/VisitorsTest.cs index 27fe5024..2d272367 100644 --- a/test/DynamicExpresso.UnitTest/VisitorsTest.cs +++ b/test/DynamicExpresso.UnitTest/VisitorsTest.cs @@ -1,5 +1,4 @@ -using System.Linq.Expressions; -using DynamicExpresso.Exceptions; +using DynamicExpresso.Exceptions; using NUnit.Framework; namespace DynamicExpresso.UnitTest @@ -15,8 +14,8 @@ public void By_default_reflection_is_not_permitted() Assert.Throws(() => target.Parse("typeof(double).GetMethods()")); Assert.Throws(() => target.Parse("typeof(double).Assembly")); - Assert.Throws(() => target.Parse("x.GetType().GetMethods()", Expression.Parameter(typeof(X), "x"))); - Assert.Throws(() => target.Parse("x.GetType().Assembly", Expression.Parameter(typeof(X), "x"))); + Assert.Throws(() => target.Parse("x.GetType().GetMethods()", new Parameter("x", typeof(X)))); + Assert.Throws(() => target.Parse("x.GetType().Assembly", new Parameter("x", typeof(X)))); } [Test] From 48ba1240f030e2912af97d907fd43d1fd7b1452d Mon Sep 17 00:00:00 2001 From: eugenealeykin Date: Tue, 14 Dec 2021 12:33:00 +0200 Subject: [PATCH 5/9] code review --- .../ExpressionInterpreter.cs | 211 +++++++++-- src/DynamicExpresso.Core/Interpreter.cs | 176 ++++++--- ...Extensions.cs => InterpreterExtensions.cs} | 11 +- .../InterpreterSettings.cs | 344 ------------------ 4 files changed, 318 insertions(+), 424 deletions(-) rename src/DynamicExpresso.Core/{LambdaExtensions.cs => InterpreterExtensions.cs} (90%) delete mode 100644 src/DynamicExpresso.Core/InterpreterSettings.cs diff --git a/src/DynamicExpresso.Core/ExpressionInterpreter.cs b/src/DynamicExpresso.Core/ExpressionInterpreter.cs index ce9748e2..3f204a98 100644 --- a/src/DynamicExpresso.Core/ExpressionInterpreter.cs +++ b/src/DynamicExpresso.Core/ExpressionInterpreter.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Linq.Expressions; using DynamicExpresso.Reflection; +using DynamicExpresso.Visitors; namespace DynamicExpresso { @@ -13,14 +14,15 @@ namespace DynamicExpresso /// public class ExpressionInterpreter { - private readonly InterpreterSettings _settings; + private readonly ParserSettings _settings; + private readonly ISet _visitors = new HashSet(); #region Constructors - /// /// Creates a new ExpressionInterpreter using InterpreterOptions.Default. /// - public ExpressionInterpreter() : this(InterpreterOptions.Default) + public ExpressionInterpreter() + : this(InterpreterOptions.Default) { } @@ -30,7 +32,34 @@ public ExpressionInterpreter() : this(InterpreterOptions.Default) /// public ExpressionInterpreter(InterpreterOptions options) { - _settings = new InterpreterSettings(options); + var caseInsensitive = options.HasFlag(InterpreterOptions.CaseInsensitive); + + var lateBindObject = options.HasFlag(InterpreterOptions.LateBindObject); + + _settings = new ParserSettings(caseInsensitive, lateBindObject); + + if ((options & InterpreterOptions.SystemKeywords) == InterpreterOptions.SystemKeywords) + { + SetIdentifiers(LanguageConstants.Literals); + } + + if ((options & InterpreterOptions.PrimitiveTypes) == InterpreterOptions.PrimitiveTypes) + { + Reference(LanguageConstants.PrimitiveTypes); + Reference(LanguageConstants.CSharpPrimitiveTypes); + } + + if ((options & InterpreterOptions.CommonTypes) == InterpreterOptions.CommonTypes) + { + Reference(LanguageConstants.CommonTypes); + } + + if ((options & InterpreterOptions.LambdaExpressions) == InterpreterOptions.LambdaExpressions) + { + _settings.LambdaExpressions = true; + } + + _visitors.Add(new DisableReflectionVisitor()); } /// @@ -38,30 +67,52 @@ public ExpressionInterpreter(InterpreterOptions options) /// internal ExpressionInterpreter(ParserSettings settings) { - _settings = new InterpreterSettings(settings); + _settings = settings; } - #endregion #region Properties - - public bool CaseInsensitive { get { return _settings.CaseInsensitive; } } + public bool CaseInsensitive + { + get + { + return _settings.CaseInsensitive; + } + } /// /// Gets a list of registeres types. Add types by using the Reference method. /// - public IEnumerable ReferencedTypes { get { return _settings.ReferencedTypes; } } + public IEnumerable ReferencedTypes + { + get + { + return _settings.KnownTypes + .Select(p => p.Value) + .ToList(); + } + } /// /// Gets a list of known identifiers. Add identifiers using SetVariable, SetFunction or SetExpression methods. /// - public IEnumerable Identifiers { get { return _settings.Identifiers; } } + public IEnumerable Identifiers + { + get + { + return _settings.Identifiers + .Select(p => p.Value) + .ToList(); + } + } /// /// Gets the available assignment operators. /// - public AssignmentOperators AssignmentOperators { get { return _settings.AssignmentOperators; } } - + public AssignmentOperators AssignmentOperators + { + get { return _settings.AssignmentOperators; } + } #endregion #region Options @@ -73,7 +124,7 @@ internal ExpressionInterpreter(ParserSettings settings) /// public ExpressionInterpreter SetDefaultNumberType(DefaultNumberType defaultNumberType) { - _settings.SetDefaultNumberType(defaultNumberType); + _settings.DefaultNumberType = defaultNumberType; return this; } @@ -85,15 +136,17 @@ public ExpressionInterpreter SetDefaultNumberType(DefaultNumberType defaultNumbe /// public ExpressionInterpreter EnableAssignment(AssignmentOperators assignmentOperators) { - _settings.EnableAssignment(assignmentOperators); + _settings.AssignmentOperators = assignmentOperators; + return this; } - #endregion #region Visitors - - public ISet Visitors { get { return _settings.Visitors; } } + public ISet Visitors + { + get { return _visitors; } + } /// /// Enable reflection expression (like x.GetType().GetMethod() or typeof(double).Assembly) by removing the DisableReflectionVisitor. @@ -101,14 +154,15 @@ public ExpressionInterpreter EnableAssignment(AssignmentOperators assignmentOper /// public ExpressionInterpreter EnableReflection() { - _settings.EnableReflection(); + var visitor = Visitors.FirstOrDefault(p => p is DisableReflectionVisitor); + if (visitor != null) + Visitors.Remove(visitor); + return this; } - #endregion #region Register identifiers - /// /// Allow the specified function delegate to be called from a parsed expression. /// @@ -117,7 +171,18 @@ public ExpressionInterpreter EnableReflection() /// public ExpressionInterpreter SetFunction(string name, Delegate value) { - _settings.SetFunction(name, value); + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentNullException(nameof(name)); + + if (_settings.Identifiers.TryGetValue(name, out var identifier) && identifier is FunctionIdentifier fIdentifier) + { + fIdentifier.AddOverload(value); + } + else + { + SetIdentifier(new FunctionIdentifier(name, value)); + } + return this; } @@ -129,8 +194,10 @@ public ExpressionInterpreter SetFunction(string name, Delegate value) /// public ExpressionInterpreter SetVariable(string name, object value) { - _settings.SetVariable(name, value); - return this; + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentNullException(nameof(name)); + + return SetExpression(name, Expression.Constant(value)); } /// @@ -141,8 +208,7 @@ public ExpressionInterpreter SetVariable(string name, object value) /// public ExpressionInterpreter SetVariable(string name, T value) { - _settings.SetVariable(name, value); - return this; + return SetVariable(name, value, typeof(T)); } /// @@ -154,8 +220,12 @@ public ExpressionInterpreter SetVariable(string name, T value) /// public ExpressionInterpreter SetVariable(string name, object value, Type type) { - _settings.SetVariable(name, value, type); - return this; + if (type == null) + throw new ArgumentNullException(nameof(type)); + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentNullException(nameof(name)); + + return SetExpression(name, Expression.Constant(value, type)); } /// @@ -167,8 +237,7 @@ public ExpressionInterpreter SetVariable(string name, object value, Type type) /// public ExpressionInterpreter SetExpression(string name, Expression expression) { - _settings.SetVariable(name, expression); - return this; + return SetIdentifier(new Identifier(name, expression)); } /// @@ -179,7 +248,9 @@ public ExpressionInterpreter SetExpression(string name, Expression expression) /// public ExpressionInterpreter SetIdentifiers(IEnumerable identifiers) { - _settings.SetIdentifiers(identifiers); + foreach (var i in identifiers) + SetIdentifier(i); + return this; } @@ -191,10 +262,82 @@ public ExpressionInterpreter SetIdentifiers(IEnumerable identifiers) /// public ExpressionInterpreter SetIdentifier(Identifier identifier) { - _settings.SetIdentifier(identifier); + if (identifier == null) + throw new ArgumentNullException(nameof(identifier)); + + if (LanguageConstants.ReservedKeywords.Contains(identifier.Name)) + throw new InvalidOperationException($"{identifier.Name} is a reserved word"); + + _settings.Identifiers[identifier.Name] = identifier; + + return this; + } + #endregion + + #region Register referenced types + /// + /// Allow the specified type to be used inside an expression. The type will be available using its name. + /// If the type contains method extensions methods they will be available inside expressions. + /// + /// + /// + public ExpressionInterpreter Reference(Type type) + { + if (type == null) + throw new ArgumentNullException(nameof(type)); + + return Reference(type, type.Name); + } + + /// + /// Allow the specified type to be used inside an expression. + /// See Reference(Type, string) method. + /// + /// + /// + public ExpressionInterpreter Reference(IEnumerable types) + { + if (types == null) + throw new ArgumentNullException(nameof(types)); + + foreach (var t in types) + Reference(t); + return this; } + /// + /// Allow the specified type to be used inside an expression by using a custom alias. + /// If the type contains extensions methods they will be available inside expressions. + /// + /// + /// Public name that must be used in the expression. + /// + public ExpressionInterpreter Reference(Type type, string typeName) + { + return Reference(new ReferenceType(typeName, type)); + } + + /// + /// Allow the specified type to be used inside an expression by using a custom alias. + /// If the type contains extensions methods they will be available inside expressions. + /// + /// + /// + public ExpressionInterpreter Reference(ReferenceType type) + { + if (type == null) + throw new ArgumentNullException(nameof(type)); + + _settings.KnownTypes[type.Name] = type; + + foreach (var extensionMethod in type.ExtensionMethods) + { + _settings.ExtensionMethods.Add(extensionMethod); + } + + return this; + } #endregion #region Parse @@ -227,11 +370,11 @@ public ParseResult Parse(string expressionText, Type expressionReturnType, param var arguments = new ParserArguments( expressionText, - _settings.ParserSettings, + _settings, expressionReturnType, parameters.Select(x => new Parameter(x))); - var expression = _settings.Visitors.Aggregate(Parser.Parse(arguments), (current, visitor) => visitor.Visit(current)); + var expression = Visitors.Aggregate(Parser.Parse(arguments), (current, visitor) => visitor.Visit(current)); var lambda = new ParseResult( expression: expression, @@ -253,7 +396,7 @@ public ParseResult Parse(string expressionText, Type expressionReturnType, param public IdentifiersInfo DetectIdentifiers(string expression) { - var detector = new Detector(_settings.ParserSettings); + var detector = new Detector(_settings); return detector.DetectIdentifiers(expression); } diff --git a/src/DynamicExpresso.Core/Interpreter.cs b/src/DynamicExpresso.Core/Interpreter.cs index a7e2b700..b8fdcff0 100644 --- a/src/DynamicExpresso.Core/Interpreter.cs +++ b/src/DynamicExpresso.Core/Interpreter.cs @@ -1,7 +1,9 @@ using DynamicExpresso.Parsing; using DynamicExpresso.Reflection; +using DynamicExpresso.Visitors; using System; using System.Collections.Generic; +using System.Linq; using System.Linq.Expressions; using DynamicExpresso.Exceptions; @@ -13,14 +15,15 @@ namespace DynamicExpresso /// public class Interpreter { - private readonly InterpreterSettings _settings; + private readonly ParserSettings _settings; + private readonly ISet _visitors = new HashSet(); #region Constructors - /// /// Creates a new Interpreter using InterpreterOptions.Default. /// - public Interpreter() : this(InterpreterOptions.Default) + public Interpreter() + : this(InterpreterOptions.Default) { } @@ -30,7 +33,34 @@ public Interpreter() : this(InterpreterOptions.Default) /// public Interpreter(InterpreterOptions options) { - _settings = new InterpreterSettings(options); + var caseInsensitive = options.HasFlag(InterpreterOptions.CaseInsensitive); + + var lateBindObject = options.HasFlag(InterpreterOptions.LateBindObject); + + _settings = new ParserSettings(caseInsensitive, lateBindObject); + + if ((options & InterpreterOptions.SystemKeywords) == InterpreterOptions.SystemKeywords) + { + SetIdentifiers(LanguageConstants.Literals); + } + + if ((options & InterpreterOptions.PrimitiveTypes) == InterpreterOptions.PrimitiveTypes) + { + Reference(LanguageConstants.PrimitiveTypes); + Reference(LanguageConstants.CSharpPrimitiveTypes); + } + + if ((options & InterpreterOptions.CommonTypes) == InterpreterOptions.CommonTypes) + { + Reference(LanguageConstants.CommonTypes); + } + + if ((options & InterpreterOptions.LambdaExpressions) == InterpreterOptions.LambdaExpressions) + { + _settings.LambdaExpressions = true; + } + + _visitors.Add(new DisableReflectionVisitor()); } /// @@ -38,30 +68,52 @@ public Interpreter(InterpreterOptions options) /// internal Interpreter(ParserSettings settings) { - _settings = new InterpreterSettings(settings); + _settings = settings; } - #endregion #region Properties - - public bool CaseInsensitive { get { return _settings.CaseInsensitive; } } + public bool CaseInsensitive + { + get + { + return _settings.CaseInsensitive; + } + } /// /// Gets a list of registeres types. Add types by using the Reference method. /// - public IEnumerable ReferencedTypes { get { return _settings.ReferencedTypes; } } + public IEnumerable ReferencedTypes + { + get + { + return _settings.KnownTypes + .Select(p => p.Value) + .ToList(); + } + } /// /// Gets a list of known identifiers. Add identifiers using SetVariable, SetFunction or SetExpression methods. /// - public IEnumerable Identifiers { get { return _settings.Identifiers; } } + public IEnumerable Identifiers + { + get + { + return _settings.Identifiers + .Select(p => p.Value) + .ToList(); + } + } /// /// Gets the available assignment operators. /// - public AssignmentOperators AssignmentOperators { get { return _settings.AssignmentOperators; } } - + public AssignmentOperators AssignmentOperators + { + get { return _settings.AssignmentOperators; } + } #endregion #region Options @@ -73,26 +125,29 @@ internal Interpreter(ParserSettings settings) /// public Interpreter SetDefaultNumberType(DefaultNumberType defaultNumberType) { - _settings.SetDefaultNumberType(defaultNumberType); + _settings.DefaultNumberType = defaultNumberType; return this; } /// - /// Allows to enable/disable assignment operators. + /// Allows to enable/disable assignment operators. /// For security when expression are generated by the users is more safe to disable assignment operators. /// /// /// public Interpreter EnableAssignment(AssignmentOperators assignmentOperators) { - _settings.EnableAssignment(assignmentOperators); + _settings.AssignmentOperators = assignmentOperators; + return this; } #endregion #region Visitors - - public ISet Visitors { get { return _settings.Visitors; } } + public ISet Visitors + { + get { return _visitors; } + } /// /// Enable reflection expression (like x.GetType().GetMethod() or typeof(double).Assembly) by removing the DisableReflectionVisitor. @@ -100,14 +155,15 @@ public Interpreter EnableAssignment(AssignmentOperators assignmentOperators) /// public Interpreter EnableReflection() { - _settings.EnableReflection(); + var visitor = Visitors.FirstOrDefault(p => p is DisableReflectionVisitor); + if (visitor != null) + Visitors.Remove(visitor); + return this; } - #endregion #region Register identifiers - /// /// Allow the specified function delegate to be called from a parsed expression. /// @@ -116,7 +172,18 @@ public Interpreter EnableReflection() /// public Interpreter SetFunction(string name, Delegate value) { - _settings.SetFunction(name, value); + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentNullException(nameof(name)); + + if (_settings.Identifiers.TryGetValue(name, out var identifier) && identifier is FunctionIdentifier fIdentifier) + { + fIdentifier.AddOverload(value); + } + else + { + SetIdentifier(new FunctionIdentifier(name, value)); + } + return this; } @@ -128,8 +195,10 @@ public Interpreter SetFunction(string name, Delegate value) /// public Interpreter SetVariable(string name, object value) { - _settings.SetVariable(name, value); - return this; + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentNullException(nameof(name)); + + return SetExpression(name, Expression.Constant(value)); } /// @@ -140,8 +209,7 @@ public Interpreter SetVariable(string name, object value) /// public Interpreter SetVariable(string name, T value) { - _settings.SetVariable(name, value); - return this; + return SetVariable(name, value, typeof(T)); } /// @@ -153,8 +221,12 @@ public Interpreter SetVariable(string name, T value) /// public Interpreter SetVariable(string name, object value, Type type) { - _settings.SetVariable(name, value, type); - return this; + if (type == null) + throw new ArgumentNullException(nameof(type)); + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentNullException(nameof(name)); + + return SetExpression(name, Expression.Constant(value, type)); } /// @@ -166,8 +238,7 @@ public Interpreter SetVariable(string name, object value, Type type) /// public Interpreter SetExpression(string name, Expression expression) { - _settings.SetExpression(name, expression); - return this; + return SetIdentifier(new Identifier(name, expression)); } /// @@ -178,7 +249,9 @@ public Interpreter SetExpression(string name, Expression expression) /// public Interpreter SetIdentifiers(IEnumerable identifiers) { - _settings.SetIdentifiers(identifiers); + foreach (var i in identifiers) + SetIdentifier(i); + return this; } @@ -190,7 +263,14 @@ public Interpreter SetIdentifiers(IEnumerable identifiers) /// public Interpreter SetIdentifier(Identifier identifier) { - _settings.SetIdentifier(identifier); + if (identifier == null) + throw new ArgumentNullException(nameof(identifier)); + + if (LanguageConstants.ReservedKeywords.Contains(identifier.Name)) + throw new InvalidOperationException($"{identifier.Name} is a reserved word"); + + _settings.Identifiers[identifier.Name] = identifier; + return this; } #endregion @@ -204,8 +284,10 @@ public Interpreter SetIdentifier(Identifier identifier) /// public Interpreter Reference(Type type) { - _settings.Reference(type); - return this; + if (type == null) + throw new ArgumentNullException(nameof(type)); + + return Reference(type, type.Name); } /// @@ -216,7 +298,12 @@ public Interpreter Reference(Type type) /// public Interpreter Reference(IEnumerable types) { - _settings.Reference(types); + if (types == null) + throw new ArgumentNullException(nameof(types)); + + foreach (var t in types) + Reference(t); + return this; } @@ -229,8 +316,7 @@ public Interpreter Reference(IEnumerable types) /// public Interpreter Reference(Type type, string typeName) { - _settings.Reference(type, typeName); - return this; + return Reference(new ReferenceType(typeName, type)); } /// @@ -241,13 +327,21 @@ public Interpreter Reference(Type type, string typeName) /// public Interpreter Reference(ReferenceType type) { - _settings.Reference(type); + if (type == null) + throw new ArgumentNullException(nameof(type)); + + _settings.KnownTypes[type.Name] = type; + + foreach (var extensionMethod in type.ExtensionMethods) + { + _settings.ExtensionMethods.Add(extensionMethod); + } + return this; } #endregion #region Parse - /// /// Parse a text expression and returns a Lambda class that can be used to invoke it. /// @@ -326,7 +420,7 @@ internal LambdaExpression ParseAsExpression(Type delegateType, string expression public Lambda ParseAs(string expressionText, params string[] parametersNames) { - return ParseAs(typeof(TDelegate), expressionText, parametersNames); + return ParseAs(typeof(TDelegate), expressionText, parametersNames); } internal Lambda ParseAs(Type delegateType, string expressionText, params string[] parametersNames) @@ -376,7 +470,7 @@ public object Eval(string expressionText, Type expressionType, params Parameter[ #region Detection public IdentifiersInfo DetectIdentifiers(string expression) { - var detector = new Detector(_settings.ParserSettings); + var detector = new Detector(_settings); return detector.DetectIdentifiers(expression); } @@ -388,7 +482,7 @@ private Lambda ParseAsLambda(string expressionText, Type expressionType, Paramet { var arguments = new ParserArguments( expressionText, - _settings.ParserSettings, + _settings, expressionType, parameters); diff --git a/src/DynamicExpresso.Core/LambdaExtensions.cs b/src/DynamicExpresso.Core/InterpreterExtensions.cs similarity index 90% rename from src/DynamicExpresso.Core/LambdaExtensions.cs rename to src/DynamicExpresso.Core/InterpreterExtensions.cs index 25a34b1f..04d77ab0 100644 --- a/src/DynamicExpresso.Core/LambdaExtensions.cs +++ b/src/DynamicExpresso.Core/InterpreterExtensions.cs @@ -9,7 +9,7 @@ namespace DynamicExpresso /// /// Lambda extensions. /// - public static class LambdaExtensions + public static class InterpreterExtensions { /// /// Compiles lambda with declared parameters. @@ -72,10 +72,11 @@ public static object Eval(this ExpressionInterpreter interpreter private static Parameter Value(this Expression> parameter) { - return new Parameter( - parameter.Parameters.First().Name, - parameter.ReturnType, - ((ConstantExpression)parameter.Body).Value); + var objectMember = Expression.Convert(parameter.Body, typeof(object)); + var getterLambda = Expression.Lambda>(objectMember); + var getter = getterLambda.Compile(); + + return new Parameter(parameter.Parameters.First().Name, parameter.ReturnType, getter()); } public static object Eval(this ExpressionInterpreter interpreter, string expression, params Parameter[] args) diff --git a/src/DynamicExpresso.Core/InterpreterSettings.cs b/src/DynamicExpresso.Core/InterpreterSettings.cs deleted file mode 100644 index f0ffbb44..00000000 --- a/src/DynamicExpresso.Core/InterpreterSettings.cs +++ /dev/null @@ -1,344 +0,0 @@ -using DynamicExpresso.Parsing; -using DynamicExpresso.Visitors; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; - -namespace DynamicExpresso -{ - /// - /// Interpreter settings - /// - public class InterpreterSettings - { - public ParserSettings ParserSettings { get; } - - public HashSet Visitors { get; } = new HashSet(); - - #region Constructors - - /// - /// Creates a new InterpreterSettings using InterpreterOptions.Default. - /// - public InterpreterSettings() : this(InterpreterOptions.Default) - { - } - - /// - /// Creates a new InterpreterSettings using the specified options. - /// - /// - public InterpreterSettings(InterpreterOptions options) - { - var caseInsensitive = options.HasFlag(InterpreterOptions.CaseInsensitive); - - var lateBindObject = options.HasFlag(InterpreterOptions.LateBindObject); - - ParserSettings = new ParserSettings(caseInsensitive, lateBindObject); - - if ((options & InterpreterOptions.SystemKeywords) == InterpreterOptions.SystemKeywords) - { - SetIdentifiers(LanguageConstants.Literals); - } - - if ((options & InterpreterOptions.PrimitiveTypes) == InterpreterOptions.PrimitiveTypes) - { - Reference(LanguageConstants.PrimitiveTypes); - Reference(LanguageConstants.CSharpPrimitiveTypes); - } - - if ((options & InterpreterOptions.CommonTypes) == InterpreterOptions.CommonTypes) - { - Reference(LanguageConstants.CommonTypes); - } - - if ((options & InterpreterOptions.LambdaExpressions) == InterpreterOptions.LambdaExpressions) - { - ParserSettings.LambdaExpressions = true; - } - - Visitors.Add(new DisableReflectionVisitor()); - } - - /// - /// Create a new interpreter with the settings copied from another interpreter - /// - internal InterpreterSettings(ParserSettings settings) - { - ParserSettings = settings; - } - - #endregion - - #region Properties - - public bool CaseInsensitive - { - get - { - return ParserSettings.CaseInsensitive; - } - } - - /// - /// Gets a list of registeres types. Add types by using the Reference method. - /// - public IEnumerable ReferencedTypes - { - get - { - return ParserSettings.KnownTypes - .Select(p => p.Value) - .ToList(); - } - } - - /// - /// Gets a list of known identifiers. Add identifiers using SetVariable, SetFunction or SetExpression methods. - /// - public IEnumerable Identifiers - { - get - { - return ParserSettings.Identifiers - .Select(p => p.Value) - .ToList(); - } - } - - /// - /// Gets the available assignment operators. - /// - public AssignmentOperators AssignmentOperators - { - get { return ParserSettings.AssignmentOperators; } - } - - #endregion - - #region Options - - /// - /// Allow to set de default numeric type when no suffix is specified (Int by default, Double if real number) - /// - /// - /// - public InterpreterSettings SetDefaultNumberType(DefaultNumberType defaultNumberType) - { - ParserSettings.DefaultNumberType = defaultNumberType; - return this; - } - - /// - /// Allows to enable/disable assignment operators. - /// For security when expression are generated by the users is more safe to disable assignment operators. - /// - /// - /// - public InterpreterSettings EnableAssignment(AssignmentOperators assignmentOperators) - { - ParserSettings.AssignmentOperators = assignmentOperators; - return this; - } - - #endregion - - #region Visitors - - /// - /// Enable reflection expression (like x.GetType().GetMethod() or typeof(double).Assembly) by removing the DisableReflectionVisitor. - /// - /// - public InterpreterSettings EnableReflection() - { - var visitor = Visitors.FirstOrDefault(p => p is DisableReflectionVisitor); - if (visitor != null) - Visitors.Remove(visitor); - - return this; - } - - #endregion - - #region Register identifiers - - /// - /// Allow the specified function delegate to be called from a parsed expression. - /// - /// - /// - /// - public InterpreterSettings SetFunction(string name, Delegate value) - { - if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentNullException(nameof(name)); - - if (ParserSettings.Identifiers.TryGetValue(name, out var identifier) && identifier is FunctionIdentifier fIdentifier) - { - fIdentifier.AddOverload(value); - } - else - { - SetIdentifier(new FunctionIdentifier(name, value)); - } - - return this; - } - - /// - /// Allow the specified variable to be used in a parsed expression. - /// - /// - /// - /// - public InterpreterSettings SetVariable(string name, object value) - { - if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentNullException(nameof(name)); - - return SetExpression(name, Expression.Constant(value)); - } - - /// - /// Allow the specified variable to be used in a parsed expression. - /// - /// - /// - /// - public InterpreterSettings SetVariable(string name, T value) - { - return SetVariable(name, value, typeof(T)); - } - - /// - /// Allow the specified variable to be used in a parsed expression. - /// - /// - /// - /// - /// - public InterpreterSettings SetVariable(string name, object value, Type type) - { - if (type == null) - throw new ArgumentNullException(nameof(type)); - - if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentNullException(nameof(name)); - - return SetExpression(name, Expression.Constant(value, type)); - } - - /// - /// Allow the specified Expression to be used in a parsed expression. - /// Basically add the specified expression as a known identifier. - /// - /// - /// - /// - public InterpreterSettings SetExpression(string name, Expression expression) - { - return SetIdentifier(new Identifier(name, expression)); - } - - /// - /// Allow the specified list of identifiers to be used in a parsed expression. - /// Basically add the specified expressions as a known identifier. - /// - /// - /// - public InterpreterSettings SetIdentifiers(IEnumerable identifiers) - { - foreach (var i in identifiers) - SetIdentifier(i); - - return this; - } - - /// - /// Allow the specified identifier to be used in a parsed expression. - /// Basically add the specified expression as a known identifier. - /// - /// - /// - public InterpreterSettings SetIdentifier(Identifier identifier) - { - if (identifier == null) - throw new ArgumentNullException(nameof(identifier)); - - if (LanguageConstants.ReservedKeywords.Contains(identifier.Name)) - throw new InvalidOperationException($"{identifier.Name} is a reserved word"); - - ParserSettings.Identifiers[identifier.Name] = identifier; - return this; - } - #endregion - - #region Register referenced types - /// - /// Allow the specified type to be used inside an expression. The type will be available using its name. - /// If the type contains method extensions methods they will be available inside expressions. - /// - /// - /// - public InterpreterSettings Reference(Type type) - { - if (type == null) - throw new ArgumentNullException(nameof(type)); - - return Reference(type, type.Name); - } - - /// - /// Allow the specified type to be used inside an expression. - /// See Reference(Type, string) method. - /// - /// - /// - public InterpreterSettings Reference(IEnumerable types) - { - if (types == null) - throw new ArgumentNullException(nameof(types)); - - foreach (var t in types) - Reference(t); - - return this; - } - - /// - /// Allow the specified type to be used inside an expression by using a custom alias. - /// If the type contains extensions methods they will be available inside expressions. - /// - /// - /// Public name that must be used in the expression. - /// - public InterpreterSettings Reference(Type type, string typeName) - { - return Reference(new ReferenceType(typeName, type)); - } - - /// - /// Allow the specified type to be used inside an expression by using a custom alias. - /// If the type contains extensions methods they will be available inside expressions. - /// - /// - /// - public InterpreterSettings Reference(ReferenceType type) - { - if (type == null) - throw new ArgumentNullException(nameof(type)); - - ParserSettings.KnownTypes[type.Name] = type; - - foreach (var extensionMethod in type.ExtensionMethods) - { - ParserSettings.ExtensionMethods.Add(extensionMethod); - } - - return this; - } - - #endregion - } -} From d5f32c2643fe8257e66283c29182d7f426e1686e Mon Sep 17 00:00:00 2001 From: eugenealeykin Date: Tue, 14 Dec 2021 12:34:07 +0200 Subject: [PATCH 6/9] internal ParserSettings --- src/DynamicExpresso.Core/Parsing/ParserSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DynamicExpresso.Core/Parsing/ParserSettings.cs b/src/DynamicExpresso.Core/Parsing/ParserSettings.cs index 7f4c4a1b..042f14fc 100644 --- a/src/DynamicExpresso.Core/Parsing/ParserSettings.cs +++ b/src/DynamicExpresso.Core/Parsing/ParserSettings.cs @@ -4,7 +4,7 @@ namespace DynamicExpresso.Parsing { - public class ParserSettings + internal class ParserSettings { private readonly Dictionary _identifiers; private readonly Dictionary _knownTypes; From 2f09b4969c9a8fe6e6422e07158bae43252d4eea Mon Sep 17 00:00:00 2001 From: eugenealeykin Date: Tue, 14 Dec 2021 13:22:05 +0200 Subject: [PATCH 7/9] comments --- .../ExpressionInterpreter.cs | 23 +++++++++ .../InterpreterExtensions.cs | 49 ++++++++++++++++++- 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/src/DynamicExpresso.Core/ExpressionInterpreter.cs b/src/DynamicExpresso.Core/ExpressionInterpreter.cs index 3f204a98..b9b38fef 100644 --- a/src/DynamicExpresso.Core/ExpressionInterpreter.cs +++ b/src/DynamicExpresso.Core/ExpressionInterpreter.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using DynamicExpresso.Exceptions; using DynamicExpresso.Reflection; using DynamicExpresso.Visitors; @@ -342,6 +343,13 @@ public ExpressionInterpreter Reference(ReferenceType type) #region Parse + /// + /// Parse a text expression and convert it into a ParseResult with Delegate type info. + /// + /// Expression statement + /// + /// + /// public ParseResult Parse(string expressionText, params string[] parametersNames) { var delegateInfo = ReflectionExtensions.GetDelegateInfo(typeof(TDelegate), parametersNames); @@ -358,11 +366,26 @@ public ParseResult Parse(string expressionText, params str identifiers: parseResult.Identifiers); } + /// + /// Parse a text expression. + /// + /// Expression statement + /// Expression input parameters + /// + /// public ParseResult Parse(string expressionText, params ParameterExpression[] parameters) { return Parse(expressionText, typeof(void), parameters); } + /// + /// Parse a text expression. + /// + /// Expression statement + /// Expected expression return type + /// Expression input parameters + /// + /// public ParseResult Parse(string expressionText, Type expressionReturnType, params ParameterExpression[] parameters) { if (parameters == null) diff --git a/src/DynamicExpresso.Core/InterpreterExtensions.cs b/src/DynamicExpresso.Core/InterpreterExtensions.cs index 04d77ab0..456e5c98 100644 --- a/src/DynamicExpresso.Core/InterpreterExtensions.cs +++ b/src/DynamicExpresso.Core/InterpreterExtensions.cs @@ -3,14 +3,31 @@ using System.Linq.Expressions; using System.Reflection; using System.Runtime.ExceptionServices; +using DynamicExpresso.Exceptions; namespace DynamicExpresso { /// - /// Lambda extensions. + /// Interpreter extensions. /// public static class InterpreterExtensions { + /// + /// Parse a text expression with expected return type. + /// + /// + /// Expression statement + /// + /// + /// + public static ParseResult ParseWithReturnType( + this ExpressionInterpreter interpreter, + string expressionText, + params ParameterExpression[] parameters) + { + return interpreter.Parse(expressionText, typeof(TReturnType), parameters); + } + /// /// Compiles lambda with declared parameters. /// @@ -21,6 +38,9 @@ public static Delegate Compile(this ParseResult parseResult) return lambdaExpression.Compile(); } + /// + /// Compiles lambda with declared parameters. + /// public static TDelegate Compile(this ParseResult parseResult) { var lambdaExpression = Expression.Lambda(parseResult.Expression, parseResult.DeclaredParameters.ToArray()); @@ -28,41 +48,65 @@ public static TDelegate Compile(this ParseResult parseResult) return lambdaExpression.Compile(); } + /// + /// Compiles lambda with declared parameters. + /// public static TDelegate Compile(this ParseResult parseResult) { return Compile((ParseResult) parseResult); } + /// + /// Convert parse result to expression. + /// public static Expression AsExpression(this ParseResult parseResult) { return Expression.Lambda(parseResult.Expression, parseResult.DeclaredParameters.ToArray()); } + /// + /// Convert parse result to expression. + /// public static Expression AsExpression(this ParseResult parseResult) { return ((ParseResult) parseResult).AsExpression(); } + /// + /// Convert parse result to lambda expression. + /// public static LambdaExpression AsLambdaExpression(this ParseResult parseResult, Type delegateType) { return Expression.Lambda(delegateType, parseResult.Expression, parseResult.DeclaredParameters.ToArray()); } + /// + /// Evaluate expression. + /// public static object Eval(this ExpressionInterpreter interpreter, string expression, Expression> a1) => interpreter.Eval(expression, a1.Value()); + /// + /// Evaluate expression. + /// public static object Eval(this ExpressionInterpreter interpreter, string expression, Expression> a1, Expression> a2) => interpreter.Eval(expression, a1.Value(), a2.Value()); + /// + /// Evaluate expression. + /// public static object Eval(this ExpressionInterpreter interpreter, string expression, Expression> a1, Expression> a2, Expression> a3) => interpreter.Eval(expression, a1.Value(), a2.Value(), a3.Value()); + /// + /// Evaluate expression. + /// public static object Eval(this ExpressionInterpreter interpreter, string expression, Expression> a1, Expression> a2, @@ -79,6 +123,9 @@ private static Parameter Value(this Expression> parameter) return new Parameter(parameter.Parameters.First().Name, parameter.ReturnType, getter()); } + /// + /// Evaluate expression. + /// public static object Eval(this ExpressionInterpreter interpreter, string expression, params Parameter[] args) { try From 2b4884d37e66a8b433149491f69af70d884e4d28 Mon Sep 17 00:00:00 2001 From: eugenealeykin Date: Tue, 14 Dec 2021 15:15:25 +0200 Subject: [PATCH 8/9] simplefied Eval extension to use Func<> instead of Expression> Renamed Parameter.Is to Parameter.Create --- .../InterpreterExtensions.cs | 31 +++++++++---------- src/DynamicExpresso.Core/Parameter.cs | 2 +- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/DynamicExpresso.Core/InterpreterExtensions.cs b/src/DynamicExpresso.Core/InterpreterExtensions.cs index 456e5c98..db91da05 100644 --- a/src/DynamicExpresso.Core/InterpreterExtensions.cs +++ b/src/DynamicExpresso.Core/InterpreterExtensions.cs @@ -84,43 +84,42 @@ public static LambdaExpression AsLambdaExpression(this ParseResult parseResult, /// Evaluate expression. /// public static object Eval(this ExpressionInterpreter interpreter, string expression, - Expression> a1) + Func a1) => interpreter.Eval(expression, a1.Value()); /// /// Evaluate expression. /// public static object Eval(this ExpressionInterpreter interpreter, string expression, - Expression> a1, - Expression> a2) + Func a1, + Func a2) => interpreter.Eval(expression, a1.Value(), a2.Value()); /// /// Evaluate expression. /// public static object Eval(this ExpressionInterpreter interpreter, string expression, - Expression> a1, - Expression> a2, - Expression> a3) + Func a1, + Func a2, + Func a3) => interpreter.Eval(expression, a1.Value(), a2.Value(), a3.Value()); /// /// Evaluate expression. /// public static object Eval(this ExpressionInterpreter interpreter, string expression, - Expression> a1, - Expression> a2, - Expression> a3, - Expression> a4) + Func a1, + Func a2, + Func a3, + Func a4) => interpreter.Eval(expression, a1.Value(), a2.Value(), a3.Value(), a4.Value()); - private static Parameter Value(this Expression> parameter) + private static Parameter Value(this Func parameter) { - var objectMember = Expression.Convert(parameter.Body, typeof(object)); - var getterLambda = Expression.Lambda>(objectMember); - var getter = getterLambda.Compile(); - - return new Parameter(parameter.Parameters.First().Name, parameter.ReturnType, getter()); + return new Parameter( + parameter.Method.GetParameters().First().Name, + parameter.Method.ReturnType, + parameter(default)); } /// diff --git a/src/DynamicExpresso.Core/Parameter.cs b/src/DynamicExpresso.Core/Parameter.cs index 5792d696..cf491858 100644 --- a/src/DynamicExpresso.Core/Parameter.cs +++ b/src/DynamicExpresso.Core/Parameter.cs @@ -38,7 +38,7 @@ public Parameter(string name, Type type, object value = null) Expression = System.Linq.Expressions.Expression.Parameter(type, name); } - public static Parameter Is(string name, T value) + public static Parameter Create(string name, T value) { return new Parameter(name, typeof(T), value); } From 559fca6da759cb829df504a127606176ad1565f5 Mon Sep 17 00:00:00 2001 From: eugenealeykin Date: Tue, 14 Dec 2021 15:57:34 +0200 Subject: [PATCH 9/9] Eval extensions review --- .../ExpressionInterpreter.cs | 15 +++++- .../InterpreterExtensions.cs | 47 ++++++++++++++----- 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/src/DynamicExpresso.Core/ExpressionInterpreter.cs b/src/DynamicExpresso.Core/ExpressionInterpreter.cs index b9b38fef..8958c63a 100644 --- a/src/DynamicExpresso.Core/ExpressionInterpreter.cs +++ b/src/DynamicExpresso.Core/ExpressionInterpreter.cs @@ -387,9 +387,22 @@ public ParseResult Parse(string expressionText, params ParameterExpression[] par /// /// public ParseResult Parse(string expressionText, Type expressionReturnType, params ParameterExpression[] parameters) + { + return Parse(expressionText, expressionReturnType, parameters.AsEnumerable()); + } + + /// + /// Parse a text expression. + /// + /// Expression statement + /// Expected expression return type + /// Expression input parameters + /// + /// + public ParseResult Parse(string expressionText, Type expressionReturnType, IEnumerable parameters) { if (parameters == null) - parameters = new ParameterExpression[0]; + parameters = Enumerable.Empty(); var arguments = new ParserArguments( expressionText, diff --git a/src/DynamicExpresso.Core/InterpreterExtensions.cs b/src/DynamicExpresso.Core/InterpreterExtensions.cs index db91da05..e706bd6b 100644 --- a/src/DynamicExpresso.Core/InterpreterExtensions.cs +++ b/src/DynamicExpresso.Core/InterpreterExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -83,36 +84,36 @@ public static LambdaExpression AsLambdaExpression(this ParseResult parseResult, /// /// Evaluate expression. /// - public static object Eval(this ExpressionInterpreter interpreter, string expression, + public static object Eval(this ExpressionInterpreter interpreter, string expressionText, Func a1) - => interpreter.Eval(expression, a1.Value()); + => interpreter.Eval(expressionText, a1.Value()); /// /// Evaluate expression. /// - public static object Eval(this ExpressionInterpreter interpreter, string expression, + public static object Eval(this ExpressionInterpreter interpreter, string expressionText, Func a1, Func a2) - => interpreter.Eval(expression, a1.Value(), a2.Value()); + => interpreter.Eval(expressionText, a1.Value(), a2.Value()); /// /// Evaluate expression. /// - public static object Eval(this ExpressionInterpreter interpreter, string expression, + public static object Eval(this ExpressionInterpreter interpreter, string expressionText, Func a1, Func a2, Func a3) - => interpreter.Eval(expression, a1.Value(), a2.Value(), a3.Value()); + => interpreter.Eval(expressionText, a1.Value(), a2.Value(), a3.Value()); /// /// Evaluate expression. /// - public static object Eval(this ExpressionInterpreter interpreter, string expression, + public static object Eval(this ExpressionInterpreter interpreter, string expressionText, Func a1, Func a2, Func a3, Func a4) - => interpreter.Eval(expression, a1.Value(), a2.Value(), a3.Value(), a4.Value()); + => interpreter.Eval(expressionText, a1.Value(), a2.Value(), a3.Value(), a4.Value()); private static Parameter Value(this Func parameter) { @@ -125,14 +126,38 @@ private static Parameter Value(this Func parameter) /// /// Evaluate expression. /// - public static object Eval(this ExpressionInterpreter interpreter, string expression, params Parameter[] args) + public static object Eval(this ExpressionInterpreter interpreter, string expressionText, params Parameter[] parameters) + { + return interpreter.Eval(expressionText, typeof(void), parameters.AsEnumerable()); + } + + /// + /// Evaluate expression. + /// + public static TReturnType Eval(this ExpressionInterpreter interpreter, string expressionText, params Parameter[] parameters) + { + return (TReturnType) interpreter.Eval(expressionText, typeof(TReturnType), parameters.AsEnumerable()); + } + + /// + /// Evaluate expression. + /// + public static object Eval(this ExpressionInterpreter interpreter, string expressionText, Type expressionReturnType, params Parameter[] parameters) + { + return interpreter.Eval(expressionText, expressionReturnType, parameters.AsEnumerable()); + } + + /// + /// Evaluate expression. + /// + public static object Eval(this ExpressionInterpreter interpreter, string expressionText, Type expressionReturnType, IEnumerable parameters) { try { return interpreter - .Parse(expression, args.Select(x => Expression.Parameter(x.Type, x.Name)).ToArray()) + .Parse(expressionText, expressionReturnType, parameters.Select(x => Expression.Parameter(x.Type, x.Name))) .Compile() - .DynamicInvoke(args.Select(x => x.Value).ToArray()); + .DynamicInvoke(parameters.Select(x => x.Value).ToArray()); } catch (TargetInvocationException exc) {