From 1792b39b3bad6b49006e8f6c966a1f6fb20c6f6d Mon Sep 17 00:00:00 2001 From: metoule Date: Fri, 10 Dec 2021 10:29:28 +0100 Subject: [PATCH] Allow a lambda expression to access the initial expression's parameters (#201) * Allow a lambda expression to access the initial expression's parameters. Fix #200 --- src/DynamicExpresso.Core/Parsing/Parser.cs | 13 ++++-- .../Parsing/ParserSettings.cs | 40 +++++++++++++------ .../LambdaExpressionTest.cs | 31 ++++++++++++++ 3 files changed, 69 insertions(+), 15 deletions(-) diff --git a/src/DynamicExpresso.Core/Parsing/Parser.cs b/src/DynamicExpresso.Core/Parsing/Parser.cs index 61b89d41..a76e1ab2 100644 --- a/src/DynamicExpresso.Core/Parsing/Parser.cs +++ b/src/DynamicExpresso.Core/Parsing/Parser.cs @@ -167,7 +167,7 @@ private Expression ParseLambdaExpression() } var lambdaBodyExp = _expressionText.Substring(startExpr, _token.pos - startExpr); - return new InterpreterExpression(_arguments.Settings, lambdaBodyExp, parameters); + return new InterpreterExpression(_arguments, lambdaBodyExp, parameters); } catch (ParseException) { @@ -3022,12 +3022,19 @@ private class InterpreterExpression : Expression private readonly IList _parameters; private Type _type; - public InterpreterExpression(ParserSettings parentSettings, string expressionText, params Parameter[] parameters) + public InterpreterExpression(ParserArguments parserArguments, string expressionText, params Parameter[] parameters) { - _interpreter = new Interpreter(parentSettings); + _interpreter = new Interpreter(parserArguments.Settings.Clone()); _expressionText = expressionText; _parameters = parameters; + // convert the parent's parameters to variables + // note: this doesn't impact the initial settings, because they're cloned + foreach (var pe in parserArguments.DeclaredParameters) + { + _interpreter.SetVariable(pe.Name, pe.Value, pe.Type); + } + // prior to evaluation, we don't know the generic arguments types _type = typeof(Func<>).Assembly.GetType($"System.Func`{parameters.Length + 1}"); } diff --git a/src/DynamicExpresso.Core/Parsing/ParserSettings.cs b/src/DynamicExpresso.Core/Parsing/ParserSettings.cs index 4a5e1c6b..042f14fc 100644 --- a/src/DynamicExpresso.Core/Parsing/ParserSettings.cs +++ b/src/DynamicExpresso.Core/Parsing/ParserSettings.cs @@ -9,8 +9,8 @@ internal class ParserSettings private readonly Dictionary _identifiers; private readonly Dictionary _knownTypes; private readonly HashSet _extensionMethods; - - public ParserSettings(bool caseInsensitive,bool lateBindObject) + + public ParserSettings(bool caseInsensitive, bool lateBindObject) { CaseInsensitive = caseInsensitive; @@ -27,12 +27,32 @@ public ParserSettings(bool caseInsensitive,bool lateBindObject) _extensionMethods = new HashSet(); AssignmentOperators = AssignmentOperators.All; - + DefaultNumberType = DefaultNumberType.Default; - + LambdaExpressions = false; } + private ParserSettings(ParserSettings other) : this(other.CaseInsensitive, other.LateBindObject) + { + _knownTypes = new Dictionary(other._knownTypes); + _identifiers = new Dictionary(other._identifiers); + _extensionMethods = new HashSet(other._extensionMethods); + + AssignmentOperators = other.AssignmentOperators; + DefaultNumberType = other.DefaultNumberType; + LambdaExpressions = other.LambdaExpressions; + } + + /// + /// Creates a deep copy of the current settings, so that the identifiers/types/methods can be changed + /// without impacting the existing settings. + /// + public ParserSettings Clone() + { + return new ParserSettings(this); + } + public IDictionary KnownTypes { get { return _knownTypes; } @@ -51,31 +71,27 @@ public HashSet ExtensionMethods public bool CaseInsensitive { get; - private set; } public bool LateBindObject { get; - private set; } - public DefaultNumberType DefaultNumberType + public StringComparison KeyComparison { get; - set; } - public StringComparison KeyComparison + public IEqualityComparer KeyComparer { get; - private set; } - public IEqualityComparer KeyComparer + public DefaultNumberType DefaultNumberType { get; - private set; + set; } public AssignmentOperators AssignmentOperators diff --git a/test/DynamicExpresso.UnitTest/LambdaExpressionTest.cs b/test/DynamicExpresso.UnitTest/LambdaExpressionTest.cs index 1ac60f55..ba021abb 100644 --- a/test/DynamicExpresso.UnitTest/LambdaExpressionTest.cs +++ b/test/DynamicExpresso.UnitTest/LambdaExpressionTest.cs @@ -269,6 +269,37 @@ public void Zip() Assert.AreEqual(3, results.Count()); Assert.AreEqual(strList.Zip(intList, (str, i) => str + i), results); } + + [Test] + public void Lambda_with_parameter() + { + var target = new Interpreter(InterpreterOptions.Default | InterpreterOptions.LambdaExpressions); + var listInt = target.Eval>("list.Where(n => n > x)", new Parameter("list", new[] { 1, 2, 3 }), new Parameter("x", 1)); + Assert.AreEqual(new[] { 2, 3 }, listInt); + + // ensure the parameters can be reused with different values + listInt = target.Eval>("list.Where(n => n > x)", new Parameter("list", new[] { 2, 4, 5 }), new Parameter("x", 2)); + Assert.AreEqual(new[] { 4, 5 }, listInt); + } + + [Test] + public void Lambda_with_parameter_2() + { + var target = new Interpreter(InterpreterOptions.Default | InterpreterOptions.LambdaExpressions); + var listInt = target.Eval>("list.Select(n => n - 1).Where(n => n > x).Select(n => n + x)", new Parameter("list", new[] { 1, 2, 3 }), new Parameter("x", 1)); + Assert.AreEqual(new[] { 3 }, listInt); + } + + [Test] + public void Lambda_with_variable() + { + var target = new Interpreter(InterpreterOptions.Default | InterpreterOptions.LambdaExpressions); + target.SetVariable("list", new[] { 1, 2, 3 }); + target.SetVariable("x", 1); + + var listInt = target.Eval>("list.Where(n => n > x)"); + Assert.AreEqual(new[] { 2, 3 }, listInt); + } } ///