From 488367c526f0950a4d16a4d7de3832a589f8640b Mon Sep 17 00:00:00 2001 From: eugenealeykin Date: Sun, 12 Dec 2021 13:38:43 +0200 Subject: [PATCH 1/5] Separation of parse and compile. --- .../ExpressionInterpreter.cs | 459 ++++++++++++++++++ .../InterpreterExtensions.cs | 171 +++++++ src/DynamicExpresso.Core/Parameter.cs | 5 + src/DynamicExpresso.Core/ParseResult.cs | 67 +++ test/DynamicExpresso.UnitTest/NullableTest.cs | 51 +- 5 files changed, 717 insertions(+), 36 deletions(-) create mode 100644 src/DynamicExpresso.Core/ExpressionInterpreter.cs create mode 100644 src/DynamicExpresso.Core/InterpreterExtensions.cs create mode 100644 src/DynamicExpresso.Core/ParseResult.cs diff --git a/src/DynamicExpresso.Core/ExpressionInterpreter.cs b/src/DynamicExpresso.Core/ExpressionInterpreter.cs new file mode 100644 index 00000000..8958c63a --- /dev/null +++ b/src/DynamicExpresso.Core/ExpressionInterpreter.cs @@ -0,0 +1,459 @@ +using DynamicExpresso.Parsing; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using DynamicExpresso.Exceptions; +using DynamicExpresso.Reflection; +using DynamicExpresso.Visitors; + +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 ParserSettings _settings; + private readonly ISet _visitors = new HashSet(); + + #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) + { + 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()); + } + + /// + /// Create a new ExpressionInterpreter with the settings copied from another interpreter + /// + internal ExpressionInterpreter(ParserSettings settings) + { + _settings = 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.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 + .Select(p => p.Value) + .ToList(); + } + } + + /// + /// 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.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 ExpressionInterpreter EnableAssignment(AssignmentOperators assignmentOperators) + { + _settings.AssignmentOperators = assignmentOperators; + + return this; + } + #endregion + + #region Visitors + public ISet Visitors + { + get { return _visitors; } + } + + /// + /// Enable reflection expression (like x.GetType().GetMethod() or typeof(double).Assembly) by removing the DisableReflectionVisitor. + /// + /// + public ExpressionInterpreter 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 ExpressionInterpreter 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)); + } + + return this; + } + + /// + /// Allow the specified variable to be used in a parsed expression. + /// + /// + /// + /// + public ExpressionInterpreter 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 ExpressionInterpreter SetVariable(string name, T value) + { + return SetVariable(name, value, typeof(T)); + } + + /// + /// Allow the specified variable to be used in a parsed expression. + /// + /// + /// + /// + /// + public ExpressionInterpreter 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 ExpressionInterpreter 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 ExpressionInterpreter 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 ExpressionInterpreter 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; + + 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 + + /// + /// 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); + 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); + } + + /// + /// 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) + { + 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 = Enumerable.Empty(); + + var arguments = new ParserArguments( + expressionText, + _settings, + expressionReturnType, + 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 + + 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) + { + 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/InterpreterExtensions.cs b/src/DynamicExpresso.Core/InterpreterExtensions.cs new file mode 100644 index 00000000..e706bd6b --- /dev/null +++ b/src/DynamicExpresso.Core/InterpreterExtensions.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Runtime.ExceptionServices; +using DynamicExpresso.Exceptions; + +namespace DynamicExpresso +{ + /// + /// 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. + /// + public static Delegate Compile(this ParseResult parseResult) + { + var lambdaExpression = Expression.Lambda(parseResult.Expression, parseResult.DeclaredParameters.ToArray()); + + return lambdaExpression.Compile(); + } + + /// + /// Compiles lambda with declared parameters. + /// + public static TDelegate Compile(this ParseResult parseResult) + { + var lambdaExpression = Expression.Lambda(parseResult.Expression, parseResult.DeclaredParameters.ToArray()); + + 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 expressionText, + Func a1) + => interpreter.Eval(expressionText, a1.Value()); + + /// + /// Evaluate expression. + /// + public static object Eval(this ExpressionInterpreter interpreter, string expressionText, + Func a1, + Func a2) + => interpreter.Eval(expressionText, a1.Value(), a2.Value()); + + /// + /// Evaluate expression. + /// + public static object Eval(this ExpressionInterpreter interpreter, string expressionText, + Func a1, + Func a2, + Func a3) + => interpreter.Eval(expressionText, a1.Value(), a2.Value(), a3.Value()); + + /// + /// Evaluate expression. + /// + public static object Eval(this ExpressionInterpreter interpreter, string expressionText, + Func a1, + Func a2, + Func a3, + Func a4) + => interpreter.Eval(expressionText, a1.Value(), a2.Value(), a3.Value(), a4.Value()); + + private static Parameter Value(this Func parameter) + { + return new Parameter( + parameter.Method.GetParameters().First().Name, + parameter.Method.ReturnType, + parameter(default)); + } + + /// + /// Evaluate expression. + /// + 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(expressionText, expressionReturnType, parameters.Select(x => Expression.Parameter(x.Type, x.Name))) + .Compile() + .DynamicInvoke(parameters.Select(x => x.Value).ToArray()); + } + catch (TargetInvocationException exc) + { + if (exc.InnerException != null) + ExceptionDispatchInfo.Capture(exc.InnerException).Throw(); + + throw; + } + } + } +} diff --git a/src/DynamicExpresso.Core/Parameter.cs b/src/DynamicExpresso.Core/Parameter.cs index 03b0c4e6..cf491858 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 Create(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/ParseResult.cs b/src/DynamicExpresso.Core/ParseResult.cs new file mode 100644 index 00000000..033ea456 --- /dev/null +++ b/src/DynamicExpresso.Core/ParseResult.cs @@ -0,0 +1,67 @@ +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; } + } + + 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/test/DynamicExpresso.UnitTest/NullableTest.cs b/test/DynamicExpresso.UnitTest/NullableTest.cs index 7348f827..1c76620d 100644 --- a/test/DynamicExpresso.UnitTest/NullableTest.cs +++ b/test/DynamicExpresso.UnitTest/NullableTest.cs @@ -241,45 +241,24 @@ 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); - - lambda = interpreter.Parse("a - b"); - Assert.AreEqual(a - b, lambda.Invoke()); - Assert.AreEqual(typeof(TimeSpan?), 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); + Verify(interpreter, "a - b", a - b); b = null; interpreter.SetVariable("b", b, typeof(DateTimeOffset?)); - lambda = interpreter.Parse("a - b"); - Assert.AreEqual(a - b, lambda.Invoke()); - Assert.AreEqual(typeof(TimeSpan?), lambda.ReturnType); + Verify(interpreter, "a - b", a - b); + } + + 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); } } } From 234569a56516fac31a70bd6d4408a48011971839 Mon Sep 17 00:00:00 2001 From: metoule Date: Sun, 19 Dec 2021 14:11:51 +0100 Subject: [PATCH 2/5] Minimize the amount of changes for #207. --- .../ExpressionInterpreter.cs | 459 ------------------ src/DynamicExpresso.Core/Interpreter.cs | 53 +- .../InterpreterExtensions.cs | 137 +----- .../InterpreterOptions.cs | 12 +- src/DynamicExpresso.Core/Lambda.cs | 100 +--- src/DynamicExpresso.Core/ParseResult.cs | 61 ++- src/DynamicExpresso.Core/ParserArguments.cs | 5 +- src/DynamicExpresso.Core/Parsing/Parser.cs | 22 +- .../Parsing/ParserSettings.cs | 9 + test/DynamicExpresso.UnitTest/GithubIssues.cs | 15 + .../LambdaExpressionTest.cs | 12 + test/DynamicExpresso.UnitTest/NullableTest.cs | 2 +- 12 files changed, 160 insertions(+), 727 deletions(-) delete mode 100644 src/DynamicExpresso.Core/ExpressionInterpreter.cs diff --git a/src/DynamicExpresso.Core/ExpressionInterpreter.cs b/src/DynamicExpresso.Core/ExpressionInterpreter.cs deleted file mode 100644 index 8958c63a..00000000 --- a/src/DynamicExpresso.Core/ExpressionInterpreter.cs +++ /dev/null @@ -1,459 +0,0 @@ -using DynamicExpresso.Parsing; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using DynamicExpresso.Exceptions; -using DynamicExpresso.Reflection; -using DynamicExpresso.Visitors; - -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 ParserSettings _settings; - private readonly ISet _visitors = new HashSet(); - - #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) - { - 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()); - } - - /// - /// Create a new ExpressionInterpreter with the settings copied from another interpreter - /// - internal ExpressionInterpreter(ParserSettings settings) - { - _settings = 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.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 - .Select(p => p.Value) - .ToList(); - } - } - - /// - /// 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.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 ExpressionInterpreter EnableAssignment(AssignmentOperators assignmentOperators) - { - _settings.AssignmentOperators = assignmentOperators; - - return this; - } - #endregion - - #region Visitors - public ISet Visitors - { - get { return _visitors; } - } - - /// - /// Enable reflection expression (like x.GetType().GetMethod() or typeof(double).Assembly) by removing the DisableReflectionVisitor. - /// - /// - public ExpressionInterpreter 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 ExpressionInterpreter 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)); - } - - return this; - } - - /// - /// Allow the specified variable to be used in a parsed expression. - /// - /// - /// - /// - public ExpressionInterpreter 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 ExpressionInterpreter SetVariable(string name, T value) - { - return SetVariable(name, value, typeof(T)); - } - - /// - /// Allow the specified variable to be used in a parsed expression. - /// - /// - /// - /// - /// - public ExpressionInterpreter 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 ExpressionInterpreter 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 ExpressionInterpreter 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 ExpressionInterpreter 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; - - 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 - - /// - /// 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); - 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); - } - - /// - /// 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) - { - 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 = Enumerable.Empty(); - - var arguments = new ParserArguments( - expressionText, - _settings, - expressionReturnType, - 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 - - 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) - { - 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 b8fdcff0..445fe777 100644 --- a/src/DynamicExpresso.Core/Interpreter.cs +++ b/src/DynamicExpresso.Core/Interpreter.cs @@ -60,6 +60,11 @@ public Interpreter(InterpreterOptions options) _settings.LambdaExpressions = true; } + if ((options & InterpreterOptions.DetectUsedParameters) == InterpreterOptions.DetectUsedParameters) + { + _settings.DetectUsedParameters = true; + } + _visitors.Add(new DisableReflectionVisitor()); } @@ -366,7 +371,7 @@ public Lambda Parse(string expressionText, params Parameter[] parameters) /// public Lambda Parse(string expressionText, Type expressionType, params Parameter[] parameters) { - return ParseAsLambda(expressionText, expressionType, parameters); + return ParseRawExpression(expressionText, expressionType, parameters).ToLambda(); } [Obsolete("Use ParseAsDelegate(string, params string[])")] @@ -385,8 +390,8 @@ public TDelegate Parse(string expressionText, params string[] paramet /// public TDelegate ParseAsDelegate(string expressionText, params string[] parametersNames) { - var lambda = ParseAs(expressionText, parametersNames); - return lambda.Compile(); + var lambda = ParseRawExpression(expressionText, parametersNames); + return lambda.Compile(); } /// @@ -399,35 +404,14 @@ public TDelegate ParseAsDelegate(string expressionText, params string /// 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); + var lambda = ParseRawExpression(expressionText, parametersNames); + return lambda.LambdaExpression(); } 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); + var lambda = ParseRawExpression(expressionText, parametersNames); + return lambda.ToLambda(); } #endregion @@ -478,7 +462,14 @@ public IdentifiersInfo DetectIdentifiers(string expression) #region Private methods - private Lambda ParseAsLambda(string expressionText, Type expressionType, Parameter[] parameters) + internal ParseResult ParseRawExpression(string expressionText, params string[] parametersNames) + { + var delegateInfo = ReflectionExtensions.GetDelegateInfo(typeof(TDelegate), parametersNames); + var parseResult = ParseRawExpression(expressionText, delegateInfo.ReturnType, delegateInfo.Parameters); + return new ParseResult(parseResult); + } + + internal ParseResult ParseRawExpression(string expressionText, Type expressionType, Parameter[] parameters) { var arguments = new ParserArguments( expressionText, @@ -491,7 +482,7 @@ private Lambda ParseAsLambda(string expressionText, Type expressionType, Paramet foreach (var visitor in Visitors) expression = visitor.Visit(expression); - var lambda = new Lambda(expression, arguments); + var lambda = new ParseResult(expression, arguments); #if TEST_DetectIdentifiers AssertDetectIdentifiers(lambda); @@ -501,7 +492,7 @@ private Lambda ParseAsLambda(string expressionText, Type expressionType, Paramet } #if TEST_DetectIdentifiers - private void AssertDetectIdentifiers(Lambda lambda) + private void AssertDetectIdentifiers(ParseResult lambda) { var info = DetectIdentifiers(lambda.ExpressionText); diff --git a/src/DynamicExpresso.Core/InterpreterExtensions.cs b/src/DynamicExpresso.Core/InterpreterExtensions.cs index e706bd6b..31ca8353 100644 --- a/src/DynamicExpresso.Core/InterpreterExtensions.cs +++ b/src/DynamicExpresso.Core/InterpreterExtensions.cs @@ -1,10 +1,6 @@ -using System; -using System.Collections.Generic; +using System; using System.Linq; using System.Linq.Expressions; -using System.Reflection; -using System.Runtime.ExceptionServices; -using DynamicExpresso.Exceptions; namespace DynamicExpresso { @@ -14,29 +10,11 @@ namespace DynamicExpresso 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. + /// Compiles lambda with used parameters. /// public static Delegate Compile(this ParseResult parseResult) { - var lambdaExpression = Expression.Lambda(parseResult.Expression, parseResult.DeclaredParameters.ToArray()); - - return lambdaExpression.Compile(); + return parseResult.LambdaExpression().Compile(); } /// @@ -44,9 +22,7 @@ public static Delegate Compile(this ParseResult parseResult) /// public static TDelegate Compile(this ParseResult parseResult) { - var lambdaExpression = Expression.Lambda(parseResult.Expression, parseResult.DeclaredParameters.ToArray()); - - return lambdaExpression.Compile(); + return parseResult.LambdaExpression().Compile(); } /// @@ -54,118 +30,39 @@ public static TDelegate Compile(this ParseResult parseResult) /// public static TDelegate Compile(this ParseResult parseResult) { - return Compile((ParseResult) parseResult); + return Compile((ParseResult)parseResult); } /// - /// Convert parse result to expression. + /// Convert parse result to a lambda expression with used parameters. /// - public static Expression AsExpression(this ParseResult parseResult) + public static LambdaExpression LambdaExpression(this ParseResult parseResult) { - return Expression.Lambda(parseResult.Expression, parseResult.DeclaredParameters.ToArray()); + return Expression.Lambda(parseResult.Expression, parseResult.UsedParameters.Select(_ => _.Expression).ToArray()); } /// - /// Convert parse result to expression. + /// Convert parse result to a lambda expression with declared parameters. /// - public static Expression AsExpression(this ParseResult parseResult) + public static Expression LambdaExpression(this ParseResult parseResult) { - return ((ParseResult) parseResult).AsExpression(); + return Expression.Lambda(parseResult.Expression, parseResult.DeclaredParameters.Select(_ => _.Expression).ToArray()); } /// - /// Convert parse result to lambda expression. + /// Convert parse result to a lambda expression with declared parameters. /// - public static LambdaExpression AsLambdaExpression(this ParseResult parseResult, Type delegateType) + public static Expression LambdaExpression(this ParseResult parseResult) { - return Expression.Lambda(delegateType, parseResult.Expression, parseResult.DeclaredParameters.ToArray()); + return ((ParseResult)parseResult).LambdaExpression(); } /// - /// Evaluate expression. - /// - public static object Eval(this ExpressionInterpreter interpreter, string expressionText, - Func a1) - => interpreter.Eval(expressionText, a1.Value()); - - /// - /// Evaluate expression. - /// - public static object Eval(this ExpressionInterpreter interpreter, string expressionText, - Func a1, - Func a2) - => interpreter.Eval(expressionText, a1.Value(), a2.Value()); - - /// - /// Evaluate expression. - /// - public static object Eval(this ExpressionInterpreter interpreter, string expressionText, - Func a1, - Func a2, - Func a3) - => interpreter.Eval(expressionText, a1.Value(), a2.Value(), a3.Value()); - - /// - /// Evaluate expression. + /// Convert parse result to a lambda expression with declared parameters. /// - public static object Eval(this ExpressionInterpreter interpreter, string expressionText, - Func a1, - Func a2, - Func a3, - Func a4) - => interpreter.Eval(expressionText, a1.Value(), a2.Value(), a3.Value(), a4.Value()); - - private static Parameter Value(this Func parameter) + public static LambdaExpression LambdaExpression(this ParseResult parseResult, Type delegateType) { - return new Parameter( - parameter.Method.GetParameters().First().Name, - parameter.Method.ReturnType, - parameter(default)); - } - - /// - /// Evaluate expression. - /// - 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(expressionText, expressionReturnType, parameters.Select(x => Expression.Parameter(x.Type, x.Name))) - .Compile() - .DynamicInvoke(parameters.Select(x => x.Value).ToArray()); - } - catch (TargetInvocationException exc) - { - if (exc.InnerException != null) - ExceptionDispatchInfo.Capture(exc.InnerException).Throw(); - - throw; - } + return Expression.Lambda(delegateType, parseResult.Expression, parseResult.DeclaredParameters.Select(_ => _.Expression).ToArray()); } } } diff --git a/src/DynamicExpresso.Core/InterpreterOptions.cs b/src/DynamicExpresso.Core/InterpreterOptions.cs index 5ed789f3..bd950aaf 100644 --- a/src/DynamicExpresso.Core/InterpreterOptions.cs +++ b/src/DynamicExpresso.Core/InterpreterOptions.cs @@ -31,12 +31,16 @@ public enum InterpreterOptions /// LambdaExpressions = 32, /// - /// Load all default configurations: PrimitiveTypes + SystemKeywords + CommonTypes + /// Detect which parameters are actually used in the expression, to minimise the compiled lambda signature. /// - Default = PrimitiveTypes | SystemKeywords | CommonTypes, + DetectUsedParameters = 64, /// - /// Load all default configurations: PrimitiveTypes + SystemKeywords + CommonTypes + CaseInsensitive + /// Load all default configurations: PrimitiveTypes + SystemKeywords + CommonTypes + DetectUsedParameters /// - DefaultCaseInsensitive = PrimitiveTypes | SystemKeywords | CommonTypes | CaseInsensitive, + Default = PrimitiveTypes | SystemKeywords | CommonTypes | DetectUsedParameters, + /// + /// Load all default configurations: PrimitiveTypes + SystemKeywords + CommonTypes + DetectUsedParameters + CaseInsensitive + /// + DefaultCaseInsensitive = PrimitiveTypes | SystemKeywords | CommonTypes | DetectUsedParameters | CaseInsensitive, } } diff --git a/src/DynamicExpresso.Core/Lambda.cs b/src/DynamicExpresso.Core/Lambda.cs index 4b3e3fef..d123ca2e 100644 --- a/src/DynamicExpresso.Core/Lambda.cs +++ b/src/DynamicExpresso.Core/Lambda.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; @@ -10,53 +10,20 @@ namespace DynamicExpresso /// /// Represents a lambda expression that can be invoked. This class is thread safe. /// - public class Lambda + public class Lambda : ParseResult { - private readonly Expression _expression; - private readonly ParserArguments _parserArguments; + private readonly bool _caseInsensitive; + private readonly StringComparison _keyComparison; - private readonly Delegate _delegate; + private Delegate _delegate; - internal Lambda(Expression expression, ParserArguments parserArguments) + internal Lambda(Expression expression, ParserArguments parserArguments) : base(expression, parserArguments) { - if (expression == null) - throw new ArgumentNullException("expression"); - if (parserArguments == null) - throw new ArgumentNullException("parserArguments"); - - _expression = expression; - _parserArguments = parserArguments; - - // Note: I always compile the generic lambda. Maybe in the future this can be a setting because if I generate a typed delegate this compilation is not required. - var lambdaExpression = Expression.Lambda(_expression, _parserArguments.UsedParameters.Select(p => p.Expression).ToArray()); - _delegate = lambdaExpression.Compile(); + _caseInsensitive = parserArguments.Settings.CaseInsensitive; + _keyComparison = parserArguments.Settings.KeyComparison; } - public Expression Expression { get { return _expression; } } - public bool CaseInsensitive { get { return _parserArguments.Settings.CaseInsensitive; } } - public string ExpressionText { get { return _parserArguments.ExpressionText; } } - public Type ReturnType { get { return _delegate.Method.ReturnType; } } - - /// - /// Gets the parameters actually used in the expression parsed. - /// - /// The used parameters. - [Obsolete("Use UsedParameters or DeclaredParameters")] - public IEnumerable Parameters { get { return _parserArguments.UsedParameters; } } - - /// - /// Gets the parameters actually used in the expression parsed. - /// - /// The used parameters. - public IEnumerable UsedParameters { get { return _parserArguments.UsedParameters; } } - /// - /// Gets the parameters declared when parsing the expression. - /// - /// The declared parameters. - public IEnumerable DeclaredParameters { get { return _parserArguments.DeclaredParameters; } } - - public IEnumerable Types { get { return _parserArguments.UsedTypes; } } - public IEnumerable Identifiers { get { return _parserArguments.UsedIdentifiers; } } + public bool CaseInsensitive => _caseInsensitive; public object Invoke() { @@ -72,7 +39,7 @@ public object Invoke(IEnumerable parameters) { var args = (from usedParameter in UsedParameters from actualParameter in parameters - where usedParameter.Name.Equals(actualParameter.Name, _parserArguments.Settings.KeyComparison) + where usedParameter.Name.Equals(actualParameter.Name, _keyComparison) select actualParameter.Value) .ToArray(); @@ -110,6 +77,9 @@ public object Invoke(params object[] args) private object InvokeWithUsedParameters(object[] orderedArgs) { + if (_delegate == null) + _delegate = this.Compile(); + try { return _delegate.DynamicInvoke(orderedArgs); @@ -122,49 +92,5 @@ private object InvokeWithUsedParameters(object[] orderedArgs) throw; } } - - public override string ToString() - { - return ExpressionText; - } - - /// - /// Generate the given delegate by compiling the lambda expression. - /// - /// The delegate to generate. Delegate parameters must match the one defined when creating the expression, see UsedParameters. - public TDelegate Compile() - { - var lambdaExpression = LambdaExpression(); - return lambdaExpression.Compile(); - } - - [Obsolete("Use Compile()")] - public TDelegate Compile(IEnumerable parameters) - { - var lambdaExpression = Expression.Lambda(_expression, parameters.Select(p => p.Expression).ToArray()); - return lambdaExpression.Compile(); - } - - /// - /// Generate a lambda expression. - /// - /// The lambda expression. - /// The delegate to generate. Delegate parameters must match the one defined when creating the expression, see UsedParameters. - public Expression LambdaExpression() - { - return Expression.Lambda(_expression, DeclaredParameters.Select(p => p.Expression).ToArray()); - } - - internal LambdaExpression LambdaExpression(Type delegateType) - { - var types = delegateType.GetGenericArguments(); - - // return type - types[types.Length - 1] = _expression.Type; - - var genericType = delegateType.GetGenericTypeDefinition(); - var inferredDelegateType = genericType.MakeGenericType(types); - return Expression.Lambda(inferredDelegateType, _expression, DeclaredParameters.Select(p => p.Expression).ToArray()); - } } } diff --git a/src/DynamicExpresso.Core/ParseResult.cs b/src/DynamicExpresso.Core/ParseResult.cs index 033ea456..818a4fb7 100644 --- a/src/DynamicExpresso.Core/ParseResult.cs +++ b/src/DynamicExpresso.Core/ParseResult.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq.Expressions; namespace DynamicExpresso @@ -8,59 +9,73 @@ namespace DynamicExpresso /// public class ParseResult { - public ParseResult( - Expression expression, - IEnumerable usedParameters, - IEnumerable declaredParameters, - IEnumerable types, - IEnumerable identifiers) + private readonly Expression _expression; + private readonly ParserArguments _parserArguments; + + internal ParseResult(Expression expression, ParserArguments parserArguments) + { + _expression = expression ?? throw new ArgumentNullException(nameof(expression)); + _parserArguments = parserArguments ?? throw new ArgumentNullException(nameof(parserArguments)); + } + + internal ParseResult(ParseResult other) : this(other._expression, other._parserArguments) { - Expression = expression; - UsedParameters = usedParameters; - DeclaredParameters = declaredParameters; - Types = types; - Identifiers = identifiers; } + public string ExpressionText => _parserArguments.ExpressionText; + public virtual Type ReturnType => _expression.Type; + /// /// Gets the parsed expression. /// /// The expression. - public Expression Expression { get; } + public Expression Expression => _expression; + + /// + /// Gets the parameters actually used in the expression parsed. + /// + /// The used parameters. + [Obsolete("Use UsedParameters or DeclaredParameters")] + public IEnumerable Parameters => UsedParameters; /// /// Gets the parameters actually used in the expression parsed. /// /// The used parameters. - public IEnumerable UsedParameters { get; } + public IEnumerable UsedParameters => _parserArguments.UsedParameters; /// /// Gets the parameters declared when parsing the expression. /// /// The declared parameters. - public IEnumerable DeclaredParameters { get; } + public IEnumerable DeclaredParameters => _parserArguments.DeclaredParameters; /// /// Gets the references types in parsed expression. /// /// The references types. - public IEnumerable Types { get; } + public IEnumerable Types => _parserArguments.UsedTypes; /// /// Gets the identifiers in parsed expression. /// /// The identifiers. - public IEnumerable Identifiers { get; } + public IEnumerable Identifiers => _parserArguments.UsedIdentifiers; + + internal Lambda ToLambda() + { + return new Lambda(_expression, _parserArguments); + } + + public override string ToString() + { + return ExpressionText; + } } public class ParseResult : ParseResult { - public ParseResult( - Expression expression, - IEnumerable usedParameters, - IEnumerable declaredParameters, - IEnumerable types, - IEnumerable identifiers) : base(expression, usedParameters, declaredParameters, types, identifiers) + internal ParseResult(ParseResult parseResult) : base(parseResult) { } } diff --git a/src/DynamicExpresso.Core/ParserArguments.cs b/src/DynamicExpresso.Core/ParserArguments.cs index 620c9991..3fa79859 100644 --- a/src/DynamicExpresso.Core/ParserArguments.cs +++ b/src/DynamicExpresso.Core/ParserArguments.cs @@ -1,4 +1,4 @@ -using DynamicExpresso.Parsing; +using DynamicExpresso.Parsing; using System; using System.Collections.Generic; using System.Linq; @@ -39,6 +39,9 @@ IEnumerable declaredParameters throw new DuplicateParameterException(pe.Name); } } + + if (!Settings.DetectUsedParameters) + _usedParameters = new HashSet(_declaredParameters.Values); } public ParserSettings Settings { get; private set;} diff --git a/src/DynamicExpresso.Core/Parsing/Parser.cs b/src/DynamicExpresso.Core/Parsing/Parser.cs index ac10beff..59452e46 100644 --- a/src/DynamicExpresso.Core/Parsing/Parser.cs +++ b/src/DynamicExpresso.Core/Parsing/Parser.cs @@ -8,6 +8,7 @@ using System.Runtime.CompilerServices; using System.Text; using DynamicExpresso.Exceptions; +using DynamicExpresso.Reflection; using DynamicExpresso.Resources; using Microsoft.CSharp.RuntimeBinder; @@ -3122,7 +3123,26 @@ public override Type Type internal LambdaExpression EvalAs(Type delegateType) { - var lambdaExpr = _interpreter.ParseAsExpression(delegateType, _expressionText, _parameters.Select(p => p.Name).ToArray()); + var parametersNames = _parameters.Select(p => p.Name).ToArray(); + 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 = _interpreter.ParseRawExpression(_expressionText, returnType, delegateInfo.Parameters); + + // change delegate return type to the actual type inferred during parsing + var types = delegateType.GetGenericArguments(); + types[types.Length - 1] = lambda.Expression.Type; + + var genericType = delegateType.GetGenericTypeDefinition(); + var inferredDelegateType = genericType.MakeGenericType(types); + var lambdaExpr = Lambda(inferredDelegateType, lambda.Expression, lambda.DeclaredParameters.Select(p => p.Expression).ToArray()); + _type = lambdaExpr.Type; return lambdaExpr; } diff --git a/src/DynamicExpresso.Core/Parsing/ParserSettings.cs b/src/DynamicExpresso.Core/Parsing/ParserSettings.cs index 042f14fc..6237fe2e 100644 --- a/src/DynamicExpresso.Core/Parsing/ParserSettings.cs +++ b/src/DynamicExpresso.Core/Parsing/ParserSettings.cs @@ -31,6 +31,8 @@ public ParserSettings(bool caseInsensitive, bool lateBindObject) DefaultNumberType = DefaultNumberType.Default; LambdaExpressions = false; + + DetectUsedParameters = false; } private ParserSettings(ParserSettings other) : this(other.CaseInsensitive, other.LateBindObject) @@ -42,6 +44,7 @@ private ParserSettings(ParserSettings other) : this(other.CaseInsensitive, other AssignmentOperators = other.AssignmentOperators; DefaultNumberType = other.DefaultNumberType; LambdaExpressions = other.LambdaExpressions; + DetectUsedParameters = other.DetectUsedParameters; } /// @@ -105,5 +108,11 @@ public bool LambdaExpressions get; set; } + + public bool DetectUsedParameters + { + get; + set; + } } } diff --git a/test/DynamicExpresso.UnitTest/GithubIssues.cs b/test/DynamicExpresso.UnitTest/GithubIssues.cs index 27ea76a3..a56bc536 100644 --- a/test/DynamicExpresso.UnitTest/GithubIssues.cs +++ b/test/DynamicExpresso.UnitTest/GithubIssues.cs @@ -446,6 +446,21 @@ public void GitHub_Issue_205() Assert.AreEqual(-1, interpreter.Eval("(date1 - date2)?.Days")); } + [Test] + public void GitHub_Issue_207() + { + var interpreter = new Interpreter(InterpreterOptions.Default ^ InterpreterOptions.DetectUsedParameters); + var parameter = new Parameter("x", typeof(int)); + var expression = interpreter.Parse("x + 1", parameter).Expression; + + var lambda = interpreter + .SetExpression("value", expression) + .Parse("value + 1", parameter); + + var result = lambda.Invoke(1); + Assert.AreEqual(3, result); + } + public class Utils { public static List Array(IEnumerable collection) => new List(collection); diff --git a/test/DynamicExpresso.UnitTest/LambdaExpressionTest.cs b/test/DynamicExpresso.UnitTest/LambdaExpressionTest.cs index ba021abb..5ae41ef6 100644 --- a/test/DynamicExpresso.UnitTest/LambdaExpressionTest.cs +++ b/test/DynamicExpresso.UnitTest/LambdaExpressionTest.cs @@ -231,6 +231,18 @@ public void Parent_scope_variable() Assert.AreEqual(new[] { 4, 5, 6 }, results); } + [Test] + public void Unused_lambda_parameter() + { + var target = new Interpreter(_options); + var list = new List { 1, 2, 3 }; + target.SetVariable("myList", list); + var results = target.Eval>("myList.Select(i => 4)"); + + Assert.AreEqual(3, results.Count()); + Assert.AreEqual(new[] { 4, 4, 4 }, results); + } + [Test] public void Lambda_with_multiple_params() { diff --git a/test/DynamicExpresso.UnitTest/NullableTest.cs b/test/DynamicExpresso.UnitTest/NullableTest.cs index 1c76620d..fe885d51 100644 --- a/test/DynamicExpresso.UnitTest/NullableTest.cs +++ b/test/DynamicExpresso.UnitTest/NullableTest.cs @@ -257,7 +257,7 @@ public void NullableDateTimeOffset_DatetimeOffset() private static void Verify(Interpreter interpreter, string expression, T expected) { var parsed = interpreter.Parse(expression); - Assert.AreEqual(expected, parsed.Compile().DynamicInvoke()); + Assert.AreEqual(expected, parsed.Invoke()); Assert.AreEqual(typeof(T), parsed.Expression.Type); } } From b847ab9420644cdb64b86b91ffcae185513c79b0 Mon Sep 17 00:00:00 2001 From: eugenealeykin Date: Sun, 12 Dec 2021 13:38:43 +0200 Subject: [PATCH 3/5] Separation of parse and compile. --- .../InterpreterExtensions.cs | 133 ++++++++++++++++-- src/DynamicExpresso.Core/ParseResult.cs | 59 +++----- test/DynamicExpresso.UnitTest/NullableTest.cs | 2 +- 3 files changed, 141 insertions(+), 53 deletions(-) diff --git a/src/DynamicExpresso.Core/InterpreterExtensions.cs b/src/DynamicExpresso.Core/InterpreterExtensions.cs index 31ca8353..3ae3e410 100644 --- a/src/DynamicExpresso.Core/InterpreterExtensions.cs +++ b/src/DynamicExpresso.Core/InterpreterExtensions.cs @@ -1,6 +1,10 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using System.Reflection; +using System.Runtime.ExceptionServices; +using DynamicExpresso.Exceptions; namespace DynamicExpresso { @@ -10,11 +14,29 @@ namespace DynamicExpresso public static class InterpreterExtensions { /// - /// Compiles lambda with used parameters. + /// 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. /// public static Delegate Compile(this ParseResult parseResult) { - return parseResult.LambdaExpression().Compile(); + var lambdaExpression = Expression.Lambda(parseResult.Expression, parseResult.DeclaredParameters.ToArray()); + + return lambdaExpression.Compile(); } /// @@ -22,7 +44,9 @@ public static Delegate Compile(this ParseResult parseResult) /// public static TDelegate Compile(this ParseResult parseResult) { - return parseResult.LambdaExpression().Compile(); + var lambdaExpression = Expression.Lambda(parseResult.Expression, parseResult.DeclaredParameters.ToArray()); + + return lambdaExpression.Compile(); } /// @@ -34,35 +58,114 @@ public static TDelegate Compile(this ParseResult parseResu } /// - /// Convert parse result to a lambda expression with used parameters. + /// Convert parse result to expression. /// - public static LambdaExpression LambdaExpression(this ParseResult parseResult) + public static Expression AsExpression(this ParseResult parseResult) { - return Expression.Lambda(parseResult.Expression, parseResult.UsedParameters.Select(_ => _.Expression).ToArray()); + return Expression.Lambda(parseResult.Expression, parseResult.DeclaredParameters.ToArray()); } /// - /// Convert parse result to a lambda expression with declared parameters. + /// Convert parse result to expression. /// - public static Expression LambdaExpression(this ParseResult parseResult) + public static Expression AsExpression(this ParseResult parseResult) { - return Expression.Lambda(parseResult.Expression, parseResult.DeclaredParameters.Select(_ => _.Expression).ToArray()); + return ((ParseResult)parseResult).AsExpression(); } /// - /// Convert parse result to a lambda expression with declared parameters. + /// Convert parse result to lambda expression. /// - public static Expression LambdaExpression(this ParseResult parseResult) + public static LambdaExpression AsLambdaExpression(this ParseResult parseResult, Type delegateType) { - return ((ParseResult)parseResult).LambdaExpression(); + return Expression.Lambda(delegateType, parseResult.Expression, parseResult.DeclaredParameters.ToArray()); } /// - /// Convert parse result to a lambda expression with declared parameters. + /// Evaluate expression. + /// + public static object Eval(this ExpressionInterpreter interpreter, string expressionText, + Func a1) + => interpreter.Eval(expressionText, a1.Value()); + + /// + /// Evaluate expression. + /// + public static object Eval(this ExpressionInterpreter interpreter, string expressionText, + Func a1, + Func a2) + => interpreter.Eval(expressionText, a1.Value(), a2.Value()); + + /// + /// Evaluate expression. + /// + public static object Eval(this ExpressionInterpreter interpreter, string expressionText, + Func a1, + Func a2, + Func a3) + => interpreter.Eval(expressionText, a1.Value(), a2.Value(), a3.Value()); + + /// + /// Evaluate expression. /// - public static LambdaExpression LambdaExpression(this ParseResult parseResult, Type delegateType) + public static object Eval(this ExpressionInterpreter interpreter, string expressionText, + Func a1, + Func a2, + Func a3, + Func a4) + => interpreter.Eval(expressionText, a1.Value(), a2.Value(), a3.Value(), a4.Value()); + + private static Parameter Value(this Func parameter) { - return Expression.Lambda(delegateType, parseResult.Expression, parseResult.DeclaredParameters.Select(_ => _.Expression).ToArray()); + return new Parameter( + parameter.Method.GetParameters().First().Name, + parameter.Method.ReturnType, + parameter(default)); + } + + /// + /// Evaluate expression. + /// + 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(expressionText, expressionReturnType, parameters.Select(x => Expression.Parameter(x.Type, x.Name))) + .Compile() + .DynamicInvoke(parameters.Select(x => x.Value).ToArray()); + } + catch (TargetInvocationException exc) + { + if (exc.InnerException != null) + ExceptionDispatchInfo.Capture(exc.InnerException).Throw(); + + throw; + } } } } diff --git a/src/DynamicExpresso.Core/ParseResult.cs b/src/DynamicExpresso.Core/ParseResult.cs index 818a4fb7..1230a728 100644 --- a/src/DynamicExpresso.Core/ParseResult.cs +++ b/src/DynamicExpresso.Core/ParseResult.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq.Expressions; @@ -9,73 +8,59 @@ namespace DynamicExpresso /// public class ParseResult { - private readonly Expression _expression; - private readonly ParserArguments _parserArguments; - - internal ParseResult(Expression expression, ParserArguments parserArguments) - { - _expression = expression ?? throw new ArgumentNullException(nameof(expression)); - _parserArguments = parserArguments ?? throw new ArgumentNullException(nameof(parserArguments)); - } - - internal ParseResult(ParseResult other) : this(other._expression, other._parserArguments) + public ParseResult( + Expression expression, + IEnumerable usedParameters, + IEnumerable declaredParameters, + IEnumerable types, + IEnumerable identifiers) { + Expression = expression; + UsedParameters = usedParameters; + DeclaredParameters = declaredParameters; + Types = types; + Identifiers = identifiers; } - public string ExpressionText => _parserArguments.ExpressionText; - public virtual Type ReturnType => _expression.Type; - /// /// Gets the parsed expression. /// /// The expression. - public Expression Expression => _expression; - - /// - /// Gets the parameters actually used in the expression parsed. - /// - /// The used parameters. - [Obsolete("Use UsedParameters or DeclaredParameters")] - public IEnumerable Parameters => UsedParameters; + public Expression Expression { get; } /// /// Gets the parameters actually used in the expression parsed. /// /// The used parameters. - public IEnumerable UsedParameters => _parserArguments.UsedParameters; + public IEnumerable UsedParameters { get; } /// /// Gets the parameters declared when parsing the expression. /// /// The declared parameters. - public IEnumerable DeclaredParameters => _parserArguments.DeclaredParameters; + public IEnumerable DeclaredParameters { get; } /// /// Gets the references types in parsed expression. /// /// The references types. - public IEnumerable Types => _parserArguments.UsedTypes; + public IEnumerable Types { get; } /// /// Gets the identifiers in parsed expression. /// /// The identifiers. - public IEnumerable Identifiers => _parserArguments.UsedIdentifiers; - - internal Lambda ToLambda() - { - return new Lambda(_expression, _parserArguments); - } - - public override string ToString() - { - return ExpressionText; - } + public IEnumerable Identifiers { get; } } public class ParseResult : ParseResult { - internal ParseResult(ParseResult parseResult) : base(parseResult) + public ParseResult( + Expression expression, + IEnumerable usedParameters, + IEnumerable declaredParameters, + IEnumerable types, + IEnumerable identifiers) : base(expression, usedParameters, declaredParameters, types, identifiers) { } } diff --git a/test/DynamicExpresso.UnitTest/NullableTest.cs b/test/DynamicExpresso.UnitTest/NullableTest.cs index fe885d51..1c76620d 100644 --- a/test/DynamicExpresso.UnitTest/NullableTest.cs +++ b/test/DynamicExpresso.UnitTest/NullableTest.cs @@ -257,7 +257,7 @@ public void NullableDateTimeOffset_DatetimeOffset() private static void Verify(Interpreter interpreter, string expression, T expected) { var parsed = interpreter.Parse(expression); - Assert.AreEqual(expected, parsed.Invoke()); + Assert.AreEqual(expected, parsed.Compile().DynamicInvoke()); Assert.AreEqual(typeof(T), parsed.Expression.Type); } } From 95f0559bea18ce40af5204cad4e77e1b592119e6 Mon Sep 17 00:00:00 2001 From: metoule Date: Sun, 19 Dec 2021 14:16:26 +0100 Subject: [PATCH 4/5] Revert "Separation of parse and compile." This reverts commit b847ab9420644cdb64b86b91ffcae185513c79b0. --- .../InterpreterExtensions.cs | 133 ++---------------- src/DynamicExpresso.Core/ParseResult.cs | 59 +++++--- test/DynamicExpresso.UnitTest/NullableTest.cs | 2 +- 3 files changed, 53 insertions(+), 141 deletions(-) diff --git a/src/DynamicExpresso.Core/InterpreterExtensions.cs b/src/DynamicExpresso.Core/InterpreterExtensions.cs index 3ae3e410..31ca8353 100644 --- a/src/DynamicExpresso.Core/InterpreterExtensions.cs +++ b/src/DynamicExpresso.Core/InterpreterExtensions.cs @@ -1,10 +1,6 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -using System.Reflection; -using System.Runtime.ExceptionServices; -using DynamicExpresso.Exceptions; namespace DynamicExpresso { @@ -14,29 +10,11 @@ namespace DynamicExpresso 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. + /// Compiles lambda with used parameters. /// public static Delegate Compile(this ParseResult parseResult) { - var lambdaExpression = Expression.Lambda(parseResult.Expression, parseResult.DeclaredParameters.ToArray()); - - return lambdaExpression.Compile(); + return parseResult.LambdaExpression().Compile(); } /// @@ -44,9 +22,7 @@ public static Delegate Compile(this ParseResult parseResult) /// public static TDelegate Compile(this ParseResult parseResult) { - var lambdaExpression = Expression.Lambda(parseResult.Expression, parseResult.DeclaredParameters.ToArray()); - - return lambdaExpression.Compile(); + return parseResult.LambdaExpression().Compile(); } /// @@ -58,114 +34,35 @@ public static TDelegate Compile(this ParseResult parseResu } /// - /// Convert parse result to expression. + /// Convert parse result to a lambda expression with used parameters. /// - public static Expression AsExpression(this ParseResult parseResult) + public static LambdaExpression LambdaExpression(this ParseResult parseResult) { - return Expression.Lambda(parseResult.Expression, parseResult.DeclaredParameters.ToArray()); + return Expression.Lambda(parseResult.Expression, parseResult.UsedParameters.Select(_ => _.Expression).ToArray()); } /// - /// Convert parse result to expression. + /// Convert parse result to a lambda expression with declared parameters. /// - public static Expression AsExpression(this ParseResult parseResult) + public static Expression LambdaExpression(this ParseResult parseResult) { - return ((ParseResult)parseResult).AsExpression(); + return Expression.Lambda(parseResult.Expression, parseResult.DeclaredParameters.Select(_ => _.Expression).ToArray()); } /// - /// Convert parse result to lambda expression. + /// Convert parse result to a lambda expression with declared parameters. /// - public static LambdaExpression AsLambdaExpression(this ParseResult parseResult, Type delegateType) + public static Expression LambdaExpression(this ParseResult parseResult) { - return Expression.Lambda(delegateType, parseResult.Expression, parseResult.DeclaredParameters.ToArray()); + return ((ParseResult)parseResult).LambdaExpression(); } /// - /// Evaluate expression. - /// - public static object Eval(this ExpressionInterpreter interpreter, string expressionText, - Func a1) - => interpreter.Eval(expressionText, a1.Value()); - - /// - /// Evaluate expression. - /// - public static object Eval(this ExpressionInterpreter interpreter, string expressionText, - Func a1, - Func a2) - => interpreter.Eval(expressionText, a1.Value(), a2.Value()); - - /// - /// Evaluate expression. - /// - public static object Eval(this ExpressionInterpreter interpreter, string expressionText, - Func a1, - Func a2, - Func a3) - => interpreter.Eval(expressionText, a1.Value(), a2.Value(), a3.Value()); - - /// - /// Evaluate expression. + /// Convert parse result to a lambda expression with declared parameters. /// - public static object Eval(this ExpressionInterpreter interpreter, string expressionText, - Func a1, - Func a2, - Func a3, - Func a4) - => interpreter.Eval(expressionText, a1.Value(), a2.Value(), a3.Value(), a4.Value()); - - private static Parameter Value(this Func parameter) + public static LambdaExpression LambdaExpression(this ParseResult parseResult, Type delegateType) { - return new Parameter( - parameter.Method.GetParameters().First().Name, - parameter.Method.ReturnType, - parameter(default)); - } - - /// - /// Evaluate expression. - /// - 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(expressionText, expressionReturnType, parameters.Select(x => Expression.Parameter(x.Type, x.Name))) - .Compile() - .DynamicInvoke(parameters.Select(x => x.Value).ToArray()); - } - catch (TargetInvocationException exc) - { - if (exc.InnerException != null) - ExceptionDispatchInfo.Capture(exc.InnerException).Throw(); - - throw; - } + return Expression.Lambda(delegateType, parseResult.Expression, parseResult.DeclaredParameters.Select(_ => _.Expression).ToArray()); } } } diff --git a/src/DynamicExpresso.Core/ParseResult.cs b/src/DynamicExpresso.Core/ParseResult.cs index 1230a728..818a4fb7 100644 --- a/src/DynamicExpresso.Core/ParseResult.cs +++ b/src/DynamicExpresso.Core/ParseResult.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq.Expressions; @@ -8,59 +9,73 @@ namespace DynamicExpresso /// public class ParseResult { - public ParseResult( - Expression expression, - IEnumerable usedParameters, - IEnumerable declaredParameters, - IEnumerable types, - IEnumerable identifiers) + private readonly Expression _expression; + private readonly ParserArguments _parserArguments; + + internal ParseResult(Expression expression, ParserArguments parserArguments) + { + _expression = expression ?? throw new ArgumentNullException(nameof(expression)); + _parserArguments = parserArguments ?? throw new ArgumentNullException(nameof(parserArguments)); + } + + internal ParseResult(ParseResult other) : this(other._expression, other._parserArguments) { - Expression = expression; - UsedParameters = usedParameters; - DeclaredParameters = declaredParameters; - Types = types; - Identifiers = identifiers; } + public string ExpressionText => _parserArguments.ExpressionText; + public virtual Type ReturnType => _expression.Type; + /// /// Gets the parsed expression. /// /// The expression. - public Expression Expression { get; } + public Expression Expression => _expression; + + /// + /// Gets the parameters actually used in the expression parsed. + /// + /// The used parameters. + [Obsolete("Use UsedParameters or DeclaredParameters")] + public IEnumerable Parameters => UsedParameters; /// /// Gets the parameters actually used in the expression parsed. /// /// The used parameters. - public IEnumerable UsedParameters { get; } + public IEnumerable UsedParameters => _parserArguments.UsedParameters; /// /// Gets the parameters declared when parsing the expression. /// /// The declared parameters. - public IEnumerable DeclaredParameters { get; } + public IEnumerable DeclaredParameters => _parserArguments.DeclaredParameters; /// /// Gets the references types in parsed expression. /// /// The references types. - public IEnumerable Types { get; } + public IEnumerable Types => _parserArguments.UsedTypes; /// /// Gets the identifiers in parsed expression. /// /// The identifiers. - public IEnumerable Identifiers { get; } + public IEnumerable Identifiers => _parserArguments.UsedIdentifiers; + + internal Lambda ToLambda() + { + return new Lambda(_expression, _parserArguments); + } + + public override string ToString() + { + return ExpressionText; + } } public class ParseResult : ParseResult { - public ParseResult( - Expression expression, - IEnumerable usedParameters, - IEnumerable declaredParameters, - IEnumerable types, - IEnumerable identifiers) : base(expression, usedParameters, declaredParameters, types, identifiers) + internal ParseResult(ParseResult parseResult) : base(parseResult) { } } diff --git a/test/DynamicExpresso.UnitTest/NullableTest.cs b/test/DynamicExpresso.UnitTest/NullableTest.cs index 1c76620d..fe885d51 100644 --- a/test/DynamicExpresso.UnitTest/NullableTest.cs +++ b/test/DynamicExpresso.UnitTest/NullableTest.cs @@ -257,7 +257,7 @@ public void NullableDateTimeOffset_DatetimeOffset() private static void Verify(Interpreter interpreter, string expression, T expected) { var parsed = interpreter.Parse(expression); - Assert.AreEqual(expected, parsed.Compile().DynamicInvoke()); + Assert.AreEqual(expected, parsed.Invoke()); Assert.AreEqual(typeof(T), parsed.Expression.Type); } } From 4a916a3c6046c9e8c29c258d3635e92e41d137e3 Mon Sep 17 00:00:00 2001 From: metoule Date: Mon, 20 Dec 2021 09:47:20 +0100 Subject: [PATCH 5/5] Use Lazy to iniate the Lambda's delegate. --- src/DynamicExpresso.Core/Lambda.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/DynamicExpresso.Core/Lambda.cs b/src/DynamicExpresso.Core/Lambda.cs index d123ca2e..38e8ccd2 100644 --- a/src/DynamicExpresso.Core/Lambda.cs +++ b/src/DynamicExpresso.Core/Lambda.cs @@ -14,13 +14,13 @@ public class Lambda : ParseResult { private readonly bool _caseInsensitive; private readonly StringComparison _keyComparison; - - private Delegate _delegate; + private readonly Lazy _delegate; internal Lambda(Expression expression, ParserArguments parserArguments) : base(expression, parserArguments) { _caseInsensitive = parserArguments.Settings.CaseInsensitive; _keyComparison = parserArguments.Settings.KeyComparison; + _delegate = new Lazy(() => this.Compile()); } public bool CaseInsensitive => _caseInsensitive; @@ -77,12 +77,9 @@ public object Invoke(params object[] args) private object InvokeWithUsedParameters(object[] orderedArgs) { - if (_delegate == null) - _delegate = this.Compile(); - try { - return _delegate.DynamicInvoke(orderedArgs); + return _delegate.Value.DynamicInvoke(orderedArgs); } catch (TargetInvocationException exc) {