Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The identifier detection now supports lambda parameters #227

Merged
merged 1 commit into from
Feb 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}
}
}