diff --git a/src/DynamicExpresso.Core/Detector.cs b/src/DynamicExpresso.Core/Detector.cs index 9e6ecc0b..2db1fb14 100644 --- a/src/DynamicExpresso.Core/Detector.cs +++ b/src/DynamicExpresso.Core/Detector.cs @@ -1,13 +1,20 @@ -using DynamicExpresso.Parsing; +using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Text.RegularExpressions; +using DynamicExpresso.Parsing; namespace DynamicExpresso { internal class Detector { private readonly ParserSettings _settings; + + private static readonly string Type = @"\b(?[a-zA-Z_]\w*)\b"; + private static readonly string Id = @"\b(?[a-zA-Z_]\w*)\b"; + private static readonly Regex LambdaDetectionRegex = new Regex($@"(\((((?({Type}\s+)?{Id}))(\s*,\s*)?)+\)|(?{Id}))\s*=>", RegexOptions.Compiled); + private static readonly Regex IdentifiersDetectionRegex = new Regex(@"([^\.]|^)\b(?[a-zA-Z_]\w*)\b", RegexOptions.Compiled); private static readonly Regex StringDetectionRegex = new Regex(@"(?(); var knownTypes = new HashSet(); + // find lambda parameters + var lambdaParameters = new Dictionary(); + foreach (Match match in LambdaDetectionRegex.Matches(expression)) + { + var withtypes = match.Groups["withtype"].Captures; + var types = match.Groups["type"].Captures; + var identifiers = match.Groups["id"].Captures; + + // match identifier with its type + var t = 0; + for (var i = 0; i < withtypes.Count; i++) + { + var withtype = withtypes[i].Value; + var identifier = identifiers[i].Value; + var type = typeof(object); + if (withtype != identifier) + { + var typeName = types[t].Value; + if (_settings.KnownTypes.TryGetValue(typeName, out ReferenceType knownType)) + type = knownType.Type; + + t++; + } + + // there might be several lambda parameters with the same name; + // in that case, we ignore the detected type + if (lambdaParameters.TryGetValue(identifier, out Identifier already) && already.Expression.Type != type) + type = typeof(object); + + var defaultValue = type.IsValueType ? Activator.CreateInstance(type) : null; + lambdaParameters[identifier] = new Identifier(identifier, Expression.Constant(defaultValue, type)); + } + } + + foreach (Match match in IdentifiersDetectionRegex.Matches(expression)) { var identifier = match.Groups["id"].Value; @@ -35,6 +77,8 @@ public IdentifiersInfo DetectIdentifiers(string expression) if (_settings.Identifiers.TryGetValue(identifier, out Identifier knownIdentifier)) knownIdentifiers.Add(knownIdentifier); + else if (lambdaParameters.TryGetValue(identifier, out Identifier knownLambdaParam)) + knownIdentifiers.Add(knownLambdaParam); else if (_settings.KnownTypes.TryGetValue(identifier, out ReferenceType knownType)) knownTypes.Add(knownType); else diff --git a/test/DynamicExpresso.UnitTest/DetectIdentifiersTest.cs b/test/DynamicExpresso.UnitTest/DetectIdentifiersTest.cs index f33569d1..68071c84 100644 --- a/test/DynamicExpresso.UnitTest/DetectIdentifiersTest.cs +++ b/test/DynamicExpresso.UnitTest/DetectIdentifiersTest.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; using NUnit.Framework; namespace DynamicExpresso.UnitTest @@ -191,5 +192,112 @@ public void Detect_identifiers_inside_other_expressions() Assert.AreEqual(2, detectedIdentifiers.UnknownIdentifiers.Count()); } } + + [Test] + public void Detect_identifiers_inside_lambda_expression_GitHub_Issue_226() + { + var target = new Interpreter(InterpreterOptions.Default | InterpreterOptions.LambdaExpressions); + target.SetVariable("list", new List()); + + var detectedIdentifiers = target.DetectIdentifiers("list.Any(x => x == null)"); + Assert.IsEmpty(detectedIdentifiers.UnknownIdentifiers); + + Assert.AreEqual(3, detectedIdentifiers.Identifiers.Count()); + + Assert.AreEqual("list", detectedIdentifiers.Identifiers.ElementAt(0).Name); + Assert.AreEqual(typeof(List), detectedIdentifiers.Identifiers.ElementAt(0).Expression.Type); + + Assert.AreEqual("x", detectedIdentifiers.Identifiers.ElementAt(1).Name); + Assert.AreEqual(typeof(object), detectedIdentifiers.Identifiers.ElementAt(1).Expression.Type); + + Assert.AreEqual("null", detectedIdentifiers.Identifiers.ElementAt(2).Name); + Assert.AreEqual(typeof(object), detectedIdentifiers.Identifiers.ElementAt(2).Expression.Type); + } + + [Test] + public void Detect_identifiers_inside_lambda_expression_2() + { + var target = new Interpreter(InterpreterOptions.Default | InterpreterOptions.LambdaExpressions); + + var detectedIdentifiers = target.DetectIdentifiers("x => x + 5"); + Assert.IsEmpty(detectedIdentifiers.UnknownIdentifiers); + + Assert.AreEqual(1, detectedIdentifiers.Identifiers.Count()); + + Assert.AreEqual("x", detectedIdentifiers.Identifiers.ElementAt(0).Name); + Assert.AreEqual(typeof(object), detectedIdentifiers.Identifiers.ElementAt(0).Expression.Type); + } + + [Test] + public void Detect_identifiers_inside_lambda_expression_multiple_params() + { + var target = new Interpreter(InterpreterOptions.Default | InterpreterOptions.LambdaExpressions); + + var detectedIdentifiers = target.DetectIdentifiers("(x, y) => x + y"); + Assert.IsEmpty(detectedIdentifiers.UnknownIdentifiers); + + Assert.AreEqual(2, detectedIdentifiers.Identifiers.Count()); + + Assert.AreEqual("x", detectedIdentifiers.Identifiers.ElementAt(0).Name); + Assert.AreEqual(typeof(object), detectedIdentifiers.Identifiers.ElementAt(0).Expression.Type); + + Assert.AreEqual("y", detectedIdentifiers.Identifiers.ElementAt(1).Name); + Assert.AreEqual(typeof(object), detectedIdentifiers.Identifiers.ElementAt(1).Expression.Type); + } + + [Test] + public void Detect_identifiers_inside_lambda_expression_multiple_params_with_type() + { + var target = new Interpreter(InterpreterOptions.Default | InterpreterOptions.LambdaExpressions); + + var detectedIdentifiers = target.DetectIdentifiers("(int x, string y) => x + y"); + Assert.IsEmpty(detectedIdentifiers.UnknownIdentifiers); + + Assert.AreEqual(2, detectedIdentifiers.Types.Count()); + Assert.AreEqual("int", detectedIdentifiers.Types.ElementAt(0).Name); + Assert.AreEqual(typeof(int), detectedIdentifiers.Types.ElementAt(0).Type); + Assert.AreEqual("string", detectedIdentifiers.Types.ElementAt(1).Name); + Assert.AreEqual(typeof(string), detectedIdentifiers.Types.ElementAt(1).Type); + + Assert.AreEqual(2, detectedIdentifiers.Identifiers.Count()); + + Assert.AreEqual("x", detectedIdentifiers.Identifiers.ElementAt(0).Name); + Assert.AreEqual(typeof(int), detectedIdentifiers.Identifiers.ElementAt(0).Expression.Type); + + Assert.AreEqual("y", detectedIdentifiers.Identifiers.ElementAt(1).Name); + Assert.AreEqual(typeof(string), detectedIdentifiers.Identifiers.ElementAt(1).Expression.Type); + } + + [Test] + public void Detect_identifiers_inside_lambda_expression_duplicate_param_name() + { + var target = new Interpreter(InterpreterOptions.Default | InterpreterOptions.LambdaExpressions); + + var detectedIdentifiers = target.DetectIdentifiers("(x, int y, z, int a) => x.Select(z => z + y).Select((string a, string b) => b)"); + Assert.IsEmpty(detectedIdentifiers.UnknownIdentifiers); + + Assert.AreEqual(2, detectedIdentifiers.Types.Count()); + Assert.AreEqual("int", detectedIdentifiers.Types.ElementAt(0).Name); + Assert.AreEqual(typeof(int), detectedIdentifiers.Types.ElementAt(0).Type); + Assert.AreEqual("string", detectedIdentifiers.Types.ElementAt(1).Name); + Assert.AreEqual(typeof(string), detectedIdentifiers.Types.ElementAt(1).Type); + + Assert.AreEqual(5, detectedIdentifiers.Identifiers.Count()); + + Assert.AreEqual("x", detectedIdentifiers.Identifiers.ElementAt(0).Name); + Assert.AreEqual(typeof(object), detectedIdentifiers.Identifiers.ElementAt(0).Expression.Type); + + Assert.AreEqual("y", detectedIdentifiers.Identifiers.ElementAt(1).Name); + Assert.AreEqual(typeof(int), detectedIdentifiers.Identifiers.ElementAt(1).Expression.Type); + + Assert.AreEqual("z", detectedIdentifiers.Identifiers.ElementAt(2).Name); + Assert.AreEqual(typeof(object), detectedIdentifiers.Identifiers.ElementAt(2).Expression.Type); + + Assert.AreEqual("a", detectedIdentifiers.Identifiers.ElementAt(3).Name); + Assert.AreEqual(typeof(object), detectedIdentifiers.Identifiers.ElementAt(3).Expression.Type); + + Assert.AreEqual("b", detectedIdentifiers.Identifiers.ElementAt(4).Name); + Assert.AreEqual(typeof(string), detectedIdentifiers.Identifiers.ElementAt(4).Expression.Type); + } } }