Coder Social home page Coder Social logo

dynamicexpresso / dynamicexpresso Goto Github PK

View Code? Open in Web Editor NEW
1.8K 95.0 360.0 1.38 MB

C# expressions interpreter

Home Page: http://dynamic-expresso.azurewebsites.net/

License: MIT License

C# 100.00%
expression-evaluator c-sharp lambda-expressions

dynamicexpresso's Introduction

Dynamic Expresso

NuGet version .NET CI

Supported platforms: .NET Core 3.1, .NET Core 5.0 and above, .NET 4.6.2

Dynamic Expresso is an interpreter for simple C# statements written in .NET Standard 2.0. Dynamic Expresso embeds its own parsing logic, really interprets C# statements by converting it to .NET lambda expressions or delegates.

Using Dynamic Expresso developers can create scriptable applications, execute .NET code without compilation or create dynamic linq statements.

Statements are written using a subset of C# language specifications. Global variables or parameters can be injected and used inside expressions. It doesn't generate assembly but it creates an expression tree on the fly.

dynamic expresso workflow

For example you can evaluate math expressions:

var interpreter = new Interpreter();
var result = interpreter.Eval("8 / 2 + 2");

or parse an expression with variables or parameters and invoke it multiple times:

var interpreter = new Interpreter().SetVariable("service", new ServiceExample());
string expression = "x > 4 ? service.OneMethod() : service.AnotherMethod()";
Lambda parsedExpression = interpreter.Parse(expression, new Parameter("x", typeof(int)));
var result = parsedExpression.Invoke(5);

or generate delegates and lambda expressions for LINQ queries:

var prices = new [] { 5, 8, 6, 2 };
var whereFunction = new Interpreter().ParseAsDelegate<Func<int, bool>>("arg > 5");
var count = prices.Where(whereFunction).Count();

Live demo

Dynamic Expresso live demo: http://dynamic-expresso.azurewebsites.net/

Quick start

Dynamic Expresso is available on [NuGet]. You can install the package using:

PM> Install-Package DynamicExpresso.Core

Source code and symbols (.pdb files) for debugging are available on [Symbol Source].

Features

  • Expressions can be written using a subset of C# syntax (see Syntax section for more information)
  • Support for variables and parameters
  • Can generate delegates or lambda expression
  • Full suite of unit tests
  • 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
  • Easy to use and deploy, it is all contained in a single assembly without other external dependencies
  • Written in .NET Standard 2.0
    • Build available for .NET 4.6.1 and .NET Core 2.0
  • Open source (MIT license)

Return value

You can parse and execute void expression (without a return value) or you can return any valid .NET type. When parsing an expression you can specify the expected expression return type. For example you can write:

var target = new Interpreter();
double result = target.Eval<double>("Math.Pow(x, y) + 5",
				    new Parameter("x", typeof(double), 10),
				    new Parameter("y", typeof(double), 2));

The built-in parser can also understand the return type of any given expression so you can check if the expression returns what you expect.

Variables

Variables can be used inside expressions with Interpreter.SetVariable method:

var target = new Interpreter().SetVariable("myVar", 23);

Assert.AreEqual(23, target.Eval("myVar"));

Variables can be primitive types or custom complex types (classes, structures, delegates, arrays, collections, ...).

Custom functions can be passed with delegate variables using Interpreter.SetFunction method:

Func<double, double, double> pow = (x, y) => Math.Pow(x, y);
var target = new Interpreter().SetFunction("pow", pow);

Assert.AreEqual(9.0, target.Eval("pow(3, 2)"));

Custom Expression can be passed by using Interpreter.SetExpression method.

Parameters

Parsed expressions can accept one or more parameters:

var interpreter = new Interpreter();

var parameters = new[] {
	new Parameter("x", 23),
	new Parameter("y", 7)
};

Assert.AreEqual(30, interpreter.Eval("x + y", parameters));

Parameters can be primitive types or custom types. You can parse an expression once and invoke it multiple times with different parameter values:

var target = new Interpreter();

var parameters = new[] {
	new Parameter("x", typeof(int)),
	new Parameter("y", typeof(int))
};

var myFunc = target.Parse("x + y", parameters);

Assert.AreEqual(30, myFunc.Invoke(23, 7));
Assert.AreEqual(30, myFunc.Invoke(32, -2));

Special identifiers

Either a variable or a parameter with name this can be referenced implicitly.

class Customer { public string Name { get; set; } }

var target = new Interpreter();

// 'this' is treated as a special identifier and can be accessed implicitly 
target.SetVariable("this", new Customer { Name = "John" });

// explicit context reference via 'this' variable
Assert.AreEqual("John", target.Eval("this.Name"));

// 'this' variable is referenced implicitly
Assert.AreEqual("John", target.Eval("Name"));

Built-in types and custom types

Currently predefined types available are:

Object object 
Boolean bool 
Char char
String string
SByte Byte byte
Int16 UInt16 Int32 int UInt32 Int64 long UInt64 
Single Double double Decimal decimal 
DateTime TimeSpan
Guid
Math Convert

You can reference any other custom .NET type by using Interpreter.Reference method:

var target = new Interpreter().Reference(typeof(Uri));

Assert.AreEqual(typeof(Uri), target.Eval("typeof(Uri)"));
Assert.AreEqual(Uri.UriSchemeHttp, target.Eval("Uri.UriSchemeHttp"));

Generate dynamic delegates

You can use the Interpreter.ParseAsDelegate<TDelegate> method to directly parse an expression into a .NET delegate type that can be normally invoked. In the example below I generate a Func<Customer, bool> delegate that can be used in a LINQ where expression.

class Customer
{
	public string Name { get; set; }
	public int Age { get; set; }
	public char Gender { get; set; }
}

[Test]
public void Linq_Where()
{
	var customers = new List<Customer> {
		new Customer() { Name = "David", Age = 31, Gender = 'M' },
		new Customer() { Name = "Mary", Age = 29, Gender = 'F' },
		new Customer() { Name = "Jack", Age = 2, Gender = 'M' },
		new Customer() { Name = "Marta", Age = 1, Gender = 'F' },
		new Customer() { Name = "Moses", Age = 120, Gender = 'M' },
	};

	string whereExpression = "customer.Age > 18 && customer.Gender == 'F'";

	var interpreter = new Interpreter();
	Func<Customer, bool> dynamicWhere = interpreter.ParseAsDelegate<Func<Customer, bool>>(whereExpression, "customer");

	Assert.AreEqual(1, customers.Where(dynamicWhere).Count());
}

This is the preferred way to parse an expression that you known at compile time what parameters can accept and what value must return.

Generate lambda expressions

You can use the Interpreter.ParseAsExpression<TDelegate> method to directly parse an expression into a .NET lambda expression (Expression<TDelegate>).

In the example below I generate a Expression<Func<Customer, bool>> expression that can be used in a Queryable LINQ where expression or in any other place where an expression is required. Like Entity Framework or other similar libraries.

class Customer
{
	public string Name { get; set; }
	public int Age { get; set; }
	public char Gender { get; set; }
}

[Test]
public void Linq_Queryable_Expression_Where()
{
	IQueryable<Customer> customers = (new List<Customer> {
		new Customer() { Name = "David", Age = 31, Gender = 'M' },
		new Customer() { Name = "Mary", Age = 29, Gender = 'F' },
		new Customer() { Name = "Jack", Age = 2, Gender = 'M' },
		new Customer() { Name = "Marta", Age = 1, Gender = 'F' },
		new Customer() { Name = "Moses", Age = 120, Gender = 'M' },
	}).AsQueryable();

	string whereExpression = "customer.Age > 18 && customer.Gender == 'F'";

	var interpreter = new Interpreter();
	Expression<Func<Customer, bool>> expression = interpreter.ParseAsExpression<Func<Customer, bool>>(whereExpression, "customer");

	Assert.AreEqual(1, customers.Where(expression).Count());
}

Syntax and operators

Statements can be written using a subset of the C# syntax. Here you can find a list of the supported expressions:

Operators

Supported operators:

CategoryOperators
Primaryx.y f(x) a[x] new typeof
Unary+ - ! (T)x
Multiplicative* / %
Additive+ -
Relational and type testing< > <= >= is as
Equality== !=
Logical AND&
Logical OR|
Logical XOR^
Conditional AND&&
Conditional OR||
Conditional?:
Assignment=
Null coalescing??

Operators precedence is respected following C# rules (Operator precedence and associativity).

Some operators, like the assignment operator, can be disabled for security reason.

Literals

CategoryOperators
Constantstrue false null
Real literal suffixesd f m
Integer literal suffixesu l ul lu
String/char"" ''

The following character escape sequences are supported inside string or char literals:

  • \' - single quote, needed for character literals
  • \" - double quote, needed for string literals
  • \\ - backslash
  • \0 - Unicode character 0
  • \a - Alert (character 7)
  • \b - Backspace (character 8)
  • \f - Form feed (character 12)
  • \n - New line (character 10)
  • \r - Carriage return (character 13)
  • \t - Horizontal tab (character 9)
  • \v - Vertical quote (character 11)

Type's members invocation

Any standard .NET method, field, property or constructor can be invoked.

var service = new MyTestService();
var context = new MyTestContext();

var target = new Interpreter()
  .SetVariable("x", service)
  .SetVariable("this", context);

Assert.AreEqual(service.HelloWorld(), target.Eval("x.HelloWorld()"));
Assert.AreEqual(service.AProperty, target.Eval("x.AProperty"));
Assert.AreEqual(service.AField, target.Eval("x.AField"));

// implicit context reference
Assert.AreEqual(context.GetContextId(), target.Eval("GetContextId()"));
Assert.AreEqual(context.ContextName, target.Eval("ContextName"));
Assert.AreEqual(context.ContextField, target.Eval("ContextField"));
var target = new Interpreter();
Assert.AreEqual(new DateTime(2015, 1, 24), target.Eval("new DateTime(2015, 1, 24)"));

Dynamic Expresso also supports:

  • Extension methods
var x = new int[] { 10, 30, 4 };
var target = new Interpreter()
	.Reference(typeof(System.Linq.Enumerable))
	.SetVariable("x", x);
Assert.AreEqual(x.Count(), target.Eval("x.Count()"));
  • Indexer methods (like array[0])
  • 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:

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:

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:

var target = new Interpreter(InterpreterOptions.DefaultCaseInsensitive);

double x = 2;
var parameters = new[] {
	new Parameter("x", x.GetType(), x)
};

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. Because if you parse an expression without the right parameter an exception is throwed.

In these cases you can use Interpreter.DetectIdentifiers method to obtain a list of used identifiers, both known and unknown.

var target = new Interpreter();

var detectedIdentifiers = target.DetectIdentifiers("x + y");

CollectionAssert.AreEqual(new[] { "x", "y" }, 
			  detectedIdentifiers.UnknownIdentifiers.ToArray());

Default number type

In C #, numbers are usually interpreted as integers or doubles if they have decimal places.

In some cases it may be useful to be able to configure the default type of numbers if no particular suffix is โ€‹โ€‹specified: for example in financial calculations, where usually numbers are interpreted as decimal type.

In these cases you can set the default number type using Interpreter.SetDefaultNumberType method.

var target = new Interpreter();

target.SetDefaultNumberType(DefaultNumberType.Decimal);

Assert.IsInstanceOf(typeof(System.Decimal), target.Eval("45"));
Assert.AreEqual(10M/3M, target.Eval("10/3")); // 3.33333333333 instead of 3

Limitations

Not every C# syntaxes are supported. Here some examples of NOT supported features:

  • Multiline expressions
  • for/foreach/while/do operators
  • Array/list/dictionary initialization
  • Explicit generic invocation (like method<type>(arg))
  • Lambda/delegate declaration (delegate and lamda are only supported as variables or parameters or as a return type of the expression)
  • Array/list/dictionary element assignment (set indexer operator)
  • Other operations on dynamic objects (only property, method invocation and index now are supported)

Exceptions

If there is an error during the parsing always an exception of type ParseException is throwed. ParseException has several specialization classes based on the type of error (UnknownIdentifierException, NoApplicableMethodException. ...).

Performance and multithreading

The Interpreter class can be used by multiple threads but without modify it. In essence only get properties, Parse and Eval methods are thread safe. Other methods (SetVariable, Reference, ...) must be called in an initialization phase. Lambda and Parameter classes are completely thread safe.

If you need to run the same expression multiple times with different parameters I suggest to parse it one time and then invoke the parsed expression multiple times.

Security

If you allow an end user to write expression you must consider some security implications.

Parsed expressions can access only the .NET types that you have referenced using the Interpreter.Reference method or types that you pass as a variable or parameter. You must pay attention of what types you expose. In any case generated delegates are executed as any other delegate and standard security .NET rules can be applied (for more info see Security in the .NET Framework).

If expressions test can be written directly by users you must ensure that only certain features are available. Here some guidelines:

For example you can disable assignment operators, to ensure that the user cannot change some values that you don't expect. By default assignment operators are enables, by you can disable it using:

var target = new Interpreter().EnableAssignment(AssignmentOperators.None);

From version 1.3 to prevent malicious users to call unexpected types or assemblies within an expression, some reflection methods are blocked. For example you cannot write:

var target = new Interpreter();
target.Eval("typeof(double).GetMethods()");
// or
target.Eval("typeof(double).Assembly");

The only exception to this rule is the Type.Name property that is permitted for debugging reasons. To enable standard reflection features you can use Interpreter.EnableReflection method, like:

var target = new Interpreter().EnableReflection();

Usage scenarios

Here are some possible usage scenarios of Dynamic Expresso:

  • Programmable applications
  • Allow the user to inject customizable rules and logic without recompiling
  • Evaluate dynamic functions or commands
  • LINQ dynamic query

Future roadmap

See github open issues and milestones.

Help and support

If you need help you can try one of the following:

Maintainers

Currently Dynamic Expresso is maintained by @davideicardi and @metoule.

Credits

This project is based on two old works:

Thanks to all contributors!

Other resources or similar projects

Below you can find a list of some similar projects that I have evaluated or that can be interesting to study. For one reason or another none of these projects exactly fit my needs so I decided to write my own interpreter.

Continuous Integration

A continuous integration pipeline is configured using Github Actions, see .github/workflows folder.

Whenever a new Release is created, Nuget packages are published. For snapshot releases packages are published only to Github. For official releases packages are published to both GitHub and Nuget.

Compiling and run tests

To compile the solution you can run:

dotnet build DynamicExpresso.sln -c Release

To create nuget packages:

dotnet pack DynamicExpresso.sln -c Release

To run unit tests:

dotnet test DynamicExpresso.sln -c Release

or run unit tests for a specific project with a specific framework:

dotnet test DynamicExpresso.sln --no-restore -c Release --verbosity normal -f netcoreapp3.1

Add --logger:trx to generate test results for VSTS.

Release notes

See releases page.

dynamicexpresso's People

Contributors

alex141 avatar alexk47 avatar andreasmo avatar bcachet avatar bortolin avatar btnlq avatar btull89 avatar davideicardi avatar davidwynne avatar denbell5 avatar dhamburgler avatar dlahmad avatar halamah avatar holdenmai avatar israellot avatar jasonhumberstone avatar jay-jay-d avatar justinrchou avatar kendarorg avatar metoule avatar moh-hassan avatar nemo048 avatar nike61 avatar pentiva avatar repko-artem avatar rotsin avatar rwecho avatar tingwei628 avatar weltall avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dynamicexpresso's Issues

Nullable types support

It should be possible to write:

        var target = new Interpreter();

        var lambda = target.Parse("null", typeof(int?));
        Assert.AreEqual(typeof(int?), lambda.ReturnType);
        Assert.IsNull(lambda.Invoke());

        lambda = target.Parse("4651", typeof(int?));
        Assert.AreEqual(typeof(int?), lambda.ReturnType);
        Assert.AreEqual(4651, lambda.Invoke());

Indexed property setter not working

Hi, can anything be done about the indexed property setter not working. In both examples below the following exception occurs:

An unhandled exception of type 'System.ArgumentException' occurred in System.Core.dll.
Additional information: Expression must be writeable

Code:

`var interpreter = new Interpreter();

        var arr = new[] { 1 };
        arr[0] = 2;
        interpreter.SetVariable("arr", arr);
        interpreter.Eval("arr[0] = 3");

        var list = new List<int>(new[] { 1 });
        list[0] = 2;
        interpreter.SetVariable("list", list);
        interpreter.Eval("list[0] = 3");`

Thanks.

Discover Variables

Provide a a way to discover the list of required variables in an expression string.

Localizable Error Messages

It would be nice if it was possible to replace error messages. My system is used in several languages and i need to show all error messages in user's language.

The simplest solution is to change ErrorMessages from internal to public and remove const modifier from error messages.

But it would be nice if we have a mechanism to inject error messages or a Abstract Factory that creates ErrorMessages based on some conditions.

Possible to transform the parsed expression?

Great project. I'm using it to parse queries in a REST api I'm developing. Currently, I'm using ParseAsExpression to just pass through a user's query to EntityFramework LINQ query.

What I want to do is have a separate model for my API than I do for EF. So instead of returning EF models from my API I want to have DTO's (Data Transfer Objects) that provide a user-friendly view of my data.

What I want is to parse an expression using the DTO classes but then somehow turn that into an expression that uses the EF classes. Is this even feasible?

Adding some more operations

The compiler works all fine. But i would like to know if the power operator, increment and decrement operators are not required ? If they are required I would like to contribute the same. Please let me know asap

Using DynamicExpresso with Xamarin

Hi, I tried to install the package in a Xamarin project but got an error. Any thoughts?

Attempting to gather dependency information for package 'DynamicExpresso.Core.1.3.3.5' with respect to project 'Newton.iOS', targeting 'Xamarin.iOS,Version=v1.0'
GET https://www.nuget.org/api/v2/Packages(Id='DynamicExpresso.Core',Version='1.3.3.5')
OK https://www.nuget.org/api/v2/Packages(Id='DynamicExpresso.Core',Version='1.3.3.5') 220ms
Total number of results gathered : 98
Gathering dependency information took 239.7 ms
Summary of time taken to gather dependencies per source :
https://www.nuget.org/api/v2/ - 221.25 ms
/Users/lynas/Downloads - 3.52 ms
Attempting to resolve dependencies for package 'DynamicExpresso.Core.1.3.3.5' with DependencyBehavior 'Lowest'
Resolving dependency information took 0 ms
Resolving actions to install package 'DynamicExpresso.Core.1.3.3.5'
Resolved actions to install package 'DynamicExpresso.Core.1.3.3.5'
Retrieving package 'DynamicExpresso.Core 1.3.3.5' from 'Official NuGet Gallery'.
GET https://www.nuget.org/api/v2/package/DynamicExpresso.Core/1.3.3.5
OK https://www.nuget.org/api/v2/package/DynamicExpresso.Core/1.3.3.5 674ms
Acquiring lock for the installation of DynamicExpresso.Core 1.3.3.5
Acquired lock for the installation of DynamicExpresso.Core 1.3.3.5
Installing DynamicExpresso.Core 1.3.3.5.
Completed installation of DynamicExpresso.Core 1.3.3.5
For adding package 'DynamicExpresso.Core.1.3.3.5' to project 'App.iOS' that targets 'xamarinios10'.
Install failed. Rolling back...
Package 'DynamicExpresso.Core.1.3.3.5' does not exist in project 'App.iOS'
Package 'DynamicExpresso.Core.1.3.3.5' does not exist in folder '/Users/me/Projects/app.git/packages'
Executing nuget actions took 971.49 ms
Could not install package 'DynamicExpresso.Core 1.3.3.5'. You are trying to install this package into a project that targets 'Xamarin.iOS,Version=v1.0', but the package does not contain any assembly references or content files that are compatible with that framework. For more information, contact the package author.

Add support of net3.5

Hello David!

It would be nice to make DynamicExpresso support of net 3.5. It is possible or there is some fundamental problems?

Custom binary operators not supported

A ParserException occurs when attempting to evaluate an expression that uses custom binary operators on objects. Add the following code to OperatorsTest.cs to reproduce.

[TestMethod]
public void Can_use_overloaded_binary_operators()
{
    var target = new Interpreter();

    var x = new TypeWithOverloadedBinaryOperators(3);
    target.SetVariable("x", x);

    string y = "5";
    Assert.IsFalse(x == y);
    Assert.IsFalse(target.Eval<bool>("x == y", new Parameter("y", y)));

    y = "3";
    Assert.IsTrue(x == y);
    Assert.IsTrue(target.Eval<bool>("x == y", new Parameter("y", y)));

    Assert.IsFalse(target.Eval<bool>("x == \"4\""));
    Assert.IsTrue(target.Eval<bool>("x == \"3\""));
}

struct TypeWithOverloadedBinaryOperators
{
    private int _value;

    public TypeWithOverloadedBinaryOperators(int value)
    {
        _value = value;
    }

    public static bool operator ==(TypeWithOverloadedBinaryOperators instance, string value)
    {
        return instance._value.ToString().Equals(value);
    }

    public static bool operator !=(TypeWithOverloadedBinaryOperators instance, string value)
    {
        return !instance._value.ToString().Equals(value);
    }

    public override bool Equals(object obj)
    {
        if (obj == null)
            return false;
        if (obj is TypeWithOverloadedBinaryOperators)
        {
            return this._value.Equals(((TypeWithOverloadedBinaryOperators)obj)._value);
        }
        return base.Equals(obj);
    }

    public override int GetHashCode()
    {
        return _value.GetHashCode();
    }
}

causes the following:

Operator '==' incompatible with operand types 'TypeWithOverloadedBinaryOperators' and 'String' (at index 2).
       at DynamicExpresso.ExpressionParser.CheckAndPromoteOperands(Type signatures, String opName, Expression& left, Expression& right, Int32 errorPos)

Async await support in Functions

Hello. I've succesfully integrated DynamicExpresso (.NET Core) in my project, but there is a question with the Functions, that returns Task. Is there support for this kind of Expressions?

Example:

var target = new Interpreter();
Func<int, Task<bool>> func = async (x) => await _someHttpService.Get(x);
target.SetFunction("foo", func);
var parsedExpression = target.Parse("true && foo(1)", parameters));

Currentry I receive Exception

The binary operator AndAlso is not defined for the types 'System.Threading.Tasks.Task`1[System.Boolean]' and 'System.Threading.Tasks.Task`1[System.Boolean]'.

Parse select expression for grouping result

Hello.

Is it possible to do smth like this? (Now it cannot understand new {)

using System;
using System.Collections.Generic;
using System.Linq;
using DynamicExpresso;

namespace Temp
{
	class Program
	{
		public static void Main()
		{
			IQueryable<Expenditure> exps = (new List<Expenditure> {
				new Expenditure { Group = "Gr1", Amount = 31, Class = "Class1" },
				new Expenditure { Group = "Gr2", Amount = 29, Class = "Class2" },
				new Expenditure { Group = "Gr2", Amount = 2, Class = "Class2" },
				new Expenditure { Group = "Gr2", Amount = 1, Class = "Class3" },
				new Expenditure { Group = "Gr2", Amount = 120, Class = "Class2" },

			}).AsQueryable();

			var interpreter = new Interpreter();
			var selectExpression = interpreter.ParseAsExpression<Func<dynamic, dynamic>>("new { x.Key.Group, x.Key.Class, Sum = x.Sum(y => y.Amount)", "x");

			var groups = exps.GroupBy(x => new { x.Group, x.Class }).Select(selectExpression);

			foreach (var gr in groups)
			{
				Console.WriteLine(gr);
			}

			Console.ReadLine();
		}

		public class Expenditure
		{
			public string Group { get; set; }
			public string Class { get; set; }
			public int Amount { get; set; }
		}
	}
}

Prevent unexpected access to other types using reflection for security

We should disable code that use reflection in a malicious way. For example if I reference type X inside the interpreter I should not be allowed to write an expression like:

typeof(X).Assembly

Because potentially in this way you can access and invoke any type defined in that assembly.
The same can be for instances:

xInstance.GetType().Assembly

Think about other similar security problems...

Expression return type is automatically converted if needed

It should be possible to force the return type:

        var target = new Interpreter();
        var expressionType = typeof(int);

        var lambda = target.Parse("213.46", expressionType);

        Assert.AreEqual(expressionType, lambda.ReturnType);
        Assert.AreEqual((int)213.46, lambda.Invoke());

Ability to call a method extension

It should be possible to write:

        var x = new int[] { 10, 30, 4 };

        var target = new Interpreter()
                                .Reference(typeof(System.Linq.Enumerable))
                                .SetVariable("x", x);

        Assert.AreEqual(x.Count(), target.Eval("x.Count()"));

Calling method on integer literals (like: `5.ToString()` )

Hi Davide,

It is not much of an issue at all, but I found a minor defect in number parsing. It seems that number parsing is a little too eager.

This will not work:
5.ToString()

But 5 .ToString() or (5).ToString() or 5.0.ToString() will.

As I said earlier, a very minor problem and easy to work around. Would also probably not be encountered very often.

DynamicExpresso.ParseException: Digit expected (at index 2).
at DynamicExpresso.ExpressionParser.ValidateDigit()
at DynamicExpresso.ExpressionParser.NextToken()
at DynamicExpresso.ExpressionParser..ctor(String expression, Type expressionType, ParameterExpression[] parameters, ParserSettings settings)
at DynamicExpresso.Interpreter.ParseExpression(String expressionText, Type expressionType, ParameterExpression[] parameters)
at DynamicExpresso.Interpreter.Parse(String expressionText, Type expressionType, Parameter[] parameters)
at DynamicExpresso.Interpreter.Parse(String expressionText, Parameter[] parameters)
at DynamicExpressoDemo.MainViewModel.Evaluate() in c:\Users\neal.borelli\Documents\Visual Studio 2013\Projects\DynamicExpressoDemo\DynamicExpressoDemo\MainViewModel.cs:line 128

Operator ?: seams to evaluate both sides of expression.

Expression
xx == null ? 0 : xx.SomeProperty
failed with exception "No property or field SomeProperty exists in type Object (at index 22)."
The problem is that the expression xx is null.
If I change the expression to "xx == null ? 1 : 2" it is evaluated and the result is 1.

'Unused Parameters' make lambda compilation fail

Hi,

Just discovered this project and I was playing around with it. I found a minor issue and figured I'd report it.
Basically I have a set of scripts that all run on an instance that I pass as parameter. On startup, I create an interpreter and try to simulate context by adding all the properties of said parameter.

static class Scripting {
        static readonly Interpreter _interpreter;
        static readonly Parameter _parameter = new Parameter("value", typeof(GlobalContext));

        static Scripting()
        {
            _interpreter = new Interpreter(InterpreterOptions.DefaultCaseInsensitive);
            _interpreter.Reference(typeof(GlobalContext));

            var knownTypes = new HashSet<Type>(_interpreter.ReferencedTypes.Select(x => x.Type));

            foreach (var property in typeof(GlobalContext).GetProperties(BindingFlags.Instance | BindingFlags.Public)
                                                       .Where(x => x.CanRead))
            {
                var propExpr = Expression.Property(_parameter.Expression, property);
                _interpreter.SetExpression(property.Name, propExpr);

                if (knownTypes.Add(property.PropertyType))
                    _interpreter.Reference(property.PropertyType);
            }
        }
}

Eventually, I will generate a compiled lambda.

var lambda = _interpreter.Parse(condition, typeof(bool), _parameter);
var @delegate = lambda.Compile<Func<GlobalContext, bool>>();

The Parse method will fail, because of the following line in the constructor of Lambda.cs

var lambdaExpression = Expression.Lambda(_expression, _parserArguments.UsedParameters.Select(p => p.Expression).ToArray());

UsedParameters is empty, because I haven't actually used the parameter, I've used properties of said parameter. (meaning I did use it, but the parser didn't notice)

After reading the note that it's not really needed to compile the lambda there if you're going to compile a typed one later, I changed the code there to a Lazy<Delegate> instead, which was enough to make things work out for me. However, when compiling a typed lambda, I noticed you used the following code:

public Expression<TDelegate> LambdaExpression<TDelegate>()
{
	return Expression.Lambda<TDelegate>(_expression, DeclaredParameters.Select(p => p.Expression).ToArray());
}

Here DeclaredParameters is used instead of UsedParameters.
Shouldn't this also be the case for the lambda compilation in the ctor of Lambda.cs ?

Formula with SI Units

Hello,

First of all, a huge thanks for this library. It's awesome.

I need to parse formula of this kind: "3mm + 1cm"
Values have Physical Dimensions/Units.

I already have libraries that perform +,-,*,/ operations on objects with Dimension support (https://github.com/anjdreas/UnitsNet, https://github.com/objorke/QuantityTypes).
These libraries are also able to create Dimensioned object from string.

Is there already a way to do this or to add this support ?

I was thinking of:

  • Replacing Physical Units contained in the formula by Variables that I will pass to the Interpreter.
  • Adding a ParserArguments.Settings to indicate that we want to parse Numbers (IntegerLiteral/RealLiteral) as value with Physical Dimension representation (PhysicalValueLiteral)
    In this case, IntegerLiteral & RealLiteral will be parsed using ParsePhysicalValueLiteral instead of ParseIntegerLiteral/ParseRealLiteral.

What is your opinion on this usage ?

Invoke object's methods on interface types

It should be possible to invoke object's methods (like ToString, GetType, GetHashCode) also on interface variable or parameters.

For example:

    [TestMethod]
    public void GetType_Method_on_a_custom_interface_type()
    {
        MyTestInterface service = new MyTestInterfaceImp();
        var target = new Interpreter()
                                        .SetVariable("service", service, typeof(MyTestInterface));

        Assert.AreEqual(typeof(MyTestInterfaceImp), target.Eval("service.GetType()"));
    } 

.ToString should be called automatically for string concatenation

In C# we can do:

string text = "abc" + 123; which results in a single string "abc123".

However the following does not work:

	public class Unit
	{
		public string Name = "Test";
		public int Age = 123;
	}

DynamicExpresso.Interpreter interpreter = new DynamicExpresso.Interpreter(
			DynamicExpresso.InterpreterOptions.CommonTypes |
			DynamicExpresso.InterpreterOptions.SystemKeywords |
			DynamicExpresso.InterpreterOptions.PrimitiveTypes);

	interpreter.Reference(typeof(Func<>));

	string code = "u.Name + u.Age";

	var function = interpreter.ParseAsDelegate<Func<Unit, string>>(code, "u");

	var text = function.Invoke(new Unit());

If code is changed to u.Name + u.Age.ToString() everything works perfectly, so I guess it has to do with casting u.Age to an object before passing it to string.Concat?

Roslyn ~ DynamicExpresso

Question...

Hi, I started looking at your solution here, but following your comment in your FAQ stating that if possible one should use Rosyln, one should.. I am doing just that.

However, one thing I have found dead easy using DynamicExpresso is that I defined 'this' using SetVariable("this", this);

and I am easily able to use 'this.' in my 'Formula'

I am struggling to do this in Roslyn, any pointers?

Thanks.

Is their any potential risk?

This is an excellent piece of work David. Impressed by the quality of this code. Do you think using Eval is dangerous in terms of security of the system/codebase/data? Can I send a piece of code that would may be delete the database/file/add a virus or give any flexibility to the user to pass some malicious code?

Really Awesome!

I really wish Github had a "kudos" feature. :) Just wanted to send you some appreciation for this library! I had some terrible evaluation code that was based on ASP.NET databinding from back in the day... YUCK. You just saved me a ton of work. :)

I will be using this in my framework, FYI! Thanks again. Please feel free to close this issue once you see it.

Eval typed expression

It should be possible to write:

        double result = target.Eval<double>("Math.Pow(x, y) + 5",
                                                new Parameter("x", typeof(double), 10),
                                                new Parameter("y", typeof(double), 2));

default keyword missing

It seems there's currently no support for default(int). It reports unknown identifier 'default'.

Can we have support for default keyword added?

API hangs on bad formula

While this is not a proper formula, when evaluating the following expression "2()", the API hangs rather than throwing an error.

Feature Request: Ability to do assignment with DynamicExpresso

First of all, thank you for writing DynamicExpresso. It worked as claimed.

I am trying to do assignment. Right now I did the assignment using function call, which is less than ideal.

public class ObjectA { public object Value; }
public class ObjectB { public object Value; }

public void Test1()
{
    var interpreter = new Interpreter();
    var objA = new ObjectA();
    var objB = new ObjectB();
    var data = new Dictionary<string, dynamic>() {
        {"a",objA},
        {"b",objB},
    };
    var paramsList2 = pData.Select(r => new Parameter(r.Key, r.Value)).ToArray();
    interpreter.Eval("a.Value = b.Value", paramsList2);

    Debug.WriteLine(objA.Value);
    Debug.WriteLine(objB.Value);
}

public void Test2()
{
    var interpreter = new Interpreter();
    var objA = new ObjectA();
    var objB = new ObjectB();
    var data = new Dictionary<string, dynamic>() {
        {"a",objA},
        {"b",objB},
    };
    //suppose I set all the SetFunction() here.
    ....
    //Attempting to make Func and Action class working in string expression
    interpreter.Reference(typeof(Func<bool>));
    interpreter.Reference(typeof(Action));
    var paramsList2 = pData.Select(r => new Parameter(r.Key, r.Value)).ToArray();

    //The following won't work because => operator not supported
    interpreter.Eval("new Func<bool>(() => { a.Value = b.Value; return true; })", paramsList2);
    //The following won't work because it returns nothing, and => operator not supported
    interpreter.Eval("new Action(() => { a.Value = b.Value; })", paramsList2);
    //Works, but limited my possibility and flexibility
    interpreter.Eval("SetVal(a,b)", paramsList2);

    Debug.WriteLine(objA.Value);
    Debug.WriteLine(objB.Value);
}
public bool SetVal(dynamic pa, dynamic pb)
{
    pa.Value = pb.Value;
    return true;
}

Thank you!

Mono bug calling methods

When attempting to use code which calls int.Parse(var) an exception is thrown in mono. This code works fine in .NET.

The error is as follows:
DynamicExpresso.NoApplicableMethodException: No applicable method 'Parse' exists in type 'Int32' (at index 4)

Parsing of expression with cast fails

Dynamic Expresso Version 0.11.0.0

The following expression parses correctly:

(double) 1

However, this expression does not parse correctly:

((double) 1)

This is a problem if we want to cast an object and then access a member of that object.

For example accessing a property that is IDictionary<string, object>:

((DateTime)Values["key"]).Year

Will throw an exception.

Here is the parsing error I get:

DynamicExpresso.ParseException: ')' or operator expected (at index 12).
at DynamicExpresso.ExpressionParser.ValidateToken(TokenId t, String errorMessage)
at DynamicExpresso.ExpressionParser.ParseParenExpression()
at DynamicExpresso.ExpressionParser.ParsePrimaryStart()
at DynamicExpresso.ExpressionParser.ParsePrimary()
at DynamicExpresso.ExpressionParser.ParseUnary()
at DynamicExpresso.ExpressionParser.ParseMultiplicative()
at DynamicExpresso.ExpressionParser.ParseAdditive()
at DynamicExpresso.ExpressionParser.ParseTypeTesting()
at DynamicExpresso.ExpressionParser.ParseComparison()
at DynamicExpresso.ExpressionParser.ParseLogicalAnd()
at DynamicExpresso.ExpressionParser.ParseLogicalOr()
at DynamicExpresso.ExpressionParser.ParseConditional()
at DynamicExpresso.ExpressionParser.ParseExpressionSegment()
at DynamicExpresso.ExpressionParser.ParseExpressionSegment(Type returnType)
at DynamicExpresso.ExpressionParser.Parse()
at DynamicExpresso.Interpreter.ParseExpression(String expressionText, Type

Nested dynamic objects

Given a nested dynamic object

dynamic dyn = new ExpandoObject(); dyn.Sub = new ExpandoObject(); dyn.Sub.Foo = "bar";

calling

Eval("dyn.Sub.Foo")

will not recognize the Foo variable

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.