Skip to content

Commit

Permalink
Merge branch 'master' into fix_325
Browse files Browse the repository at this point in the history
  • Loading branch information
metoule authored Nov 16, 2024
2 parents 1675a6d + 94366a5 commit 16b537c
Show file tree
Hide file tree
Showing 17 changed files with 412 additions and 371 deletions.
34 changes: 25 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, DetectorOptions option)
{
expression = PrepareExpression(expression);

Expand Down Expand Up @@ -59,24 +71,28 @@ 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 = option == DetectorOptions.IncludeChildren
? 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 (option == DetectorOptions.None && idGroup.Index > 0)
{
var previousChar = expression[idGroup.Index - 1];

Expand Down
11 changes: 11 additions & 0 deletions src/DynamicExpresso.Core/DetectorOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace DynamicExpresso
{
/// <summary>
/// Option to set the detector variable level
/// </summary>
public enum DetectorOptions
{
None = 0,
IncludeChildren = 1
}
}
15 changes: 15 additions & 0 deletions src/DynamicExpresso.Core/DynamicExpresso.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,19 @@
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
</ItemGroup>

<ItemGroup>
<EmbeddedResource Update="Resources\ErrorMessages.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>ErrorMessages.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>

<ItemGroup>
<Compile Update="Resources\ErrorMessages.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>ErrorMessages.resx</DependentUpon>
</Compile>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
using System;
using System.Runtime.Serialization;
using DynamicExpresso.Resources;

namespace DynamicExpresso.Exceptions
{
[Serializable]
public class AssignmentOperatorDisabledException : ParseException
{
public AssignmentOperatorDisabledException(string operatorString, int position)
: base(string.Format("Assignment operator '{0}' not allowed", operatorString), position)
: base(string.Format(ErrorMessages.AssignmentOperatorNotAllowed, operatorString), position)
{
OperatorString = operatorString;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
using System;
using System.Runtime.Serialization;
using DynamicExpresso.Resources;

namespace DynamicExpresso.Exceptions
{
[Serializable]
public class DuplicateParameterException : DynamicExpressoException
{
public DuplicateParameterException(string identifier)
: base(string.Format("The parameter '{0}' was defined more than once", identifier))
: base(string.Format(ErrorMessages.DuplicateParameter, identifier))
{
Identifier = identifier;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
using System;
using System.Runtime.Serialization;
using DynamicExpresso.Resources;

namespace DynamicExpresso.Exceptions
{
[Serializable]
public class NoApplicableMethodException : ParseException
{
public NoApplicableMethodException(string methodName, string methodTypeName, int position)
: base(string.Format("No applicable method '{0}' exists in type '{1}'", methodName, methodTypeName), position)
: base(string.Format(ErrorMessages.InvalidMethodCall2, methodName, methodTypeName), position)
{
MethodTypeName = methodTypeName;
MethodName = methodName;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
using System;
using System.Runtime.Serialization;
using DynamicExpresso.Resources;

namespace DynamicExpresso.Exceptions
{
[Serializable]
public class ReflectionNotAllowedException : ParseException
{
public ReflectionNotAllowedException()
: base("Reflection expression not allowed. To enable reflection use Interpreter.EnableReflection().", 0)
: base(ErrorMessages.ReflectionNotAllowed, 0)
{
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
using System;
using System.Runtime.Serialization;
using DynamicExpresso.Resources;

namespace DynamicExpresso.Exceptions
{
[Serializable]
public class UnknownIdentifierException : ParseException
{
public UnknownIdentifierException(string identifier, int position)
: base(string.Format("Unknown identifier '{0}'", identifier), position)
: base(string.Format(ErrorMessages.UnknownIdentifier, identifier), position)
{
Identifier = identifier;
}
Expand Down
51 changes: 40 additions & 11 deletions src/DynamicExpresso.Core/Interpreter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Linq;
using System.Linq.Expressions;
using DynamicExpresso.Exceptions;
using DynamicExpresso.Resources;

namespace DynamicExpresso
{
Expand All @@ -19,6 +20,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 +72,11 @@ internal Interpreter(ParserSettings settings)
{
_settings = settings;
}

#endregion

#region Properties

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

#endregion

#region Options
Expand All @@ -130,7 +135,7 @@ public Interpreter SetDefaultNumberType(DefaultNumberType defaultNumberType)
}

/// <summary>
/// Allows to enable/disable assignment operators.
/// Allows to enable/disable assignment operators.
/// For security when expression are generated by the users is more safe to disable assignment operators.
/// </summary>
/// <param name="assignmentOperators"></param>
Expand All @@ -141,9 +146,11 @@ public Interpreter EnableAssignment(AssignmentOperators assignmentOperators)

return this;
}

#endregion

#region Visitors

public ISet<ExpressionVisitor> Visitors
{
get { return _visitors; }
Expand All @@ -161,9 +168,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 +186,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 @@ -269,7 +279,7 @@ public Interpreter SetIdentifier(Identifier identifier)
throw new ArgumentNullException(nameof(identifier));

if (LanguageConstants.ReservedKeywords.Contains(identifier.Name))
throw new InvalidOperationException($"{identifier.Name} is a reserved word");
throw new InvalidOperationException(string.Format(ErrorMessages.ReservedWord, identifier.Name));

_settings.Identifiers[identifier.Name] = identifier;

Expand Down Expand Up @@ -319,9 +329,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 +397,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 +457,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 +482,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 +491,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 +529,36 @@ public object Eval(string expressionText, Type expressionType, params Parameter[
{
return Parse(expressionText, expressionType, parameters).Invoke(parameters);
}

#endregion

#region Detection

public IdentifiersInfo DetectIdentifiers(string expression)
{
var detector = new Detector(_settings);

return detector.DetectIdentifiers(expression);
return detector.DetectIdentifiers(expression, DetectorOptions.None);
}

public IdentifiersInfo DetectIdentifiers(string expression, DetectorOptions options)
{
var detector = new Detector(_settings);

return detector.DetectIdentifiers(expression, options);
}

#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 +587,7 @@ private void AssertDetectIdentifiers(Lambda lambda)
throw new Exception("Detected unknown identifiers doesn't match actual parameters");
}
#endif

#endregion
}
}
Loading

0 comments on commit 16b537c

Please sign in to comment.