using DynamicExpresso.Exceptions; using NUnit.Framework; using System; using System.Collections.Generic; using System.Linq; namespace DynamicExpresso.UnitTest { [TestFixture] public class LambdaExpressionTest { private const InterpreterOptions _options = InterpreterOptions.Default | InterpreterOptions.LambdaExpressions; [Test] public void Invalid_Lambda_Should_Produce_a_ParseException() { var target = new Interpreter(_options); var list = new List { "abc", "dfe", "test" }; target.SetVariable("list", list); Assert.Throws(() => target.Parse("list.Select(str => str.Legnth)") ); } [Test] public void Check_Lambda_Return_Type() { var target = new Interpreter(_options); var list = new List { "abc", "dfe", "test" }; target.SetVariable("list", list); var lambda = target.Parse("list.Select(str => str.Length)"); Assert.That(lambda.ReturnType, Is.EqualTo(typeof(IEnumerable))); } [Test] public void Where_inferred_parameter_type() { var target = new Interpreter(_options); var list = new List { 1, 10, 19, 21 }; target.SetVariable("myList", list); var results = target.Eval>("myList.Where(x => x >= 19)"); Assert.That(results.Count(), Is.EqualTo(2)); Assert.That(results, Is.EqualTo(new[] { 19, 21 })); } [Test] public void Where_explicit_parameter_type() { var target = new Interpreter(_options); var list = new List { 1, 10, 19, 21 }; target.SetVariable("myList", list); var results = target.Eval>("myList.Where((int x) => x >= 19)"); Assert.That(results.Count(), Is.EqualTo(2)); Assert.That(results, Is.EqualTo(new[] { 19, 21 })); } [Test] public void Select_inferred_return_type() { var target = new Interpreter(_options); var list = new List { 1, 10, 19, 21 }; target.SetVariable("myList", list); var results = target.Eval>("myList.Select(i => i.ToString()).Select(str => str[0])"); Assert.That(results.Count(), Is.EqualTo(4)); Assert.That(results, Is.EqualTo(new[] { '1', '1', '1', '2' })); } [Test] public void Where_select() { var target = new Interpreter(_options); var list = new List { "this", "is", "awesome" }; target.SetVariable("myList", list); var results = target.Eval>("myList.Where(str => str.Length > 5).Select(str => str.ToUpper())"); Assert.That(results.Count(), Is.EqualTo(1)); Assert.That(results, Is.EqualTo(new[] { "AWESOME" })); } [Test] public void Lambda_expression_to_delegate() { var target = new Interpreter(_options); var lambda = target.Eval>("str => str.ToUpper()"); Assert.That(lambda.Invoke("test"), Is.EqualTo("TEST")); } [Test] public void Lambda_expression_no_arguments() { var target = new Interpreter(_options); var lambda = target.Eval>("() => 5 + 6"); Assert.That(lambda.Invoke(), Is.EqualTo(11)); } [Test] public void Lambda_expression_to_delegate_multi_params() { var target = new Interpreter(_options); target.SetVariable("increment", 3); var lambda = target.Eval>("(i, str) => str.ToUpper() + (i + increment)"); Assert.That(lambda.Invoke(5, "test"), Is.EqualTo("TEST8")); } [Test] public void Select_many_str() { var target = new Interpreter(_options); var list = new List { "ab", "cd" }; target.SetVariable("myList", list); var results = target.Eval>("myList.SelectMany(str => str)"); Assert.That(results.Count(), Is.EqualTo(4)); Assert.That(results, Is.EqualTo(new[] { 'a', 'b', 'c', 'd' })); } [Test] public void Select_many() { var target = new Interpreter(_options); var list = new[]{ new { Strings = new[] { "ab", "cd" } }, new { Strings = new[] { "ef", "gh" } }, }; target.SetVariable("myList", list); var results = target.Eval>("myList.SelectMany(obj => obj.Strings)"); Assert.That(results.Count(), Is.EqualTo(4)); Assert.That(results, Is.EqualTo(new[] { "ab", "cd", "ef", "gh" })); } [Test] public void Nested_lambda() { var target = new Interpreter(_options); var list = new List { "ab", "cd" }; target.SetVariable("myList", list); var results = target.Eval>("myList.Select(str => str.SingleOrDefault(c => c == 'd')).Where(c => c != '\0')"); Assert.That(results.Count(), Is.EqualTo(1)); Assert.That(results, Is.EqualTo(new[] { 'd' })); } [Test] public void Lambda_candidate_is_generic_parameter() { var target = new Interpreter(_options).Reference(typeof(ExtensionMethodExt)); var str = "cd"; target.SetVariable("str", str); var result = target.Eval("str.MySingleOrDefault(c => c == 'd')"); Assert.That(result, Is.EqualTo(str.SingleOrDefault(c => c == 'd'))); } [Test] public void Lambda_candidate_with_multiple_parameters() { var target = new Interpreter(_options).Reference(typeof(ExtensionMethodExt)); var str = "cd"; target.SetVariable("str", str); var result = target.Eval("str.WithSeveralParams((c) => c == 'd')"); Assert.That(result, Is.EqualTo('d')); result = target.Eval("str.WithSeveralParams((c, i) => c == 'd')"); Assert.That(result, Is.EqualTo('d')); result = target.Eval("str.WithSeveralParams((c, i, str2) => c == 'd')"); Assert.That(result, Is.EqualTo('d')); } [Test] public void Sum() { var target = new Interpreter(_options); var list = new List { 1, 2, 3 }; target.SetVariable("myList", list); var results = target.Eval("myList.Sum()"); Assert.That(results, Is.EqualTo(6)); } [Test] public void Max() { var target = new Interpreter(_options); var list = new List { 1, 2, 3 }; target.SetVariable("myList", list); var results = target.Eval("myList.Max()"); Assert.That(results, Is.EqualTo(3)); } [Test] public void Sum_string_length() { var target = new Interpreter(_options); var list = new List { "abc", "dfe", "test" }; target.SetVariable("myList", list); var results = target.Eval("myList.Sum(str => str.Length)"); Assert.That(results, Is.EqualTo(10)); } [Test] public void Parent_scope_variable() { var target = new Interpreter(_options); var list = new List { 1, 2, 3 }; target.SetVariable("myList", list); target.SetVariable("increment", 3); var results = target.Eval>("myList.Select(i => i + increment)"); Assert.That(results.Count(), Is.EqualTo(3)); Assert.That(results, Is.EqualTo(new[] { 4, 5, 6 })); } [Test] public void Lambda_with_multiple_params() { var target = new Interpreter(_options); var list = new List { "aaaaa", "bbbb", "ccc", "ddd" }; target.SetVariable("myList", list); var results = target.Eval>("myList.TakeWhile((item, idx) => idx <= 2 && item.Length >= 3)"); Assert.That(results.Count(), Is.EqualTo(3)); Assert.That(results, Is.EqualTo(new[] { "aaaaa", "bbbb", "ccc" })); } [Test] public void Two_lambda_parameters() { var target = new Interpreter(_options); target.Reference(typeof(WithProp)); var list = new List { "aaaaa", "bbbb", "ccc", "ddd" }; target.SetVariable("myList", list); var results = target.Eval>("myList.ToDictionary(str => new WithProp { MyStr = str }.MyStr, str => str.Length)"); Assert.That(results.Count, Is.EqualTo(4)); Assert.That(results, Is.EqualTo(list.ToDictionary(str => new WithProp { MyStr = str }.MyStr, str => str.Length))); } private class WithProp { public string MyStr { get; set; } } [Test] public void Zip() { var target = new Interpreter(_options); var strList = new List { "aa", "bb", "cc", "dd" }; var intList = new List { 1, 2, 3 }; target.SetVariable("strList", strList); target.SetVariable("intList", intList); var results = target.Eval>("strList.Zip(intList, (str, i) => str + i)"); Assert.That(results.Count(), Is.EqualTo(3)); Assert.That(results, Is.EqualTo(strList.Zip(intList, (str, i) => str + i))); } [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.That(listInt, Is.EqualTo(new[] { 2, 3 })); // 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.That(listInt, Is.EqualTo(new[] { 4, 5 })); } [Test] public void Lambda_with_parameter_AsCompiledLambda() { var target = new Interpreter(InterpreterOptions.Default | InterpreterOptions.LambdaExpressions); var parm = new Parameter("x", 1); var list = new Parameter("list", new[] { 1, 2, 3 }); var listLamba = target.Parse("list.Where(n => n > x)", list, parm).Compile>>(); var result = listLamba(list.Value as int[], 2); Assert.That(result, Is.EqualTo(new[] { 3 })); var listInt = listLamba(list.Value as int[], 1); Assert.That(listInt, Is.EqualTo(new[] { 2, 3 })); // 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.That(listInt, Is.EqualTo(new[] { 4, 5 })); } [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.That(listInt, Is.EqualTo(new[] { 3 })); } [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.That(listInt, Is.EqualTo(new[] { 2, 3 })); } public class NestedLambdaTestClass { public NestedLambdaTestClass() { } public List Children { get; set; } public string Name { get; set; } // TODO // Add support for non generics with our lambda evaluation // The below fails to compile // public string GetChildrenIdentifiers(Func f) public string GetChildrenIdentifiers(Func f) { if (Children == null) { return string.Empty; } return string.Join(",", Children.Select(f)); } } [Test] public void Lambda_WithMultipleNestedExpressions() { var root = BuildNestedTestClassHierarchy(); var expectedResult = root.GetChildrenIdentifiers( // root l1 => l1.Name + l1.GetChildrenIdentifiers( // level 2, references my parameter, plus original lamda l2 => l2.Name + l1.Name + l2.GetChildrenIdentifiers( // level 3, references my parameter, plus parameter from l1 lamda l3 => l3.Name + l2.Name + l3.GetChildrenIdentifiers( // level 4, references my parameter, plus all parameters that have been used l4 => l4.Name + l2.Name + l3.Name + l1.Name + root.Name) ))); var target = new Interpreter(InterpreterOptions.Default | InterpreterOptions.LambdaExpressions); var evalResult = target.Eval(@"root.GetChildrenIdentifiers( l1 => l1.Name + l1.GetChildrenIdentifiers( l2 => l2.Name + l1.Name + l2.GetChildrenIdentifiers( l3 => l3.Name + l2.Name + l3.GetChildrenIdentifiers( l4 => l4.Name + l2.Name + l3.Name + l1.Name + root.Name) )))", new Parameter(nameof(root), root)); Assert.That(evalResult, Is.EqualTo(expectedResult)); } [Test] public void Lambda_SameParameterNameInDifferentLambdas() { var root = BuildNestedTestClassHierarchy(); var expectedResult = root.GetChildrenIdentifiers( // root l1 => l1.Name + l1.GetChildrenIdentifiers( // level 2, references my parameter, plus original lamda l2 => l2.Name + l1.Name + l2.GetChildrenIdentifiers(l3 => l2.Name) + l2.GetChildrenIdentifiers( // level 3, references my parameter, plus parameter from l1 lamda l3 => l3.Name + l2.Name + l3.GetChildrenIdentifiers( // level 4, references my parameter, plus all parameters that have been used l4 => l4.Name + l2.Name + l3.Name + l1.Name + root.Name) ))); var target = new Interpreter(InterpreterOptions.Default | InterpreterOptions.LambdaExpressions); var evalResult = target.Eval(@"root.GetChildrenIdentifiers( l1 => l1.Name + l1.GetChildrenIdentifiers( l2 => l2.Name + l1.Name + l2.GetChildrenIdentifiers(l3 => l2.Name) + + l2.GetChildrenIdentifiers( l3 => l3.Name + l2.Name + l3.GetChildrenIdentifiers( l4 => l4.Name + l2.Name + l3.Name + l1.Name + root.Name) )))", new Parameter(nameof(root), root)); Assert.That(evalResult, Is.EqualTo(expectedResult)); } [Test] public void Lambda_CannotUseDuplicateParameterInSubLambda() { var target = new Interpreter(InterpreterOptions.Default | InterpreterOptions.LambdaExpressions); Assert.Throws(() => target.Parse(@"root.GetChildrenIdentifiers( l1 => l1.Name + l1.GetChildrenIdentifiers( l2 => l2.Name + l1.Name + l2.GetChildrenIdentifiers(l2 => l2.Name) + l2.GetChildrenIdentifiers( l3 => l3.Name + l2.Name + l3.GetChildrenIdentifiers( l4 => l4.Name + l2.Name + l3.Name + l1.Name + root.Name) )))", new Parameter("root", typeof(NestedLambdaTestClass)))); } private class Npc { public int Money { get; set; } public void AddMoney(Action action) { action(this, 10, "test"); } } [Test] public void Lambda_ShouldAllowActionLambda() { var target = new Interpreter(InterpreterOptions.LambdaExpressions); var list = new List() { new Npc { Money = 10 } }; target.SetVariable("list", list); var result = target.Eval(@"list.ForEach(n => n.Money = 5)"); Assert.That(result, Is.Null); Assert.That(list[0].Money, Is.EqualTo(5)); } [Test] public void Lambda_MultipleActionLambdaParameters() { var target = new Interpreter(InterpreterOptions.LambdaExpressions); var npc = new Npc { Money = 10 }; target.SetVariable("npc", npc); var result = target.Eval(@"npc.AddMoney((n, i, str) => n.Money = i + str.Length)"); Assert.That(result, Is.Null); Assert.That(npc.Money, Is.EqualTo(14)); } private static NestedLambdaTestClass BuildNestedTestClassHierarchy() { return new NestedLambdaTestClass() { Name = "Root", Children = new List() { new NestedLambdaTestClass() { Name = "A", Children = new List() { new NestedLambdaTestClass() { Name = "B", Children = new List() { new NestedLambdaTestClass() { Name = "C", Children = new List() { new NestedLambdaTestClass() { Name = "C" }, new NestedLambdaTestClass() { Name = "F" } } }, new NestedLambdaTestClass() { Name = "F", Children = new List() { new NestedLambdaTestClass() { Name = "C" }, new NestedLambdaTestClass() { Name = "F" } } } } }, new NestedLambdaTestClass() { Name = "D", Children = new List() { new NestedLambdaTestClass() { Name = "E", Children = new List() { new NestedLambdaTestClass() { Name = "C" }, new NestedLambdaTestClass() { Name = "F" } } }, new NestedLambdaTestClass() { Name = "G", Children = new List() { new NestedLambdaTestClass() { Name = "C" }, new NestedLambdaTestClass() { Name = "F" } } } } } } }, new NestedLambdaTestClass() { Name = "B", Children = new List() { new NestedLambdaTestClass() { Name = "B", Children = new List() { new NestedLambdaTestClass() { Name = "C", Children = new List() { new NestedLambdaTestClass() { Name = "C" }, new NestedLambdaTestClass() { Name = "F" } } }, new NestedLambdaTestClass() { Name = "F", Children = new List() { new NestedLambdaTestClass() { Name = "C" }, new NestedLambdaTestClass() { Name = "F" } } } } }, new NestedLambdaTestClass() { Name = "D", Children = new List() { new NestedLambdaTestClass() { Name = "E", Children = new List() { new NestedLambdaTestClass() { Name = "C" }, new NestedLambdaTestClass() { Name = "F", Children = new List() { new NestedLambdaTestClass() { Name = "C" }, new NestedLambdaTestClass() { Name = "F" } } } } }, new NestedLambdaTestClass() { Name = "G" } } } } } } }; } } /// /// Ensure that a lambda expression is matched to a parameter of type delegate /// (so the 1st overload shouldn't be considered during resolution) /// internal static class ExtensionMethodExt { public static TSource MySingleOrDefault(this IEnumerable source, TSource defaultValue) { return source.SingleOrDefault(); } public static TSource MySingleOrDefault(this IEnumerable source, Func predicate) { return source.SingleOrDefault(predicate); } public static TSource WithSeveralParams(this IEnumerable source, Func predicate) { return source.SingleOrDefault(predicate); } public static TSource WithSeveralParams(this IEnumerable source, Func predicate) { return source.SingleOrDefault(_ => predicate(_, 0)); } public static TSource WithSeveralParams(this IEnumerable source, Func predicate) { return source.SingleOrDefault(_ => predicate(_, 0, string.Empty)); } } }