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

Add support of lambda expressions #140

Merged
merged 20 commits into from
Oct 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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: 31 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Dynamic Expresso
# Dynamic Expresso

[![NuGet version](https://badge.fury.io/nu/DynamicExpresso.Core.svg)](http://badge.fury.io/nu/DynamicExpresso.Core)
[![.NET CI](https://github.com/davideicardi/DynamicExpresso/actions/workflows/ci.yml/badge.svg)](https://github.com/davideicardi/DynamicExpresso/actions/workflows/ci.yml)
Expand Down Expand Up @@ -50,6 +50,7 @@ Source code and symbols (.pdb files) for debugging are available on [Symbol Sour
- Good performance compared to other similar projects
- Partial support of generic, params array and extension methods (only with implicit generic arguments detection)
- Partial support of `dynamic` (`ExpandoObject` for get properties, method invocation and indexes(#142), see #72. `DynamicObject` for get properties and indexes, see #142)
- Partial support of lambda expressions (disabled by default, because it has a slight performance penalty)
- Case insensitive expressions (default is case sensitive)
- Ability to discover identifiers (variables, types, parameters) of a given expression
- Small footprint, generated expressions are managed classes, can be unloaded and can be executed in a single appdomain
Expand Down Expand Up @@ -329,6 +330,33 @@ Assert.AreEqual(x.Count(), target.Eval("x.Count()"));
- Generics, only partially supported (only implicit, you cannot invoke an explicit generic method)
- Params array (see C# `params` keyword)

### Lambda expressions
Dynamic Expresso has partial supports of lambda expressions. For example, you can use any Linq method:

```csharp
var x = new string[] { "this", "is", "awesome" };
var options = InterpreterOptions.Default | InterpreterOptions.LambdaExpressions; // enable lambda expressions
var target = new Interpreter(options)
.SetVariable("x", x);

var results = target.Eval<IEnumerable<string>>("x.Where(str => str.Length > 5).Select(str => str.ToUpper())");
Assert.AreEqual(new[] { "AWESOME" }, results);
```

Note that parsing lambda expressions is disabled by default, because it has a slight performance cost.
To enable them, you must set the `InterpreterOptions.LambdaExpressions` flag.

It's also possible to create a delegate directly from a lambda expression:

```csharp
var options = InterpreterOptions.Default | InterpreterOptions.LambdaExpressions; // enable lambda expressions
var target = new Interpreter(options)
.SetVariable("increment", 3); // access a variable from the lambda expression

var myFunc = target.Eval<Func<int, string, string>>("(i, str) => str.ToUpper() + (i + increment)");
Assert.AreEqual("TEST8", lambda.Invoke(5, "test"));
```

### Case sensitive/insensitive
By default all expressions are considered case sensitive (`VARX` is different than `varx`, as in C#).
There is an option to use a case insensitive parser. For example:
Expand All @@ -344,6 +372,8 @@ Assert.AreEqual(x, target.Eval("x", parameters));
Assert.AreEqual(x, target.Eval("X", parameters));
```



## Identifiers detection
Sometimes you need to check which identifiers (variables, types, parameters) are used in expression before parsing it.
Maybe because you want to validate it or you want to ask the user to enter parameters value of a given expression.
Expand Down
26 changes: 25 additions & 1 deletion src/DynamicExpresso.Core/Interpreter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,21 @@ public Interpreter(InterpreterOptions options)
Reference(LanguageConstants.CommonTypes);
}

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

_visitors.Add(new DisableReflectionVisitor());
}

/// <summary>
/// Create a new interpreter with the settings copied from another interpreter
/// </summary>
internal Interpreter(ParserSettings settings)
{
_settings = settings;
}
#endregion

#region Properties
Expand Down Expand Up @@ -379,9 +392,20 @@ public Expression<TDelegate> ParseAsExpression<TDelegate>(string expressionText,
return lambda.LambdaExpression<TDelegate>();
}

internal LambdaExpression ParseAsExpression(Type delegateType, string expressionText, params string[] parametersNames)
{
var lambda = ParseAs(delegateType, expressionText, parametersNames);
return lambda.LambdaExpression(delegateType);
}

public Lambda ParseAs<TDelegate>(string expressionText, params string[] parametersNames)
{
var delegateInfo = ReflectionExtensions.GetDelegateInfo(typeof(TDelegate), 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);
}
Expand Down
4 changes: 4 additions & 0 deletions src/DynamicExpresso.Core/InterpreterOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ public enum InterpreterOptions
/// </summary>
LateBindObject = 16,
/// <summary>
/// Enable parsing of lambda expressions. Disabled by default, because it has a slight performance cost.
/// </summary>
LambdaExpressions = 32,
/// <summary>
/// Load all default configurations: PrimitiveTypes + SystemKeywords + CommonTypes
/// </summary>
Default = PrimitiveTypes | SystemKeywords | CommonTypes,
Expand Down
19 changes: 15 additions & 4 deletions src/DynamicExpresso.Core/Lambda.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.ExceptionServices;
using DynamicExpresso.Exceptions;

namespace DynamicExpresso
{
Expand Down Expand Up @@ -72,9 +71,9 @@ public object Invoke(params Parameter[] parameters)
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)
select actualParameter.Value)
from actualParameter in parameters
where usedParameter.Name.Equals(actualParameter.Name, _parserArguments.Settings.KeyComparison)
select actualParameter.Value)
.ToArray();

return InvokeWithUsedParameters(args);
Expand Down Expand Up @@ -155,5 +154,17 @@ 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());
}
}
}
Loading