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

DetectIdentifier obtain complete variable name #323

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
32 changes: 23 additions & 9 deletions src/DynamicExpresso.Core/Detector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,33 @@ internal class Detector
{
private readonly ParserSettings _settings;

private static readonly Regex IdentifiersDetectionRegex = new Regex(@"(?<id>@?[\p{L}\p{Nl}_][\p{L}\p{Nl}\p{Nd}\p{Mn}\p{Mc}\p{Pc}\p{Cf}_]*)", RegexOptions.Compiled);
private static readonly Regex RootIdentifierDetectionRegex =
new Regex(@"(?<id>@?[\p{L}\p{Nl}_][\p{L}\p{Nl}\p{Nd}\p{Mn}\p{Mc}\p{Pc}\p{Cf}_]*)", RegexOptions.Compiled);

private static readonly string Id = IdentifiersDetectionRegex.ToString();
private static readonly Regex ChildIdentifierDetectionRegex = new Regex(
@"(?<id>@?[\p{L}\p{Nl}_][\p{L}\p{Nl}\p{Nd}\p{Mn}\p{Mc}\p{Pc}\p{Cf}_]*(\.[\p{L}\p{Nl}_][\p{L}\p{Nl}\p{Nd}\p{Mn}\p{Mc}\p{Pc}\p{Cf}_]*)*)",
RegexOptions.Compiled);


private static readonly string Id = RootIdentifierDetectionRegex.ToString();
private static readonly string Type = Id.Replace("<id>", "<type>");
private static readonly Regex LambdaDetectionRegex = new Regex($@"(\((((?<withtype>({Type}\s+)?{Id}))(\s*,\s*)?)+\)|(?<withtype>{Id}))\s*=>", RegexOptions.Compiled);

private static readonly Regex StringDetectionRegex = new Regex(@"(?<!\\)?"".*?(?<!\\)""", RegexOptions.Compiled);
private static readonly Regex CharDetectionRegex = new Regex(@"(?<!\\)?'.{1,2}?(?<!\\)'", RegexOptions.Compiled);
private static readonly Regex LambdaDetectionRegex =
new Regex($@"(\((((?<withtype>({Type}\s+)?{Id}))(\s*,\s*)?)+\)|(?<withtype>{Id}))\s*=>",
RegexOptions.Compiled);

private static readonly Regex StringDetectionRegex =
new Regex(@"(?<!\\)?"".*?(?<!\\)""", RegexOptions.Compiled);

private static readonly Regex CharDetectionRegex =
new Regex(@"(?<!\\)?'.{1,2}?(?<!\\)'", RegexOptions.Compiled);

public Detector(ParserSettings settings)
{
_settings = settings;
}

public IdentifiersInfo DetectIdentifiers(string expression)
public IdentifiersInfo DetectIdentifiers(string expression, bool includeChildName = false)
{
expression = PrepareExpression(expression);

Expand Down Expand Up @@ -59,24 +71,26 @@ public IdentifiersInfo DetectIdentifiers(string expression)

// 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)
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));
}
}

var identifierRegex = includeChildName ? ChildIdentifierDetectionRegex : RootIdentifierDetectionRegex;

foreach (Match match in IdentifiersDetectionRegex.Matches(expression))
foreach (Match match in identifierRegex.Matches(expression))
{
var idGroup = match.Groups["id"];
var identifier = idGroup.Value;

if (IsReservedKeyword(identifier))
continue;

if (idGroup.Index > 0)
if (!includeChildName && idGroup.Index > 0)
{
var previousChar = expression[idGroup.Index - 1];

Expand Down
41 changes: 31 additions & 10 deletions src/DynamicExpresso.Core/Interpreter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public class Interpreter
private readonly ISet<ExpressionVisitor> _visitors = new HashSet<ExpressionVisitor>();

#region Constructors

/// <summary>
/// Creates a new Interpreter using InterpreterOptions.Default.
/// </summary>
Expand Down Expand Up @@ -70,9 +71,11 @@ internal Interpreter(ParserSettings settings)
{
_settings = settings;
}

#endregion

#region Properties

public bool CaseInsensitive
{
get
Expand Down Expand Up @@ -114,6 +117,7 @@ public AssignmentOperators AssignmentOperators
{
get { return _settings.AssignmentOperators; }
}

#endregion

#region Options
Expand Down Expand Up @@ -141,9 +145,11 @@ public Interpreter EnableAssignment(AssignmentOperators assignmentOperators)

return this;
}

#endregion

#region Visitors

public ISet<ExpressionVisitor> Visitors
{
get { return _visitors; }
Expand All @@ -161,9 +167,11 @@ public Interpreter EnableReflection()

return this;
}

#endregion

#region Register identifiers

/// <summary>
/// Allow the specified function delegate to be called from a parsed expression.
/// Overloads can be added (ie. multiple delegates can be registered with the same name).
Expand All @@ -177,7 +185,8 @@ public Interpreter SetFunction(string name, Delegate value)
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentNullException(nameof(name));

if (_settings.Identifiers.TryGetValue(name, out var identifier) && identifier is FunctionIdentifier fIdentifier)
if (_settings.Identifiers.TryGetValue(name, out var identifier) &&
identifier is FunctionIdentifier fIdentifier)
{
fIdentifier.AddOverload(value);
}
Expand Down Expand Up @@ -319,9 +328,11 @@ public Interpreter UnsetIdentifier(string name)
_settings.Identifiers.Remove(name);
return this;
}

#endregion

#region Register referenced types

/// <summary>
/// Allow the specified type to be used inside an expression. The type will be available using its name.
/// If the type contains method extensions methods they will be available inside expressions.
Expand Down Expand Up @@ -385,9 +396,11 @@ public Interpreter Reference(ReferenceType type)

return this;
}

#endregion

#region Parse

/// <summary>
/// Parse a text expression and returns a Lambda class that can be used to invoke it.
/// </summary>
Expand Down Expand Up @@ -443,13 +456,15 @@ public TDelegate ParseAsDelegate<TDelegate>(string expressionText, params string
/// <param name="parametersNames">Names of the parameters. If not specified the parameters names defined inside the delegate are used.</param>
/// <returns></returns>
/// <exception cref="ParseException"></exception>
public Expression<TDelegate> ParseAsExpression<TDelegate>(string expressionText, params string[] parametersNames)
public Expression<TDelegate> ParseAsExpression<TDelegate>(string expressionText,
params string[] parametersNames)
{
var lambda = ParseAs<TDelegate>(expressionText, parametersNames);
return lambda.LambdaExpression<TDelegate>();
}

internal LambdaExpression ParseAsExpression(Type delegateType, string expressionText, params string[] parametersNames)
internal LambdaExpression ParseAsExpression(Type delegateType, string expressionText,
params string[] parametersNames)
{
var delegateInfo = ReflectionExtensions.GetDelegateInfo(delegateType, parametersNames);

Expand All @@ -466,7 +481,7 @@ internal LambdaExpression ParseAsExpression(Type delegateType, string expression

public Lambda ParseAs<TDelegate>(string expressionText, params string[] parametersNames)
{
return ParseAs(typeof(TDelegate), expressionText, parametersNames);
return ParseAs(typeof(TDelegate), expressionText, parametersNames);
}

internal Lambda ParseAs(Type delegateType, string expressionText, params string[] parametersNames)
Expand All @@ -475,9 +490,11 @@ internal Lambda ParseAs(Type delegateType, string expressionText, params string[

return ParseAsLambda(expressionText, delegateInfo.ReturnType, delegateInfo.Parameters);
}

#endregion

#region Eval

/// <summary>
/// Parse and invoke the specified expression.
/// </summary>
Expand Down Expand Up @@ -511,26 +528,29 @@ public object Eval(string expressionText, Type expressionType, params Parameter[
{
return Parse(expressionText, expressionType, parameters).Invoke(parameters);
}

#endregion

#region Detection
public IdentifiersInfo DetectIdentifiers(string expression)

public IdentifiersInfo DetectIdentifiers(string expression, bool includeChildName = false)
{
var detector = new Detector(_settings);

return detector.DetectIdentifiers(expression);
return detector.DetectIdentifiers(expression, includeChildName);
}

#endregion

#region Private methods

private Lambda ParseAsLambda(string expressionText, Type expressionType, Parameter[] parameters)
{
var arguments = new ParserArguments(
expressionText,
_settings,
expressionType,
parameters);
expressionText,
_settings,
expressionType,
parameters);

var expression = Parser.Parse(arguments);

Expand Down Expand Up @@ -559,6 +579,7 @@ private void AssertDetectIdentifiers(Lambda lambda)
throw new Exception("Detected unknown identifiers doesn't match actual parameters");
}
#endif

#endregion
}
}
16 changes: 15 additions & 1 deletion test/DynamicExpresso.UnitTest/DetectIdentifiersTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ public void Detect_unknown_identifiers()
detectedIdentifiers.UnknownIdentifiers.ToArray());
}

[Test]
public void Detect_unknown_identifiers_with_complete_variable_name()
{
var target = new Interpreter();

var detectedIdentifiers = target.DetectIdentifiers("Contact.Personal.Year_of_birth = 1987", true);

CollectionAssert.AreEqual(
new[] { "Contact.Personal.Year_of_birth" },
detectedIdentifiers.UnknownIdentifiers.ToArray());
}

[Test]
public void Should_detect_various_format_of_identifiers()
{
Expand Down Expand Up @@ -267,7 +279,9 @@ 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)");
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());
Expand Down