diff --git a/README.md b/README.md index 8c7577d2..31b82c54 100644 --- a/README.md +++ b/README.md @@ -13,49 +13,35 @@ Statements are written using a subset of C# language specifications. Global vari ![dynamic expresso workflow](https://raw.github.com/davideicardi/DynamicExpresso/master/docs/workflow.png "dynamic expresso workflow") - For example you can evaluate math expressions: - - var interpreter = new Interpreter(); - var result = interpreter.Eval("8 / 2 + 2"); - +```csharp +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); - +```csharp +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>("arg > 5"); - - var count = prices.Where(whereFunction).Count(); - - +```csharp +var prices = new [] { 5, 8, 6, 2 }; +var whereFunction = new Interpreter().ParseAsDelegate>("arg > 5"); +var count = prices.Where(whereFunction).Count(); +``` ## Live demo - Dynamic Expresso live demo: [http://dynamic-expresso.azurewebsites.net/](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 @@ -71,70 +57,63 @@ Source code and symbols (.pdb files) for debugging are available on [Symbol Sour - 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("Math.Pow(x, y) + 5", - new Parameter("x", typeof(double), 10), - new Parameter("y", typeof(double), 2)); - - +```csharp +var target = new Interpreter(); +double result = target.Eval("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: +```csharp +var target = new Interpreter().SetVariable("myVar", 23); - var target = new Interpreter() - .SetVariable("myVar", 23); - - Assert.AreEqual(23, target.Eval("myVar")); - +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: +```csharp +Func pow = (x, y) => Math.Pow(x, y); +var target = new Interpreter().SetFunction("pow", pow); - Func pow = (x, y) => Math.Pow(x, y); - var target = new Interpreter() - .SetFunction("pow", pow); - - Assert.AreEqual(9.0, target.Eval("pow(3, 2)")); - +Assert.AreEqual(9.0, target.Eval("pow(3, 2)")); +``` Custom [Expression](http://msdn.microsoft.com/en-us/library/system.linq.expressions.expression.aspx) can be passed by using `Interpreter.SetExpression` method. ### Parameters - Parsed expressions can accept one or more parameters: +```csharp +var interpreter = new Interpreter(); - var interpreter = new Interpreter(); - - var parameters = new[] { - new Parameter("x", 23), - new Parameter("y", 7) - }; - - Assert.AreEqual(30, interpreter.Eval("x + y", parameters)); +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: +```csharp +var target = new Interpreter(); - var target = new Interpreter(); - - var parameters = new[] { - new Parameter("x", typeof(int)), - new Parameter("y", typeof(int)) - }; +var parameters = new[] { + new Parameter("x", typeof(int)), + new Parameter("y", typeof(int)) +}; - var myFunc = target.Parse("x + y", parameters); +var myFunc = target.Parse("x + y", parameters); - Assert.AreEqual(30, myFunc.Invoke(23, 7)); - Assert.AreEqual(30, myFunc.Invoke(32, -2)); +Assert.AreEqual(30, myFunc.Invoke(23, 7)); +Assert.AreEqual(30, myFunc.Invoke(32, -2)); +``` ### Built-in types and custom types - Currently predefined types available are: Object object @@ -149,90 +128,81 @@ Currently predefined types available are: Math Convert You can reference any other custom .NET type by using `Interpreter.Reference` method: +```csharp +var target = new Interpreter().Reference(typeof(Uri)); - var target = new Interpreter() - .Reference(typeof(Uri)); - - Assert.AreEqual(typeof(Uri), target.Eval("typeof(Uri)")); - Assert.AreEqual(Uri.UriSchemeHttp, target.Eval("Uri.UriSchemeHttp")); - +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` method to directly parse an expression into a .NET delegate type that can be normally invoked. In the example below I generate a `Func` delegate that can be used in a LINQ where expression. +```csharp +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 { + 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'"; - 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 { - 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 dynamicWhere = interpreter.ParseAsDelegate>(whereExpression, "customer"); - - Assert.AreEqual(1, customers.Where(dynamicWhere).Count()); - } + var interpreter = new Interpreter(); + Func dynamicWhere = interpreter.ParseAsDelegate>(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` method to directly parse an expression into a .NET lambda expression (`Expression`). + +In the example below I generate a `Expression>` 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. +```csharp +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 customers = (new List { + 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'"; -You can use the `Interpreter.ParseAsExpression` - method to directly parse an expression into a .NET lambda expression (`Expression`). - -In the example below I generate a `Expression>` 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 customers = (new List { - 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> expression = interpreter.ParseAsExpression>(whereExpression, "customer"); - - Assert.AreEqual(1, customers.Where(expression).Count()); - } + var interpreter = new Interpreter(); + Expression> expression = interpreter.ParseAsExpression>(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 - @@ -277,9 +247,7 @@ Operators precedence is respected following [C# rules (Operator precedence and a Some operators, like the assignment operator, can be disabled for security reason. - ### Literals -
@@ -313,71 +281,65 @@ The following character escape sequences are supported inside string or char lit - `\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 x = new MyTestService(); - var target = new Interpreter().SetVariable("x", x); - - Assert.AreEqual(x.HelloWorld(), target.Eval("x.HelloWorld()")); - Assert.AreEqual(x.AProperty, target.Eval("x.AProperty")); - Assert.AreEqual(x.AField, target.Eval("x.AField")); - - var target = new Interpreter(); - Assert.AreEqual(new DateTime(2015, 1, 24), target.Eval("new DateTime(2015, 1, 24)")); - +```csharp +var x = new MyTestService(); +var target = new Interpreter().SetVariable("x", x); + +Assert.AreEqual(x.HelloWorld(), target.Eval("x.HelloWorld()")); +Assert.AreEqual(x.AProperty, target.Eval("x.AProperty")); +Assert.AreEqual(x.AField, target.Eval("x.AField")); +``` +```csharp +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]) +```csharp +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) - ### 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: +```csharp +var target = new Interpreter(InterpreterOptions.DefaultCaseInsensitive); - 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)); +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. +```csharp +var target = new Interpreter(); - var target = new Interpreter(); - - var detectedIdentifiers = target.DetectIdentifiers("x + y"); - - CollectionAssert.AreEqual( - new []{ "x", "y"}, - detectedIdentifiers.UnknownIdentifiers.ToArray()); +var detectedIdentifiers = target.DetectIdentifiers("x + y"); +CollectionAssert.AreEqual(new[] { "x", "y" }, + detectedIdentifiers.UnknownIdentifiers.ToArray()); +``` ## Limitations - Not every C# syntaxes are supported. Here some examples of NOT supported features: - Multiline expressions @@ -386,24 +348,18 @@ Not every C# syntaxes are supported. Here some examples of NOT supported feature - Explicit generic invocation (like `method(arg)`) - Lambda/delegate declaration (delegate and lamda are only supported as variables or parameters or as a return type of the expression) - ## 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. @@ -414,27 +370,24 @@ If expressions test can be written directly by users you must ensure that only c 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); - +```csharp +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") - +```csharp +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: +```csharp +var target = new Interpreter().EnableReflection(); +``` - var target = new Interpreter() - .EnableReflection(); - - ## Usage scenarios - Here are some possible usage scenarios of Dynamic Expresso: - Programmable applications @@ -442,13 +395,10 @@ Here are some possible usage scenarios of Dynamic Expresso: - Evaluate dynamic functions or commands - LINQ dynamic query - ## Future roadmap - See [github open issues and milestones](https://github.com/davideicardi/DynamicExpresso/issues). ## Help and support - If you need help you can try one of the following: - [FAQ](https://github.com/davideicardi/DynamicExpresso/wiki/FAQ) wiki page @@ -457,7 +407,6 @@ If you need help you can try one of the following: ## Credits - This project is based on two old works: - "Converting String expressions to Funcs with FunctionFactory by Matthew Abbott" (http://www.fidelitydesign.net/?p=333) - DynamicQuery - Dynamic LINQ - Visual Studio 2008 sample: @@ -466,13 +415,11 @@ This project is based on two old works: ## 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. - Roslyn Project - Scripting API - https://github.com/dotnet/roslyn/wiki/Scripting-API-Samples - This is the new Microsoft Official Compiler as a service library. I suggest to consider using Roslyin instead of DynamicExpresso whenever possible. - - Mono.CSharp - C# Compiler Service and Runtime Evaulator - http://docs.go-mono.com/index.aspx?link=N%3AMono.CSharp - NCalc - Mathematical Expressions Evaluator for .NET - http://ncalc.codeplex.com/ - David Wynne CSharpEval https://github.com/DavidWynne/CSharpEval @@ -486,17 +433,14 @@ For one reason or another none of these projects exactly fit my needs so I decid - paxScript.NET http://eco148-88394.innterhost.net/paxscriptnet/ ## Continuous build - There is a build service configured at [MyGet](https://www.myget.org/BuildSource/List/webplu). [![MyGet Build Status](https://www.myget.org/BuildSource/Badge/webplu?identifier=230c74d4-485f-49ff-8968-ffaeed70d9d8)](https://www.myget.org/) ## Release notes - See [wiki release notes page](https://github.com/davideicardi/DynamicExpresso/wiki/Release-notes). ## License - *[MIT License]* Copyright (c) 2015 Davide Icardi @@ -505,8 +449,6 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - [MIT License]: http://opensource.org/licenses/mit-license.php [NuGet]: https://nuget.org/packages/DynamicExpresso.Core [Symbol Source]: http://www.symbolsource.org