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

Parse and Compile separation #207 #216

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
53 changes: 22 additions & 31 deletions src/DynamicExpresso.Core/Interpreter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ public Interpreter(InterpreterOptions options)
_settings.LambdaExpressions = true;
}

if ((options & InterpreterOptions.DetectUsedParameters) == InterpreterOptions.DetectUsedParameters)
{
_settings.DetectUsedParameters = true;
}

_visitors.Add(new DisableReflectionVisitor());
}

Expand Down Expand Up @@ -366,7 +371,7 @@ public Lambda Parse(string expressionText, params Parameter[] parameters)
/// <exception cref="ParseException"></exception>
public Lambda Parse(string expressionText, Type expressionType, params Parameter[] parameters)
{
return ParseAsLambda(expressionText, expressionType, parameters);
return ParseRawExpression(expressionText, expressionType, parameters).ToLambda();
}

[Obsolete("Use ParseAsDelegate<TDelegate>(string, params string[])")]
Expand All @@ -385,8 +390,8 @@ public TDelegate Parse<TDelegate>(string expressionText, params string[] paramet
/// <exception cref="ParseException"></exception>
public TDelegate ParseAsDelegate<TDelegate>(string expressionText, params string[] parametersNames)
{
var lambda = ParseAs<TDelegate>(expressionText, parametersNames);
return lambda.Compile<TDelegate>();
var lambda = ParseRawExpression<TDelegate>(expressionText, parametersNames);
return lambda.Compile();
}

/// <summary>
Expand All @@ -399,35 +404,14 @@ public TDelegate ParseAsDelegate<TDelegate>(string expressionText, params string
/// <exception cref="ParseException"></exception>
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)
{
var delegateInfo = ReflectionExtensions.GetDelegateInfo(delegateType, parametersNames);

// return type is object means that we have no information beforehand
// => we force it to typeof(void) so that no conversion expression is emitted by the parser
// and the actual expression type is preserved
var returnType = delegateInfo.ReturnType;
if (returnType == typeof(object))
returnType = typeof(void);

var lambda = ParseAsLambda(expressionText, returnType, delegateInfo.Parameters);
return lambda.LambdaExpression(delegateType);
var lambda = ParseRawExpression<TDelegate>(expressionText, parametersNames);
return lambda.LambdaExpression();
}

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

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

return ParseAsLambda(expressionText, delegateInfo.ReturnType, delegateInfo.Parameters);
var lambda = ParseRawExpression<TDelegate>(expressionText, parametersNames);
return lambda.ToLambda();
}
#endregion

Expand Down Expand Up @@ -478,7 +462,14 @@ public IdentifiersInfo DetectIdentifiers(string expression)

#region Private methods

private Lambda ParseAsLambda(string expressionText, Type expressionType, Parameter[] parameters)
internal ParseResult<TDelegate> ParseRawExpression<TDelegate>(string expressionText, params string[] parametersNames)
{
var delegateInfo = ReflectionExtensions.GetDelegateInfo(typeof(TDelegate), parametersNames);
var parseResult = ParseRawExpression(expressionText, delegateInfo.ReturnType, delegateInfo.Parameters);
return new ParseResult<TDelegate>(parseResult);
}

internal ParseResult ParseRawExpression(string expressionText, Type expressionType, Parameter[] parameters)
{
var arguments = new ParserArguments(
expressionText,
Expand All @@ -491,7 +482,7 @@ private Lambda ParseAsLambda(string expressionText, Type expressionType, Paramet
foreach (var visitor in Visitors)
expression = visitor.Visit(expression);

var lambda = new Lambda(expression, arguments);
var lambda = new ParseResult(expression, arguments);

#if TEST_DetectIdentifiers
AssertDetectIdentifiers(lambda);
Expand All @@ -501,7 +492,7 @@ private Lambda ParseAsLambda(string expressionText, Type expressionType, Paramet
}

#if TEST_DetectIdentifiers
private void AssertDetectIdentifiers(Lambda lambda)
private void AssertDetectIdentifiers(ParseResult lambda)
{
var info = DetectIdentifiers(lambda.ExpressionText);

Expand Down
68 changes: 68 additions & 0 deletions src/DynamicExpresso.Core/InterpreterExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System;
using System.Linq;
using System.Linq.Expressions;

namespace DynamicExpresso
{
/// <summary>
/// Interpreter extensions.
/// </summary>
public static class InterpreterExtensions
{
/// <summary>
/// Compiles lambda with used parameters.
/// </summary>
public static Delegate Compile(this ParseResult parseResult)
{
return parseResult.LambdaExpression().Compile();
}

/// <summary>
/// Compiles lambda with declared parameters.
/// </summary>
public static TDelegate Compile<TDelegate>(this ParseResult parseResult)
{
return parseResult.LambdaExpression<TDelegate>().Compile();
}

/// <summary>
/// Compiles lambda with declared parameters.
/// </summary>
public static TDelegate Compile<TDelegate>(this ParseResult<TDelegate> parseResult)
{
return Compile<TDelegate>((ParseResult)parseResult);
}

/// <summary>
/// Convert parse result to a lambda expression with used parameters.
/// </summary>
public static LambdaExpression LambdaExpression(this ParseResult parseResult)
{
return Expression.Lambda(parseResult.Expression, parseResult.UsedParameters.Select(_ => _.Expression).ToArray());
}

/// <summary>
/// Convert parse result to a lambda expression with declared parameters.
/// </summary>
public static Expression<TDelegate> LambdaExpression<TDelegate>(this ParseResult parseResult)
{
return Expression.Lambda<TDelegate>(parseResult.Expression, parseResult.DeclaredParameters.Select(_ => _.Expression).ToArray());
}

/// <summary>
/// Convert parse result to a lambda expression with declared parameters.
/// </summary>
public static Expression<TDelegate> LambdaExpression<TDelegate>(this ParseResult<TDelegate> parseResult)
{
return ((ParseResult)parseResult).LambdaExpression<TDelegate>();
}

/// <summary>
/// Convert parse result to a lambda expression with declared parameters.
/// </summary>
public static LambdaExpression LambdaExpression(this ParseResult parseResult, Type delegateType)
{
return Expression.Lambda(delegateType, parseResult.Expression, parseResult.DeclaredParameters.Select(_ => _.Expression).ToArray());
}
}
}
12 changes: 8 additions & 4 deletions src/DynamicExpresso.Core/InterpreterOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,16 @@ public enum InterpreterOptions
/// </summary>
LambdaExpressions = 32,
/// <summary>
/// Load all default configurations: PrimitiveTypes + SystemKeywords + CommonTypes
/// Detect which parameters are actually used in the expression, to minimise the compiled lambda signature.
/// </summary>
Default = PrimitiveTypes | SystemKeywords | CommonTypes,
DetectUsedParameters = 64,
/// <summary>
/// Load all default configurations: PrimitiveTypes + SystemKeywords + CommonTypes + CaseInsensitive
/// Load all default configurations: PrimitiveTypes + SystemKeywords + CommonTypes + DetectUsedParameters
/// </summary>
DefaultCaseInsensitive = PrimitiveTypes | SystemKeywords | CommonTypes | CaseInsensitive,
Default = PrimitiveTypes | SystemKeywords | CommonTypes | DetectUsedParameters,
/// <summary>
/// Load all default configurations: PrimitiveTypes + SystemKeywords + CommonTypes + DetectUsedParameters + CaseInsensitive
/// </summary>
DefaultCaseInsensitive = PrimitiveTypes | SystemKeywords | CommonTypes | DetectUsedParameters | CaseInsensitive,
}
}
91 changes: 10 additions & 81 deletions src/DynamicExpresso.Core/Lambda.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
Expand All @@ -10,47 +10,20 @@ namespace DynamicExpresso
/// <summary>
/// Represents a lambda expression that can be invoked. This class is thread safe.
/// </summary>
public class Lambda
public class Lambda : ParseResult
{
private readonly Expression _expression;
private readonly ParserArguments _parserArguments;
private readonly bool _caseInsensitive;
private readonly StringComparison _keyComparison;
private readonly Lazy<Delegate> _delegate;

internal Lambda(Expression expression, ParserArguments parserArguments)
internal Lambda(Expression expression, ParserArguments parserArguments) : base(expression, parserArguments)
{
_expression = expression ?? throw new ArgumentNullException(nameof(expression));
_parserArguments = parserArguments ?? throw new ArgumentNullException(nameof(parserArguments));

// Note: I always lazy compile the generic lambda. Maybe in the future this can be a setting because if I generate a typed delegate this compilation is not required.
_delegate = new Lazy<Delegate>(() =>
Expression.Lambda(_expression, _parserArguments.UsedParameters.Select(p => p.Expression).ToArray()).Compile());
_caseInsensitive = parserArguments.Settings.CaseInsensitive;
_keyComparison = parserArguments.Settings.KeyComparison;
_delegate = new Lazy<Delegate>(() => this.Compile());
}

public Expression Expression { get { return _expression; } }
public bool CaseInsensitive { get { return _parserArguments.Settings.CaseInsensitive; } }
public string ExpressionText { get { return _parserArguments.ExpressionText; } }
public Type ReturnType { get { return Expression.Type; } }

/// <summary>
/// Gets the parameters actually used in the expression parsed.
/// </summary>
/// <value>The used parameters.</value>
[Obsolete("Use UsedParameters or DeclaredParameters")]
public IEnumerable<Parameter> Parameters { get { return _parserArguments.UsedParameters; } }

/// <summary>
/// Gets the parameters actually used in the expression parsed.
/// </summary>
/// <value>The used parameters.</value>
public IEnumerable<Parameter> UsedParameters { get { return _parserArguments.UsedParameters; } }
/// <summary>
/// Gets the parameters declared when parsing the expression.
/// </summary>
/// <value>The declared parameters.</value>
public IEnumerable<Parameter> DeclaredParameters { get { return _parserArguments.DeclaredParameters; } }

public IEnumerable<ReferenceType> Types { get { return _parserArguments.UsedTypes; } }
public IEnumerable<Identifier> Identifiers { get { return _parserArguments.UsedIdentifiers; } }
public bool CaseInsensitive => _caseInsensitive;

public object Invoke()
{
Expand All @@ -66,7 +39,7 @@ public object Invoke(IEnumerable<Parameter> parameters)
{
var args = (from usedParameter in UsedParameters
from actualParameter in parameters
where usedParameter.Name.Equals(actualParameter.Name, _parserArguments.Settings.KeyComparison)
where usedParameter.Name.Equals(actualParameter.Name, _keyComparison)
select actualParameter.Value)
.ToArray();

Expand Down Expand Up @@ -116,49 +89,5 @@ private object InvokeWithUsedParameters(object[] orderedArgs)
throw;
}
}

public override string ToString()
{
return ExpressionText;
}

/// <summary>
/// Generate the given delegate by compiling the lambda expression.
/// </summary>
/// <typeparam name="TDelegate">The delegate to generate. Delegate parameters must match the one defined when creating the expression, see UsedParameters.</typeparam>
public TDelegate Compile<TDelegate>()
{
var lambdaExpression = LambdaExpression<TDelegate>();
return lambdaExpression.Compile();
}

[Obsolete("Use Compile<TDelegate>()")]
public TDelegate Compile<TDelegate>(IEnumerable<Parameter> parameters)
{
var lambdaExpression = Expression.Lambda<TDelegate>(_expression, parameters.Select(p => p.Expression).ToArray());
return lambdaExpression.Compile();
}

/// <summary>
/// Generate a lambda expression.
/// </summary>
/// <returns>The lambda expression.</returns>
/// <typeparam name="TDelegate">The delegate to generate. Delegate parameters must match the one defined when creating the expression, see UsedParameters.</typeparam>
public Expression<TDelegate> LambdaExpression<TDelegate>()
{
return Expression.Lambda<TDelegate>(_expression, DeclaredParameters.Select(p => p.Expression).ToArray());
}

internal LambdaExpression LambdaExpression(Type delegateType)
{
var types = delegateType.GetGenericArguments();

// return type
types[types.Length - 1] = _expression.Type;

var genericType = delegateType.GetGenericTypeDefinition();
var inferredDelegateType = genericType.MakeGenericType(types);
return Expression.Lambda(inferredDelegateType, _expression, DeclaredParameters.Select(p => p.Expression).ToArray());
}
}
}
82 changes: 82 additions & 0 deletions src/DynamicExpresso.Core/ParseResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;

namespace DynamicExpresso
{
/// <summary>
/// Represents an expression parse result.
/// </summary>
public class ParseResult
{
private readonly Expression _expression;
private readonly ParserArguments _parserArguments;

internal ParseResult(Expression expression, ParserArguments parserArguments)
{
_expression = expression ?? throw new ArgumentNullException(nameof(expression));
_parserArguments = parserArguments ?? throw new ArgumentNullException(nameof(parserArguments));
}

internal ParseResult(ParseResult other) : this(other._expression, other._parserArguments)
{
}

public string ExpressionText => _parserArguments.ExpressionText;
public virtual Type ReturnType => _expression.Type;

/// <summary>
/// Gets the parsed expression.
/// </summary>
/// <value>The expression.</value>
public Expression Expression => _expression;

/// <summary>
/// Gets the parameters actually used in the expression parsed.
/// </summary>
/// <value>The used parameters.</value>
[Obsolete("Use UsedParameters or DeclaredParameters")]
public IEnumerable<Parameter> Parameters => UsedParameters;

/// <summary>
/// Gets the parameters actually used in the expression parsed.
/// </summary>
/// <value>The used parameters.</value>
public IEnumerable<Parameter> UsedParameters => _parserArguments.UsedParameters;

/// <summary>
/// Gets the parameters declared when parsing the expression.
/// </summary>
/// <value>The declared parameters.</value>
public IEnumerable<Parameter> DeclaredParameters => _parserArguments.DeclaredParameters;

/// <summary>
/// Gets the references types in parsed expression.
/// </summary>
/// <value>The references types.</value>
public IEnumerable<ReferenceType> Types => _parserArguments.UsedTypes;

/// <summary>
/// Gets the identifiers in parsed expression.
/// </summary>
/// <value>The identifiers.</value>
public IEnumerable<Identifier> Identifiers => _parserArguments.UsedIdentifiers;

internal Lambda ToLambda()
{
return new Lambda(_expression, _parserArguments);
}

public override string ToString()
{
return ExpressionText;
}
}

public class ParseResult<TDelegate> : ParseResult
{
internal ParseResult(ParseResult parseResult) : base(parseResult)
{
}
}
}
Loading