Skip to content

Commit

Permalink
The identifier detection now supports lambda parameters. (#227)
Browse files Browse the repository at this point in the history
Fixes #226
  • Loading branch information
metoule authored Feb 5, 2022
1 parent 2f53262 commit 966a9f3
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 2 deletions.
46 changes: 45 additions & 1 deletion src/DynamicExpresso.Core/Detector.cs
Original file line number Diff line number Diff line change
@@ -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(?<type>[a-zA-Z_]\w*)\b";
private static readonly string Id = @"\b(?<id>[a-zA-Z_]\w*)\b";
private static readonly Regex LambdaDetectionRegex = new Regex($@"(\((((?<withtype>({Type}\s+)?{Id}))(\s*,\s*)?)+\)|(?<withtype>{Id}))\s*=>", RegexOptions.Compiled);

private static readonly Regex IdentifiersDetectionRegex = new Regex(@"([^\.]|^)\b(?<id>[a-zA-Z_]\w*)\b", RegexOptions.Compiled);

private static readonly Regex StringDetectionRegex = new Regex(@"(?<!\\)?"".*?(?<!\\)""", RegexOptions.Compiled);
Expand All @@ -26,6 +33,41 @@ public IdentifiersInfo DetectIdentifiers(string expression)
var knownIdentifiers = new HashSet<Identifier>();
var knownTypes = new HashSet<ReferenceType>();

// find lambda parameters
var lambdaParameters = new Dictionary<string, Identifier>();
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;
Expand All @@ -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
Expand Down
110 changes: 109 additions & 1 deletion test/DynamicExpresso.UnitTest/DetectIdentifiersTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Linq;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;

namespace DynamicExpresso.UnitTest
Expand Down Expand Up @@ -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<string>());

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<string>), 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);
}
}
}

0 comments on commit 966a9f3

Please sign in to comment.