├── CODEOWNERS ├── .vscode ├── settings.json ├── tasks.json └── launch.json ├── docs └── workflow.png ├── .github ├── FUNDING.yml └── workflows │ ├── ci.yml │ └── publish.yml ├── sample └── DynamicExpressoWebShell │ ├── appsettings.json │ ├── wwwroot │ ├── Content │ │ ├── images │ │ │ ├── glyphicons-halflings.png │ │ │ └── glyphicons-halflings-white.png │ │ └── webshell.css │ └── Scripts │ │ └── webshell.js │ ├── appsettings.Development.json │ ├── Models │ └── ErrorViewModel.cs │ ├── Program.cs │ ├── Controllers │ ├── HomeController.cs │ └── InterpreterController.cs │ ├── Properties │ ├── launchSettings.json │ └── PublishProfiles │ │ └── dynamic-expresso - Web Deploy.pubxml │ ├── Views │ ├── Shared │ │ └── Error.cshtml │ └── Home │ │ └── Index.cshtml │ ├── Services │ ├── WebShell.cs │ └── CommandsHistory.cs │ ├── DynamicExpressoWebShell.csproj │ └── Startup.cs ├── src ├── DynamicExpresso.Core │ ├── Parsing │ │ ├── Token.cs │ │ ├── ParserConstants.cs │ │ ├── TokenId.cs │ │ ├── InterpreterExpression.cs │ │ ├── ParserSettings.cs │ │ └── ParseSignatures.cs │ ├── DetectorOptions.cs │ ├── DefaultNumberType.cs │ ├── AssignmentOperators.cs │ ├── Exceptions │ │ ├── DynamicExpressoException.cs │ │ ├── ReflectionNotAllowedException.cs │ │ ├── DuplicateParameterException.cs │ │ ├── UnknownIdentifierException.cs │ │ ├── AssignmentOperatorDisabledException.cs │ │ ├── NoApplicableMethodException.cs │ │ └── ParseException.cs │ ├── Reflection │ │ ├── MethodData.cs │ │ ├── IndexerData.cs │ │ ├── SimpleMethodSignature.cs │ │ ├── ReflectionExtensions.cs │ │ ├── MemberFinder.cs │ │ └── TypeUtils.cs │ ├── IdentifiersInfo.cs │ ├── Visitors │ │ └── DisableReflectionVisitor.cs │ ├── Resolution │ │ ├── ExpressionUtils.cs │ │ └── LateBinders.cs │ ├── InterpreterOptions.cs │ ├── ReferenceType.cs │ ├── Parameter.cs │ ├── DynamicExpresso.Core.csproj │ ├── LanguageConstants.cs │ ├── ParserArguments.cs │ ├── Identifier.cs │ ├── Detector.cs │ ├── Lambda.cs │ └── Resources │ │ └── ErrorMessages.de.resx └── GlobalAssemblyInfo.cs ├── .gitattributes ├── test └── DynamicExpresso.UnitTest │ ├── PerformanceTest.cs │ ├── CaseInsensitivePropertyTest.cs │ ├── DynamicExpresso.UnitTest.csproj │ ├── FunctionTest.cs │ ├── EnumerableTest.cs │ ├── CollectionHelperTests.cs │ ├── WorkingContextTest.cs │ ├── VisitorsTest.cs │ ├── ReferencedTypesPropertyTest.cs │ ├── IdentifiersTest.cs │ ├── GenerateDelegatesTest.cs │ ├── StaticTest.cs │ ├── ExpressionTypeTest.cs │ ├── ThreadingTest.cs │ ├── DefaultOperatorTest.cs │ ├── GenerateLambdaTest.cs │ ├── ExtensionsMethodsTest.cs │ ├── InvalidExpressionTest.cs │ ├── TypesTest.cs │ ├── OtherTests.cs │ ├── VariablesTest.cs │ └── NullableTest.cs ├── LICENSE ├── DynamicExpressoWebShell.sln ├── DynamicExpresso.sln └── .gitignore /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @davideicardi @metoule 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "Expresso" 4 | ] 5 | } -------------------------------------------------------------------------------- /docs/workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dynamicexpresso/DynamicExpresso/HEAD/docs/workflow.png -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [davideicardi, metoule] 4 | -------------------------------------------------------------------------------- /sample/DynamicExpressoWebShell/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/DynamicExpresso.Core/Parsing/Token.cs: -------------------------------------------------------------------------------- 1 | namespace DynamicExpresso.Parsing 2 | { 3 | internal struct Token 4 | { 5 | public TokenId id; 6 | public string text; 7 | public int pos; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /sample/DynamicExpressoWebShell/wwwroot/Content/images/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dynamicexpresso/DynamicExpresso/HEAD/sample/DynamicExpressoWebShell/wwwroot/Content/images/glyphicons-halflings.png -------------------------------------------------------------------------------- /sample/DynamicExpressoWebShell/wwwroot/Content/images/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dynamicexpresso/DynamicExpresso/HEAD/sample/DynamicExpressoWebShell/wwwroot/Content/images/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /sample/DynamicExpressoWebShell/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /sample/DynamicExpressoWebShell/Models/ErrorViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DynamicExpressoWebShell.Models 4 | { 5 | public class ErrorViewModel 6 | { 7 | public string RequestId { get; set; } 8 | 9 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 10 | } 11 | } -------------------------------------------------------------------------------- /src/DynamicExpresso.Core/DetectorOptions.cs: -------------------------------------------------------------------------------- 1 | namespace DynamicExpresso 2 | { 3 | /// 4 | /// Option to set the detector variable level 5 | /// 6 | public enum DetectorOptions 7 | { 8 | None = 0, 9 | [System.Obsolete("IncludeChildren is deprecated and will be removed in a future version.")] 10 | IncludeChildren = 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/DynamicExpresso.Core/DefaultNumberType.cs: -------------------------------------------------------------------------------- 1 | namespace DynamicExpresso 2 | { 3 | /// 4 | /// Setting the default number types when no suffix is specified 5 | /// 6 | public enum DefaultNumberType 7 | { 8 | Default = 0, //(Int by default or Double if real number) 9 | Int = 1, 10 | Long = 2, 11 | Single = 3, 12 | Double = 4, 13 | Decimal = 5 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "taskName": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/sample/DynamicExpressoWebShell/DynamicExpressoWebShell.csproj" 11 | ], 12 | "problemMatcher": "$msCompile" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /src/GlobalAssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | [assembly: AssemblyCompany("Dynamic Expresso")] 5 | [assembly: AssemblyProduct("Dynamic Expresso")] 6 | [assembly: AssemblyDescription("C# expression interpreter/evaluator. See https://github.com/dynamicexpresso/DynamicExpresso.")] 7 | [assembly: AssemblyCopyright("Copyright © 2015")] 8 | [assembly: ComVisible(false)] 9 | [assembly: AssemblyVersion("1.3.1.0")] 10 | 11 | -------------------------------------------------------------------------------- /src/DynamicExpresso.Core/AssignmentOperators.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DynamicExpresso 4 | { 5 | [Flags] 6 | public enum AssignmentOperators 7 | { 8 | /// 9 | /// Disable all the assignment operators 10 | /// 11 | None = 0, 12 | /// 13 | /// Enable the assignment equal operator 14 | /// 15 | AssignmentEqual = 1, 16 | /// 17 | /// Enable all assignment operators 18 | /// 19 | All = AssignmentEqual 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /sample/DynamicExpressoWebShell/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore; 2 | using Microsoft.AspNetCore.Hosting; 3 | 4 | namespace DynamicExpressoWebShell 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | BuildWebHost(args).Run(); 11 | } 12 | 13 | public static IWebHost BuildWebHost(string[] args) => 14 | WebHost.CreateDefaultBuilder(args) 15 | .UseStartup() 16 | .Build(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sample/DynamicExpressoWebShell/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using DynamicExpressoWebShell.Models; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | namespace DynamicExpressoWebShell.Controllers 6 | { 7 | public class HomeController : Controller 8 | { 9 | // 10 | // GET: /Home/ 11 | 12 | public IActionResult Index() 13 | { 14 | return View(); 15 | } 16 | 17 | public IActionResult Error() 18 | { 19 | return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /test/DynamicExpresso.UnitTest/PerformanceTest.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using NUnit.Framework; 3 | 4 | namespace DynamicExpresso.UnitTest 5 | { 6 | [TestFixture] 7 | public class PerformanceTest 8 | { 9 | [Test] 10 | public void InterperterCreation() 11 | { 12 | // TODO Study if there is a better way to test performance 13 | 14 | var stopwatch = Stopwatch.StartNew(); 15 | 16 | for (var i = 0; i < 1000; i++) 17 | { 18 | new Interpreter(InterpreterOptions.Default); 19 | } 20 | 21 | Assert.That(stopwatch.ElapsedMilliseconds, Is.LessThan(200)); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/DynamicExpresso.UnitTest/CaseInsensitivePropertyTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace DynamicExpresso.UnitTest 4 | { 5 | [TestFixture] 6 | public class CaseInsensitivePropertyTest 7 | { 8 | [Test] 9 | public void CaseInsensitive_Property_Default() 10 | { 11 | var target = new Interpreter(); 12 | 13 | Assert.That(target.CaseInsensitive, Is.False); 14 | } 15 | 16 | [Test] 17 | public void Setting_CaseInsensitive() 18 | { 19 | var target = new Interpreter(InterpreterOptions.DefaultCaseInsensitive); 20 | 21 | Assert.That(target.CaseInsensitive, Is.True); 22 | } 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/DynamicExpresso.Core/Exceptions/DynamicExpressoException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DynamicExpresso.Exceptions 4 | { 5 | [Serializable] 6 | public class DynamicExpressoException : Exception 7 | { 8 | public DynamicExpressoException() { } 9 | public DynamicExpressoException(string message) : base(message) { } 10 | public DynamicExpressoException(string message, Exception inner) : base(message, inner) { } 11 | 12 | protected DynamicExpressoException( 13 | System.Runtime.Serialization.SerializationInfo info, 14 | System.Runtime.Serialization.StreamingContext context) 15 | : base(info, context) { } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/DynamicExpresso.Core/Parsing/ParserConstants.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace DynamicExpresso.Parsing 4 | { 5 | internal static class ParserConstants 6 | { 7 | public static readonly Expression NullLiteralExpression = Expression.Constant(null); 8 | 9 | public const string KeywordAs = "as"; 10 | public const string KeywordIs = "is"; 11 | public const string KeywordNew = "new"; 12 | public const string KeywordTypeof = "typeof"; 13 | public const string KeywordDefault = "default"; 14 | 15 | public static readonly string[] ReservedKeywords = { 16 | KeywordAs, 17 | KeywordIs, 18 | KeywordNew, 19 | KeywordTypeof, 20 | KeywordDefault 21 | }; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/DynamicExpresso.Core/Reflection/MethodData.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq.Expressions; 3 | using System.Reflection; 4 | 5 | namespace DynamicExpresso.Reflection 6 | { 7 | internal class MethodData 8 | { 9 | public MethodBase MethodBase; 10 | public IList Parameters; 11 | public IList PromotedParameters; 12 | public bool HasParamsArray; 13 | 14 | public static MethodData Gen(MethodBase method) 15 | { 16 | return new MethodData 17 | { 18 | MethodBase = method, 19 | Parameters = method.GetParameters() 20 | }; 21 | } 22 | 23 | public override string ToString() 24 | { 25 | return MethodBase.ToString(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/DynamicExpresso.UnitTest/DynamicExpresso.UnitTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0;net8.0;net462 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/DynamicExpresso.Core/IdentifiersInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace DynamicExpresso 5 | { 6 | public class IdentifiersInfo 7 | { 8 | public IdentifiersInfo( 9 | IEnumerable unknownIdentifiers, 10 | IEnumerable identifiers, 11 | IEnumerable types) 12 | { 13 | UnknownIdentifiers = unknownIdentifiers.ToList(); 14 | Identifiers = identifiers.ToList(); 15 | Types = types.ToList(); 16 | } 17 | 18 | public IEnumerable UnknownIdentifiers { get; private set; } 19 | public IEnumerable Identifiers { get; private set; } 20 | public IEnumerable Types { get; private set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/DynamicExpresso.Core/Exceptions/ReflectionNotAllowedException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | using DynamicExpresso.Resources; 4 | 5 | namespace DynamicExpresso.Exceptions 6 | { 7 | [Serializable] 8 | public class ReflectionNotAllowedException : ParseException 9 | { 10 | public ReflectionNotAllowedException() 11 | : base(ErrorMessages.ReflectionNotAllowed, 0) 12 | { 13 | } 14 | 15 | protected ReflectionNotAllowedException( 16 | SerializationInfo info, 17 | StreamingContext context) 18 | : base(info, context) 19 | { 20 | } 21 | 22 | public override void GetObjectData(SerializationInfo info, StreamingContext context) 23 | { 24 | base.GetObjectData(info, context); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /sample/DynamicExpressoWebShell/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:56191/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "WebApplication1": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "http://localhost:56192/" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/DynamicExpresso.UnitTest/FunctionTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NUnit.Framework; 3 | 4 | namespace DynamicExpresso.UnitTest 5 | { 6 | [TestFixture] 7 | public class FunctionTest 8 | { 9 | [Test] 10 | public void Calling_Function() 11 | { 12 | var target = new Interpreter(); 13 | Func pow = (x, y) => Math.Pow(x, y); 14 | target.SetFunction("pow", pow); 15 | 16 | Assert.That(target.Eval("pow(5, 2)"), Is.EqualTo(25)); 17 | } 18 | 19 | [Test] 20 | public void Chaining_Functions() 21 | { 22 | var target = new Interpreter(); 23 | Func pow = (x, y) => Math.Pow(x, y); 24 | target.SetFunction("pow", pow); 25 | 26 | Assert.That(target.Eval("pow(5, 2).ToString()"), Is.EqualTo("25")); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/DynamicExpresso.Core/Reflection/IndexerData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace DynamicExpresso.Reflection 5 | { 6 | internal class IndexerData : MethodData 7 | { 8 | public readonly PropertyInfo Indexer; 9 | 10 | public IndexerData(PropertyInfo indexer) 11 | { 12 | Indexer = indexer; 13 | 14 | var method = indexer.GetGetMethod(); 15 | if (method != null) 16 | { 17 | Parameters = method.GetParameters(); 18 | } 19 | else 20 | { 21 | method = indexer.GetSetMethod(); 22 | Parameters = RemoveLast(method.GetParameters()); 23 | } 24 | } 25 | 26 | private static T[] RemoveLast(T[] array) 27 | { 28 | var result = new T[array.Length - 1]; 29 | Array.Copy(array, 0, result, 0, result.Length); 30 | return result; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/DynamicExpresso.Core/Parsing/TokenId.cs: -------------------------------------------------------------------------------- 1 | namespace DynamicExpresso.Parsing 2 | { 3 | internal enum TokenId 4 | { 5 | Unknown, 6 | End, 7 | Identifier, 8 | CharLiteral, 9 | StringLiteral, 10 | IntegerLiteral, 11 | RealLiteral, 12 | Exclamation, 13 | Percent, 14 | OpenParen, 15 | CloseParen, 16 | Asterisk, 17 | Plus, 18 | Comma, 19 | Minus, 20 | Tilde, 21 | Dot, 22 | QuestionQuestion, 23 | Slash, 24 | Colon, 25 | LessThan, 26 | GreaterThan, 27 | Question, 28 | OpenBracket, 29 | CloseBracket, 30 | ExclamationEqual, 31 | Amphersand, 32 | DoubleAmphersand, 33 | LessThanEqual, 34 | DoubleEqual, 35 | GreaterThanEqual, 36 | Bar, 37 | DoubleBar, 38 | Equal, 39 | Caret, 40 | OpenCurlyBracket, 41 | CloseCurlyBracket, 42 | LambdaArrow, 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/DynamicExpresso.Core/Exceptions/DuplicateParameterException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | using DynamicExpresso.Resources; 4 | 5 | namespace DynamicExpresso.Exceptions 6 | { 7 | [Serializable] 8 | public class DuplicateParameterException : DynamicExpressoException 9 | { 10 | public DuplicateParameterException(string identifier) 11 | : base(string.Format(ErrorMessages.DuplicateParameter, identifier)) 12 | { 13 | Identifier = identifier; 14 | } 15 | public string Identifier { get; private set; } 16 | 17 | protected DuplicateParameterException( 18 | SerializationInfo info, 19 | StreamingContext context) 20 | : base(info, context) 21 | { 22 | Identifier = info.GetString("Identifier"); 23 | } 24 | 25 | public override void GetObjectData(SerializationInfo info, StreamingContext context) 26 | { 27 | info.AddValue("Identifier", Identifier); 28 | 29 | base.GetObjectData(info, context); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/DynamicExpresso.Core/Exceptions/UnknownIdentifierException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | using DynamicExpresso.Resources; 4 | 5 | namespace DynamicExpresso.Exceptions 6 | { 7 | [Serializable] 8 | public class UnknownIdentifierException : ParseException 9 | { 10 | public UnknownIdentifierException(string identifier, int position) 11 | : base(string.Format(ErrorMessages.UnknownIdentifier, identifier), position) 12 | { 13 | Identifier = identifier; 14 | } 15 | 16 | public string Identifier { get; private set; } 17 | 18 | protected UnknownIdentifierException( 19 | SerializationInfo info, 20 | StreamingContext context) 21 | : base(info, context) 22 | { 23 | Identifier = info.GetString("Identifier"); 24 | } 25 | 26 | public override void GetObjectData(SerializationInfo info, StreamingContext context) 27 | { 28 | info.AddValue("Identifier", Identifier); 29 | 30 | base.GetObjectData(info, context); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/DynamicExpresso.UnitTest/EnumerableTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using NUnit.Framework; 4 | 5 | namespace DynamicExpresso.UnitTest 6 | { 7 | [TestFixture] 8 | public class EnumerableTest 9 | { 10 | [Test] 11 | public void Invoke_enumerable_extensions() 12 | { 13 | var x = new int[] { 10, 30, 4 }; 14 | 15 | var target = new Interpreter() 16 | .Reference(typeof(System.Linq.Enumerable)) 17 | .SetVariable("x", x); 18 | 19 | Assert.That(target.Eval("x.Count()"), Is.EqualTo(x.Count())); 20 | Assert.That(target.Eval("x.Average()"), Is.EqualTo(x.Average())); 21 | Assert.That(target.Eval("x.First()"), Is.EqualTo(x.First())); 22 | Assert.That(target.Eval("x.Last()"), Is.EqualTo(x.Last())); 23 | Assert.That(target.Eval("x.Max()"), Is.EqualTo(x.Max())); 24 | Assert.That(target.Eval>("x.Skip(2)"), Is.EqualTo(x.Skip(2))); 25 | Assert.That(target.Eval>("x.Skip(2)"), Is.EqualTo(x.Skip(2))); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /sample/DynamicExpressoWebShell/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @using DynamicExpressoWebShell.Models 2 | @model DynamicExpressoWebShell.Models.ErrorViewModel 3 | @{ 4 | ViewData["Title"] = "Error"; 5 | } 6 | 7 |

Error.

8 |

An error occurred while processing your request.

9 | 10 | @if (Model.ShowRequestId) 11 | { 12 |

13 | Request ID: @Model.RequestId 14 |

15 | } 16 | 17 |

Development Mode

18 |

19 | Swapping to Development environment will display more detailed information about the error that occurred. 20 |

21 |

22 | Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application. 23 |

24 | -------------------------------------------------------------------------------- /src/DynamicExpresso.Core/Visitors/DisableReflectionVisitor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Reflection; 4 | using DynamicExpresso.Exceptions; 5 | 6 | namespace DynamicExpresso.Visitors 7 | { 8 | public class DisableReflectionVisitor : ExpressionVisitor 9 | { 10 | protected override Expression VisitMethodCall(MethodCallExpression node) 11 | { 12 | if (node.Object != null 13 | && (typeof(Type).IsAssignableFrom(node.Object.Type) 14 | || typeof(MemberInfo).IsAssignableFrom(node.Object.Type))) 15 | { 16 | throw new ReflectionNotAllowedException(); 17 | } 18 | 19 | return base.VisitMethodCall(node); 20 | } 21 | 22 | protected override Expression VisitMember(MemberExpression node) 23 | { 24 | if ((typeof(Type).IsAssignableFrom(node.Member.DeclaringType) 25 | || typeof(MemberInfo).IsAssignableFrom(node.Member.DeclaringType)) 26 | && node.Member.Name != "Name") 27 | { 28 | throw new ReflectionNotAllowedException(); 29 | } 30 | 31 | return base.VisitMember(node); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/DynamicExpresso.Core/Exceptions/AssignmentOperatorDisabledException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | using DynamicExpresso.Resources; 4 | 5 | namespace DynamicExpresso.Exceptions 6 | { 7 | [Serializable] 8 | public class AssignmentOperatorDisabledException : ParseException 9 | { 10 | public AssignmentOperatorDisabledException(string operatorString, int position) 11 | : base(string.Format(ErrorMessages.AssignmentOperatorNotAllowed, operatorString), position) 12 | { 13 | OperatorString = operatorString; 14 | } 15 | 16 | public string OperatorString { get; private set; } 17 | 18 | protected AssignmentOperatorDisabledException( 19 | SerializationInfo info, 20 | StreamingContext context) 21 | : base(info, context) 22 | { 23 | OperatorString = info.GetString("OperatorString"); 24 | } 25 | 26 | public override void GetObjectData(SerializationInfo info, StreamingContext context) 27 | { 28 | info.AddValue("OperatorString", OperatorString); 29 | 30 | base.GetObjectData(info, context); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /sample/DynamicExpressoWebShell/Controllers/InterpreterController.cs: -------------------------------------------------------------------------------- 1 | using DynamicExpressoWebShell.Services; 2 | using System.Text.Json; 3 | using System; 4 | using Microsoft.AspNetCore.Mvc; 5 | 6 | namespace DynamicExpressoWebShell.Controllers 7 | { 8 | public class InterpreterController : Controller 9 | { 10 | private readonly WebShell _webShell; 11 | 12 | public InterpreterController(WebShell webShell) 13 | { 14 | _webShell = webShell; 15 | } 16 | 17 | [HttpPost] 18 | public ActionResult Eval(string expression) 19 | { 20 | try 21 | { 22 | var result = _webShell.Eval(expression); 23 | 24 | //if (result == null) 25 | // return Json(new { success = true, result = "" }); 26 | 27 | var options = new JsonSerializerOptions { WriteIndented = true }; 28 | var prettifyOutput = JsonSerializer.Serialize(result, options); 29 | 30 | return Json(new { success = true, result = prettifyOutput }); 31 | } 32 | catch (Exception ex) 33 | { 34 | return Json(new { success = false, result = ex.Message }); 35 | } 36 | } 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Davide Icardi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /sample/DynamicExpressoWebShell/Services/WebShell.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DynamicExpressoWebShell.Services 4 | { 5 | public class WebShell 6 | { 7 | readonly DynamicExpresso.Interpreter _interpreter; 8 | readonly CommandsHistory _commandsHistory; 9 | 10 | public WebShell() 11 | { 12 | _interpreter = new DynamicExpresso.Interpreter(); 13 | _commandsHistory = new CommandsHistory(); 14 | 15 | _interpreter.SetVariable("Commands", _commandsHistory); 16 | } 17 | 18 | public object Eval(string expression) 19 | { 20 | if (expression != null && expression.Length > 200) 21 | throw new Exception("Live demo doesn't support expression with more than 200 characters."); 22 | 23 | _commandsHistory.HandleCommandExecuted(new CommandEvent(expression)); 24 | 25 | var result = _interpreter.Eval(expression); 26 | 27 | return result; 28 | } 29 | 30 | public CommandsHistory CommandsHistory 31 | { 32 | get; 33 | private set; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /test/DynamicExpresso.UnitTest/CollectionHelperTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using NUnit.Framework; 5 | 6 | namespace DynamicExpresso.UnitTest 7 | { 8 | [TestFixture] 9 | public class CollectionHelperTests 10 | { 11 | [Test] 12 | public void Where() 13 | { 14 | var target = new Interpreter(); 15 | 16 | target.SetVariable("IntCollectionHelper", new CollectionHelper()); 17 | 18 | var list = new List { 1, 10, 19, 21 }; 19 | 20 | var results = target.Eval("IntCollectionHelper.Where(list, \"x > 19\")", new Parameter("list", list)) 21 | as IEnumerable; 22 | 23 | Assert.That(results.Count(), Is.EqualTo(1)); 24 | Assert.That(results.First(), Is.EqualTo(21)); 25 | } 26 | } 27 | 28 | public class CollectionHelper 29 | { 30 | private readonly Interpreter _interpreter; 31 | 32 | public CollectionHelper() 33 | { 34 | _interpreter = new Interpreter(); 35 | } 36 | 37 | public IEnumerable Where(IEnumerable values, string expression) 38 | { 39 | var predicate = _interpreter.ParseAsDelegate>(expression, "x"); 40 | 41 | return values.Where(predicate); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/DynamicExpresso.Core/Exceptions/NoApplicableMethodException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | using DynamicExpresso.Resources; 4 | 5 | namespace DynamicExpresso.Exceptions 6 | { 7 | [Serializable] 8 | public class NoApplicableMethodException : ParseException 9 | { 10 | public NoApplicableMethodException(string methodName, string methodTypeName, int position) 11 | : base(string.Format(ErrorMessages.InvalidMethodCall2, methodName, methodTypeName), position) 12 | { 13 | MethodTypeName = methodTypeName; 14 | MethodName = methodName; 15 | } 16 | 17 | public string MethodTypeName { get; private set; } 18 | public string MethodName { get; private set; } 19 | 20 | protected NoApplicableMethodException( 21 | SerializationInfo info, 22 | StreamingContext context) 23 | : base(info, context) 24 | { 25 | MethodTypeName = info.GetString("MethodTypeName"); 26 | MethodName = info.GetString("MethodName"); 27 | } 28 | 29 | public override void GetObjectData(SerializationInfo info, StreamingContext context) 30 | { 31 | info.AddValue("MethodName", MethodName); 32 | info.AddValue("MethodTypeName", MethodTypeName); 33 | 34 | base.GetObjectData(info, context); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/DynamicExpresso.Core/Exceptions/ParseException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | using DynamicExpresso.Resources; 4 | 5 | namespace DynamicExpresso.Exceptions 6 | { 7 | [Serializable] 8 | public class ParseException : DynamicExpressoException 9 | { 10 | public ParseException(string message, int position) 11 | : base(string.Format(ErrorMessages.Format, message, position)) 12 | { 13 | Position = position; 14 | } 15 | 16 | public ParseException(string message, int position, Exception innerException) 17 | : base(string.Format(ErrorMessages.Format, message, position), innerException) 18 | { 19 | Position = position; 20 | } 21 | 22 | public int Position { get; private set; } 23 | 24 | public static ParseException Create(int pos, string format, params object[] args) 25 | { 26 | return new ParseException(string.Format(format, args), pos); 27 | } 28 | 29 | protected ParseException( 30 | SerializationInfo info, 31 | StreamingContext context) 32 | : base(info, context) 33 | { 34 | Position = info.GetInt32("Position"); 35 | } 36 | 37 | public override void GetObjectData(SerializationInfo info, StreamingContext context) 38 | { 39 | info.AddValue("Position", Position); 40 | 41 | base.GetObjectData(info, context); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /sample/DynamicExpressoWebShell/wwwroot/Content/webshell.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #terminal { 5 | border: 1px solid #CCCCCC; 6 | height: 400px; 7 | background: black; 8 | overflow: scroll; 9 | } 10 | 11 | #terminal div.line { 12 | margin-bottom: 5px; 13 | } 14 | 15 | #terminal input { 16 | width: 90%; 17 | margin: 1px 0 1px 10px; 18 | border: none; 19 | display: inline; 20 | padding: 2px; 21 | background: black; 22 | color: #FFFFFF; 23 | font-size: 16px; 24 | font-family: Consolas, Monaco, monospace; 25 | 26 | -webkit-box-shadow: none; 27 | -moz-box-shadow: none; 28 | box-shadow: none; 29 | -webkit-transition: none; 30 | } 31 | 32 | #terminal input:focus { 33 | border: none; 34 | -webkit-box-shadow: none; 35 | -moz-box-shadow: none; 36 | box-shadow: none; 37 | -webkit-transition: none; 38 | } 39 | 40 | #terminal input[disabled]{ 41 | cursor: default; 42 | } 43 | 44 | #terminal p, #terminal pre { 45 | margin: 2px; 46 | padding:0; 47 | color: #FFFFFF; 48 | font-size: 16px; 49 | background: black; 50 | font-family: Consolas, Monaco, monospace; 51 | } 52 | 53 | #terminal p.error, #terminal pre.error { 54 | color: #FF3300; 55 | } 56 | 57 | #terminal a { 58 | color: #6495ED; 59 | } 60 | 61 | #terminal span.prompt { 62 | color: #FFFFFF; 63 | font-size: 14px; 64 | margin-left: 2px; 65 | } 66 | 67 | header { 68 | } 69 | 70 | footer { 71 | margin-top: 10px; 72 | border-top: 1px solid #CCCCCC; 73 | } -------------------------------------------------------------------------------- /src/DynamicExpresso.Core/Resolution/ExpressionUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using DynamicExpresso.Parsing; 4 | using DynamicExpresso.Reflection; 5 | 6 | namespace DynamicExpresso.Resolution 7 | { 8 | internal static class ExpressionUtils 9 | { 10 | public static Expression PromoteExpression(Expression expr, Type type) 11 | { 12 | if (expr.Type == type) 13 | return expr; 14 | 15 | if (expr is ConstantExpression ce && ce == ParserConstants.NullLiteralExpression) 16 | { 17 | if (type.ContainsGenericParameters) 18 | return null; 19 | if (!type.IsValueType || TypeUtils.IsNullableType(type)) 20 | return Expression.Constant(null, type); 21 | } 22 | 23 | if (expr is InterpreterExpression ie) 24 | { 25 | if (!ie.IsCompatibleWithDelegate(type)) 26 | return null; 27 | 28 | if (!type.ContainsGenericParameters) 29 | return ie.EvalAs(type); 30 | 31 | return expr; 32 | } 33 | 34 | if (type.IsAssignableFrom(expr.Type)) 35 | { 36 | return Expression.Convert(expr, type); 37 | } 38 | 39 | if (type.IsGenericType && !TypeUtils.IsNumericType(type)) 40 | { 41 | var genericType = TypeUtils.FindAssignableGenericType(expr.Type, type); 42 | if (genericType != null) 43 | return Expression.Convert(expr, genericType); 44 | } 45 | 46 | if (TypeUtils.IsCompatibleWith(expr.Type, type)) 47 | { 48 | return Expression.Convert(expr, type); 49 | } 50 | 51 | return null; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /sample/DynamicExpressoWebShell/DynamicExpressoWebShell.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /sample/DynamicExpressoWebShell/Startup.cs: -------------------------------------------------------------------------------- 1 | using DynamicExpressoWebShell.Services; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.AspNetCore.Hosting; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.Hosting; 8 | 9 | namespace DynamicExpressoWebShell 10 | { 11 | public class Startup 12 | { 13 | public Startup(IConfiguration configuration) 14 | { 15 | Configuration = configuration; 16 | } 17 | 18 | public IConfiguration Configuration { get; } 19 | 20 | // This method gets called by the runtime. Use this method to add services to the container. 21 | public void ConfigureServices(IServiceCollection services) 22 | { 23 | services.AddSingleton(); 24 | 25 | services.AddMvc() 26 | .SetCompatibilityVersion(CompatibilityVersion.Version_3_0); 27 | } 28 | 29 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 30 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 31 | { 32 | app.UseRequestLocalization("en", "de"); 33 | 34 | if (env.IsDevelopment()) 35 | { 36 | app.UseDeveloperExceptionPage(); 37 | app.UseBrowserLink(); 38 | } 39 | else 40 | { 41 | app.UseExceptionHandler("/Home/Error"); 42 | } 43 | 44 | app.UseRouting(); 45 | app.UseStaticFiles(); 46 | 47 | app.UseEndpoints(endpoints => 48 | { 49 | endpoints.MapDefaultControllerRoute(); 50 | }); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/DynamicExpresso.Core/InterpreterOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DynamicExpresso 4 | { 5 | [Flags] 6 | public enum InterpreterOptions 7 | { 8 | None = 0, 9 | /// 10 | /// Load primitive types like 'string', 'double', 'int', 'DateTime', 'Guid', ... See also LanguageConstants.CSharpPrimitiveTypes and LanguageConstants.PrimitiveTypes 11 | /// 12 | PrimitiveTypes = 1, 13 | /// 14 | /// Load system keywords like 'true', 'false', 'null'. See also LanguageConstants.Literals. 15 | /// 16 | SystemKeywords = 2, 17 | /// 18 | /// Load common types like 'System.Math', 'System.Convert', 'System.Linq.Enumerable'. See also LanguageConstants.CommonTypes. 19 | /// 20 | CommonTypes = 4, 21 | /// 22 | /// Variables and parameters names are case insensitive. 23 | /// 24 | CaseInsensitive = 8, 25 | /// 26 | /// Allow treating expressions of type Object as dynamic 27 | /// 28 | LateBindObject = 16, 29 | /// 30 | /// Enable parsing of lambda expressions. Disabled by default, because it has a slight performance cost. 31 | /// 32 | LambdaExpressions = 32, 33 | /// 34 | /// Load all default configurations: PrimitiveTypes + SystemKeywords + CommonTypes 35 | /// 36 | Default = PrimitiveTypes | SystemKeywords | CommonTypes, 37 | /// 38 | /// Load all default configurations: PrimitiveTypes + SystemKeywords + CommonTypes + CaseInsensitive 39 | /// 40 | DefaultCaseInsensitive = PrimitiveTypes | SystemKeywords | CommonTypes | CaseInsensitive, 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/DynamicExpresso.Core/ReferenceType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using DynamicExpresso.Reflection; 6 | using DynamicExpresso.Resources; 7 | 8 | namespace DynamicExpresso 9 | { 10 | public class ReferenceType 11 | { 12 | public Type Type { get; private set; } 13 | 14 | /// 15 | /// Public name that must be used in the expression. 16 | /// 17 | public string Name { get; private set; } 18 | 19 | public IList ExtensionMethods { get; private set; } 20 | 21 | public ReferenceType(string name, Type type) 22 | { 23 | if (string.IsNullOrWhiteSpace(name)) 24 | throw new ArgumentNullException(nameof(name)); 25 | 26 | if (type == null) 27 | throw new ArgumentNullException(nameof(type)); 28 | 29 | if (type.IsGenericType && !type.IsGenericTypeDefinition) 30 | { 31 | var genericType = type.GetGenericTypeDefinition(); 32 | var genericTypeName = genericType.Name.Substring(0, genericType.Name.IndexOf('`')); 33 | genericTypeName += $"<{new string(',', genericType.GetGenericArguments().Length - 1)}>"; 34 | throw new ArgumentException(string.Format(ErrorMessages.GenericTypeReference, genericTypeName)); 35 | } 36 | 37 | Type = type; 38 | Name = name; 39 | ExtensionMethods = ReflectionExtensions.GetExtensionMethods(type).ToList(); 40 | } 41 | 42 | public ReferenceType(Type type) 43 | { 44 | if (type == null) 45 | throw new ArgumentNullException(nameof(type)); 46 | 47 | Type = type; 48 | Name = type.Name; 49 | ExtensionMethods = ReflectionExtensions.GetExtensionMethods(type).ToList(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /test/DynamicExpresso.UnitTest/WorkingContextTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System.Linq.Expressions; 3 | 4 | namespace DynamicExpresso.UnitTest 5 | { 6 | [TestFixture] 7 | public class WorkingContextTest 8 | { 9 | [Test] 10 | public void Simulate_a_working_context_using_this_keyword() 11 | { 12 | var workingContext = new { FirstName = "homer" }; 13 | 14 | var interpreter = new Interpreter(); 15 | interpreter.SetVariable("this", workingContext); 16 | 17 | Assert.That(interpreter.Eval("this.FirstName"), Is.EqualTo(workingContext.FirstName)); 18 | } 19 | 20 | [Test] 21 | public void Injection_a_property_expresion_to_simulate_a_working_context_parsing_another_property() 22 | { 23 | var workingContext = new { FirstName = "homer" }; 24 | 25 | var interpreter = new Interpreter(); 26 | interpreter.SetVariable("this", workingContext); 27 | var firstNameExpression = interpreter.Parse("this.FirstName").Expression; 28 | interpreter.SetExpression("FirstName", firstNameExpression); 29 | 30 | Assert.That(interpreter.Eval("FirstName"), Is.EqualTo(workingContext.FirstName)); 31 | } 32 | 33 | [Test] 34 | public void Injection_a_property_expresion_to_simulate_a_working_context() 35 | { 36 | var workingContext = new { FirstName = "homer" }; 37 | 38 | var workingContextExpression = Expression.Constant(workingContext); 39 | var firstNameExpression = Expression.Property(workingContextExpression, "FirstName"); 40 | 41 | var interpreter = new Interpreter(); 42 | interpreter.SetExpression("FirstName", firstNameExpression); 43 | 44 | Assert.That(interpreter.Eval("FirstName"), Is.EqualTo(workingContext.FirstName)); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/DynamicExpresso.Core/Parameter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace DynamicExpresso 5 | { 6 | /// 7 | /// An expression parameter. This class is thread safe. 8 | /// 9 | public class Parameter 10 | { 11 | public Parameter(string name, object value) 12 | { 13 | if (value == null) 14 | throw new ArgumentNullException("value"); 15 | 16 | Name = name; 17 | Type = value.GetType(); 18 | Value = value; 19 | 20 | Expression = System.Linq.Expressions.Expression.Parameter(Type, name); 21 | } 22 | 23 | public Parameter(ParameterExpression parameterExpression) 24 | { 25 | Name = parameterExpression.Name; 26 | Type = parameterExpression.Type; 27 | Value = null; 28 | 29 | Expression = parameterExpression; 30 | } 31 | 32 | public Parameter(string name, Type type, object value = null) 33 | { 34 | Name = name; 35 | Type = type; 36 | Value = value; 37 | 38 | Expression = System.Linq.Expressions.Expression.Parameter(type, name); 39 | } 40 | 41 | public static Parameter Create(string name, T value) 42 | { 43 | return new Parameter(name, typeof(T), value); 44 | } 45 | 46 | public string Name { get; private set; } 47 | public Type Type { get; private set; } 48 | public object Value { get; private set; } 49 | 50 | public ParameterExpression Expression { get; private set; } 51 | } 52 | 53 | /// 54 | /// Parameter with its position in the expression. 55 | /// 56 | internal class ParameterWithPosition : Parameter 57 | { 58 | public ParameterWithPosition(int pos, string name, Type type) 59 | : base(name, type) 60 | { 61 | Position = pos; 62 | } 63 | 64 | public int Position { get; } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/DynamicExpresso.Core/DynamicExpresso.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | DynamicExpresso 6 | 7 | Davide Icardi 8 | DynamicExpresso 9 | C# expression interpreter/evaluator 10 | Davide Icardi 11 | dynamic expression linq eval script 12 | True 13 | True 14 | True 15 | https://github.com/dynamicexpresso/DynamicExpresso 16 | MIT 17 | https://github.com/dynamicexpresso/DynamicExpresso.git 18 | git 19 | 0.0.1 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ResXFileCodeGenerator 30 | ErrorMessages.Designer.cs 31 | 32 | 33 | 34 | 35 | 36 | True 37 | True 38 | ErrorMessages.resx 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /DynamicExpressoWebShell.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27004.2005 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamicExpressoWebShell", "sample\DynamicExpressoWebShell\DynamicExpressoWebShell.csproj", "{970A9B0D-4DDF-4E8F-A184-89CC202DB542}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamicExpresso.Core", "src\DynamicExpresso.Core\DynamicExpresso.Core.csproj", "{B496A172-45A2-413A-9060-CDBE4142A8F2}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {970A9B0D-4DDF-4E8F-A184-89CC202DB542}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {970A9B0D-4DDF-4E8F-A184-89CC202DB542}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {970A9B0D-4DDF-4E8F-A184-89CC202DB542}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {970A9B0D-4DDF-4E8F-A184-89CC202DB542}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {B496A172-45A2-413A-9060-CDBE4142A8F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {B496A172-45A2-413A-9060-CDBE4142A8F2}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {B496A172-45A2-413A-9060-CDBE4142A8F2}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {B496A172-45A2-413A-9060-CDBE4142A8F2}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {A4275949-B3D5-4432-9D94-27F4EE8E5B90} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /sample/DynamicExpressoWebShell/Properties/PublishProfiles/dynamic-expresso - Web Deploy.pubxml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | MSDeploy 9 | /subscriptions/3ce5a23c-8060-42c6-9926-14831822b161/resourceGroups/Default-Web-WestUS/providers/Microsoft.Web/sites/dynamic-expresso 10 | Default-Web-WestUS 11 | AzureWebSite 12 | Release 13 | Any CPU 14 | http://dynamic-expresso.azurewebsites.net 15 | True 16 | False 17 | 970a9b0d-4ddf-4e8f-a184-89cc202db542 18 | dynamic-expresso.scm.azurewebsites.net:443 19 | dynamic-expresso 20 | 21 | True 22 | WMSVC 23 | True 24 | $dynamic-expresso 25 | <_SavePWD>True 26 | <_DestinationType>AzureWebSite 27 | 28 | -------------------------------------------------------------------------------- /test/DynamicExpresso.UnitTest/VisitorsTest.cs: -------------------------------------------------------------------------------- 1 | using DynamicExpresso.Exceptions; 2 | using NUnit.Framework; 3 | 4 | namespace DynamicExpresso.UnitTest 5 | { 6 | [TestFixture] 7 | public class VisitorsTest 8 | { 9 | [Test] 10 | public void By_default_reflection_is_not_permitted() 11 | { 12 | var target = new Interpreter(); 13 | 14 | Assert.Throws(() => target.Parse("typeof(double).GetMethods()")); 15 | Assert.Throws(() => target.Parse("typeof(double).Assembly")); 16 | 17 | Assert.Throws(() => target.Parse("x.GetType().GetMethods()", new Parameter("x", typeof(X)))); 18 | Assert.Throws(() => target.Parse("x.GetType().Assembly", new Parameter("x", typeof(X)))); 19 | } 20 | 21 | [Test] 22 | public void By_default_reflection_to_get_name_is_permitted() 23 | { 24 | var target = new Interpreter(); 25 | 26 | Assert.That(target.Eval("typeof(double).Name"), Is.EqualTo("Double")); 27 | Assert.That(target.Eval("x.GetType().Name", new Parameter("x", typeof(X), new X())), Is.EqualTo("X")); 28 | } 29 | 30 | [Test] 31 | public void Reflection_can_be_enabled() 32 | { 33 | var target = new Interpreter() 34 | .EnableReflection(); 35 | 36 | Assert.That(target.Eval("typeof(double).GetMethods()"), Is.EqualTo(typeof(double).GetMethods())); 37 | Assert.That(target.Eval("typeof(double).Assembly"), Is.EqualTo(typeof(double).Assembly)); 38 | 39 | var x = new X(); 40 | Assert.That(target.Eval("x.GetType().GetMethods()", new Parameter("x", x)), Is.EqualTo(x.GetType().GetMethods())); 41 | Assert.That(target.Eval("x.GetType().Assembly", new Parameter("x", x)), Is.EqualTo(x.GetType().Assembly)); 42 | } 43 | 44 | public class X { } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (web)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/sample/DynamicExpressoWebShell/bin/Debug/netcoreapp2.0/DynamicExpressoWebShell.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}/sample/DynamicExpressoWebShell", 16 | "stopAtEntry": false, 17 | "internalConsoleOptions": "openOnSessionStart", 18 | "launchBrowser": { 19 | "enabled": true, 20 | "args": "${auto-detect-url}", 21 | "windows": { 22 | "command": "cmd.exe", 23 | "args": "/C start ${auto-detect-url}" 24 | }, 25 | "osx": { 26 | "command": "open" 27 | }, 28 | "linux": { 29 | "command": "xdg-open" 30 | } 31 | }, 32 | "env": { 33 | "ASPNETCORE_ENVIRONMENT": "Development" 34 | }, 35 | "sourceFileMap": { 36 | "/Views": "${workspaceFolder}/Views" 37 | } 38 | }, 39 | { 40 | "name": ".NET Core Attach", 41 | "type": "coreclr", 42 | "request": "attach", 43 | "processId": "${command:pickProcess}" 44 | } 45 | ] 46 | } -------------------------------------------------------------------------------- /test/DynamicExpresso.UnitTest/ReferencedTypesPropertyTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using NUnit.Framework; 5 | 6 | namespace DynamicExpresso.UnitTest 7 | { 8 | [TestFixture] 9 | public class ReferencedTypesPropertyTest 10 | { 11 | [Test] 12 | public void Getting_a_list_of_known_types() 13 | { 14 | var target = new Interpreter(); 15 | 16 | Assert.That(target.ReferencedTypes.Any(p => p.Name == "string"), Is.True); 17 | Assert.That(target.ReferencedTypes.Any(p => p.Name == "int"), Is.True); 18 | Assert.That(target.ReferencedTypes.Any(p => p.Name == "Guid"), Is.True); 19 | } 20 | 21 | [Test] 22 | public void Known_types_should_be_empty_with_InterpreterOptions_None() 23 | { 24 | var target = new Interpreter(InterpreterOptions.None); 25 | 26 | Assert.That(target.ReferencedTypes.Any(), Is.False); 27 | } 28 | 29 | [Test] 30 | public void Registering_custom_known_types() 31 | { 32 | var target = new Interpreter(InterpreterOptions.None); 33 | 34 | target.Reference(typeof(FakeClass)); 35 | 36 | Assert.That(target.ReferencedTypes.Any(p => p.Type == typeof(FakeClass)), Is.True); 37 | } 38 | 39 | public class FakeClass 40 | { 41 | } 42 | 43 | [Test] 44 | public void Registering_generic_types() 45 | { 46 | var target = new Interpreter(InterpreterOptions.None); 47 | 48 | var exception = Assert.Throws(() => target.Reference(typeof(List))); 49 | Assert.That(exception.Message, Contains.Substring("List<>")); 50 | 51 | exception = Assert.Throws(() => target.Reference(typeof(Tuple))); 52 | Assert.That(exception.Message, Contains.Substring("Tuple<,,>")); 53 | 54 | Assert.DoesNotThrow(() => target.Reference(typeof(List<>))); 55 | Assert.DoesNotThrow(() => target.Reference(typeof(Tuple<,,>))); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /sample/DynamicExpressoWebShell/Services/CommandsHistory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | 5 | namespace DynamicExpressoWebShell.Services 6 | { 7 | 8 | public class CommandEvent 9 | { 10 | public CommandEvent(string exp) 11 | { 12 | Expression = exp; 13 | Time = DateTime.UtcNow; 14 | //UserHostAddress = HttpContext.Current.Request.UserHostAddress; 15 | //UserAgent = HttpContext.Current.Request.Browser.Browser; 16 | } 17 | 18 | public string Expression { get; private set; } 19 | public DateTime Time { get; private set; } 20 | //public string UserHostAddress { get; private set; } 21 | //public string UserAgent { get; private set; } 22 | } 23 | 24 | public class CommandsHistory 25 | { 26 | readonly List _lastCommands = new List(); 27 | long _count = 0; 28 | readonly object _lock = new object(); 29 | 30 | public long Count 31 | { 32 | get 33 | { 34 | return Interlocked.Read(ref _count); 35 | } 36 | } 37 | 38 | public void HandleCommandExecuted(CommandEvent cmd) 39 | { 40 | Interlocked.Increment(ref _count); 41 | 42 | lock (_lock) 43 | { 44 | if (_lastCommands.Count > 50) 45 | _lastCommands.Clear(); 46 | 47 | _lastCommands.Add(cmd); 48 | } 49 | } 50 | 51 | public CommandEvent[] GetLastCommands() 52 | { 53 | CommandEvent[] currentList; 54 | lock (_lock) 55 | { 56 | currentList = _lastCommands.ToArray(); 57 | } 58 | 59 | return currentList; 60 | } 61 | 62 | public void Clear() 63 | { 64 | lock (_lock) 65 | { 66 | _lastCommands.Clear(); 67 | } 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: .NET CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | test-linux: 11 | runs-on: ubuntu-24.04 12 | steps: 13 | - uses: actions/checkout@v4 14 | with: 15 | fetch-depth: '0' # all 16 | - name: Setup .NET 9.0 17 | uses: actions/setup-dotnet@v4 18 | with: 19 | dotnet-version: '9.0.x' 20 | - name: Setup gitversion 21 | run: dotnet tool install --global GitVersion.Tool 22 | - name: Calculate version 23 | id: calc_version 24 | run: | 25 | GITVERSION=$(dotnet-gitversion /output json /showvariable FullSemVer) 26 | echo "::set-output name=PROJECT_VERSION::$GITVERSION" 27 | - name: Restore packages 28 | run: dotnet restore DynamicExpresso.sln 29 | - name: Build 30 | run: dotnet build DynamicExpresso.sln --no-restore -c Release /p:Version=${{steps.calc_version.outputs.PROJECT_VERSION}} 31 | - name: Test .net core 8.0 32 | run: dotnet test DynamicExpresso.sln --no-build --no-restore -c Release --verbosity normal -f net8.0 33 | - name: Test .net core 9.0 34 | run: dotnet test DynamicExpresso.sln --no-build --no-restore -c Release --verbosity normal -f net9.0 35 | test-win: 36 | runs-on: windows-2022 37 | steps: 38 | - uses: actions/checkout@v4 39 | - name: Setup .NET 9.0 40 | uses: actions/setup-dotnet@v4 41 | with: 42 | dotnet-version: '9.0.x' 43 | - name: Restore packages 44 | run: dotnet restore DynamicExpresso.sln 45 | - name: Build 46 | run: dotnet build DynamicExpresso.sln --no-restore -c Release 47 | - name: Test .net 4.6.2 48 | run: dotnet test DynamicExpresso.sln --no-build --no-restore -c Release --verbosity normal -f net462 49 | - name: Test .net core 8.0 50 | run: dotnet test DynamicExpresso.sln --no-build --no-restore -c Release --verbosity normal -f net8.0 51 | - name: Test .net core 9.0 52 | run: dotnet test DynamicExpresso.sln --no-build --no-restore -c Release --verbosity normal -f net9.0 53 | -------------------------------------------------------------------------------- /DynamicExpresso.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.13.35806.99 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamicExpresso.UnitTest", "test\DynamicExpresso.UnitTest\DynamicExpresso.UnitTest.csproj", "{33157A92-C6B2-4A51-8262-1FEBFD6558BE}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{4088369E-AB00-4333-A56A-27E05D3767B1}" 9 | ProjectSection(SolutionItems) = preProject 10 | README.md = README.md 11 | EndProjectSection 12 | EndProject 13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamicExpresso.Core", "src\DynamicExpresso.Core\DynamicExpresso.Core.csproj", "{C6B7C0D2-B84A-4307-9C61-D95613DB564D}" 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|Any CPU = Debug|Any CPU 18 | Release|Any CPU = Release|Any CPU 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {33157A92-C6B2-4A51-8262-1FEBFD6558BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {33157A92-C6B2-4A51-8262-1FEBFD6558BE}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {33157A92-C6B2-4A51-8262-1FEBFD6558BE}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {33157A92-C6B2-4A51-8262-1FEBFD6558BE}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {C6B7C0D2-B84A-4307-9C61-D95613DB564D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {C6B7C0D2-B84A-4307-9C61-D95613DB564D}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {C6B7C0D2-B84A-4307-9C61-D95613DB564D}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {C6B7C0D2-B84A-4307-9C61-D95613DB564D}.Release|Any CPU.Build.0 = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(SolutionProperties) = preSolution 31 | HideSolutionNode = FALSE 32 | EndGlobalSection 33 | GlobalSection(ExtensibilityGlobals) = postSolution 34 | SolutionGuid = {A36C3463-448E-4051-AE87-A2994E36C1EC} 35 | EndGlobalSection 36 | GlobalSection(MonoDevelopProperties) = preSolution 37 | StartupItem = test\DynamicExpresso.UnitTest\DynamicExpresso.UnitTest.csproj 38 | EndGlobalSection 39 | EndGlobal 40 | -------------------------------------------------------------------------------- /src/DynamicExpresso.Core/Reflection/SimpleMethodSignature.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Reflection; 4 | 5 | namespace DynamicExpresso.Reflection 6 | { 7 | /// 8 | /// A simple implementation of that only provides the method signature (ie. the parameter types). 9 | /// 10 | internal class SimpleMethodSignature : MethodBase 11 | { 12 | private class SimpleParameterInfo : ParameterInfo 13 | { 14 | public SimpleParameterInfo(Type parameterType) 15 | { 16 | ClassImpl = parameterType; 17 | DefaultValueImpl = null; 18 | } 19 | 20 | public override bool HasDefaultValue => false; 21 | } 22 | 23 | public override MethodAttributes Attributes { get; } = MethodAttributes.Public; 24 | public override MemberTypes MemberType { get; } = MemberTypes.Method; 25 | 26 | private readonly ParameterInfo[] _parameterInfos; 27 | public SimpleMethodSignature(params Type[] parameterTypes) 28 | { 29 | _parameterInfos = new ParameterInfo[parameterTypes.Length]; 30 | for (var i = 0; i < parameterTypes.Length; i++) 31 | { 32 | _parameterInfos[i] = new SimpleParameterInfo(parameterTypes[i]); 33 | } 34 | } 35 | 36 | public override ParameterInfo[] GetParameters() 37 | { 38 | return _parameterInfos; 39 | } 40 | 41 | public override RuntimeMethodHandle MethodHandle => throw new NotImplementedException(); 42 | public override string Name => throw new NotImplementedException(); 43 | public override Type DeclaringType => throw new NotImplementedException(); 44 | public override Type ReflectedType => throw new NotImplementedException(); 45 | 46 | public override object[] GetCustomAttributes(bool inherit) 47 | { 48 | throw new NotImplementedException(); 49 | } 50 | 51 | public override object[] GetCustomAttributes(Type attributeType, bool inherit) 52 | { 53 | throw new NotImplementedException(); 54 | } 55 | 56 | public override MethodImplAttributes GetMethodImplementationFlags() 57 | { 58 | throw new NotImplementedException(); 59 | } 60 | 61 | public override object Invoke(object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture) 62 | { 63 | throw new NotImplementedException(); 64 | } 65 | 66 | public override bool IsDefined(Type attributeType, bool inherit) 67 | { 68 | throw new NotImplementedException(); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish packages 2 | on: 3 | release: # on new releases 4 | types: [created] 5 | workflow_dispatch: # manual event 6 | inputs: 7 | ref: 8 | description: 'The branch, tag or SHA to checkout' 9 | required: true 10 | default: 'master' 11 | isSnapshot: 12 | description: 'Is snapshot release? Set to false if this is an official release' 13 | required: true 14 | default: 'true' 15 | jobs: 16 | publish-nuget: 17 | runs-on: ubuntu-24.04 18 | steps: 19 | - uses: actions/checkout@v4 20 | with: 21 | ref: "${{ github.event.inputs.ref }}" 22 | fetch-depth: '0' # all 23 | - name: Setup .NET Core 9.0 24 | uses: actions/setup-dotnet@v4 25 | with: 26 | dotnet-version: '9.0.x' 27 | - name: Setup gitversion 28 | run: dotnet tool install --global GitVersion.Tool 29 | - name: Calculate version 30 | id: calc_version 31 | run: | 32 | GITVERSION=$(dotnet-gitversion /output json /showvariable FullSemVer) 33 | echo "::set-output name=PROJECT_VERSION::$GITVERSION" 34 | - name: Restore packages 35 | run: dotnet restore DynamicExpresso.sln 36 | - name: Build 37 | run: dotnet build DynamicExpresso.sln --no-restore -c Release /p:Version=${{steps.calc_version.outputs.PROJECT_VERSION}} 38 | - name: Test .net core 9.0 39 | run: dotnet test DynamicExpresso.sln --no-build --no-restore -c Release /p:Version=${{steps.calc_version.outputs.PROJECT_VERSION}} --verbosity normal -f net9.0 40 | - name: Setup nuget sources 41 | run: dotnet nuget add source --name github "https://nuget.pkg.github.com/dynamicexpresso/index.json" 42 | - name: Pack 43 | run: dotnet pack DynamicExpresso.sln --no-build --no-restore -c Release /p:Version=${{steps.calc_version.outputs.PROJECT_VERSION}} -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg 44 | - name: Publish to github 45 | run: dotnet nuget push "src/DynamicExpresso.Core/bin/Release/DynamicExpresso.Core.${{steps.calc_version.outputs.PROJECT_VERSION}}.nupkg" --source "github" --api-key ${{ secrets.GITHUB_TOKEN }} 46 | - name: Publish to nuget.org 47 | if: ${{ github.event.inputs.isSnapshot != 'true' }} 48 | run: dotnet nuget push "src/DynamicExpresso.Core/bin/Release/DynamicExpresso.Core.${{steps.calc_version.outputs.PROJECT_VERSION}}.nupkg" --source "nuget.org" --api-key ${{ secrets.NUGET_KEY }} 49 | -------------------------------------------------------------------------------- /test/DynamicExpresso.UnitTest/IdentifiersTest.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using NUnit.Framework; 3 | 4 | namespace DynamicExpresso.UnitTest 5 | { 6 | [TestFixture] 7 | public class IdentifiersTest 8 | { 9 | class Customer 10 | { 11 | public string Name { get; set; } 12 | 13 | public string GetName() 14 | { 15 | return Name; 16 | } 17 | } 18 | 19 | [Test] 20 | public void Default_identifiers_are_saved_inside_the_interpreter() 21 | { 22 | var target = new Interpreter(); 23 | 24 | Assert.That(target.Identifiers.Any(p => p.Name == "true"), Is.True); 25 | Assert.That(target.Identifiers.Any(p => p.Name == "false"), Is.True); 26 | Assert.That(target.Identifiers.Any(p => p.Name == "null"), Is.True); 27 | } 28 | 29 | [Test] 30 | public void Registered_custom_identifiers_are_saved_inside_the_interpreter() 31 | { 32 | var target = new Interpreter(); 33 | 34 | target.SetVariable("x", null); 35 | 36 | Assert.That(target.Identifiers.Any(p => p.Name == "x"), Is.True); 37 | } 38 | 39 | [Test] 40 | public void Getting_the_list_of_used_identifiers() 41 | { 42 | var target = new Interpreter() 43 | .SetVariable("x", 23); 44 | 45 | var lambda = target.Parse("x > a || true == b", new Parameter("a", 1), new Parameter("b", false)); 46 | 47 | Assert.That(lambda.Identifiers.Count(), Is.EqualTo(2)); 48 | Assert.That(lambda.Identifiers.ElementAt(0).Name, Is.EqualTo("x")); 49 | Assert.That(lambda.Identifiers.ElementAt(1).Name, Is.EqualTo("true")); 50 | } 51 | 52 | [Test] 53 | public void This_identifier_variable() 54 | { 55 | const string Name = "John"; 56 | 57 | var interpreter = new Interpreter(); 58 | 59 | interpreter.SetVariable("this", new Customer {Name = Name}); 60 | 61 | Assert.That(interpreter.Eval("this.Name"), Is.EqualTo(Name)); 62 | Assert.That(interpreter.Eval("this.GetName()"), Is.EqualTo(Name)); 63 | 64 | Assert.That(interpreter.Eval("Name"), Is.EqualTo(Name)); 65 | Assert.That(interpreter.Eval("GetName()"), Is.EqualTo(Name)); 66 | } 67 | 68 | [Test] 69 | public void This_identifier_parameter() 70 | { 71 | const string Name = "John"; 72 | 73 | var context = new Customer {Name = Name}; 74 | var parameter = new Parameter("this", context.GetType()); 75 | var interpreter = new Interpreter(); 76 | 77 | Assert.That(interpreter.Parse("this.Name", parameter).Invoke(context), Is.EqualTo(Name)); 78 | Assert.That(interpreter.Parse("this.GetName()", parameter).Invoke(context), Is.EqualTo(Name)); 79 | 80 | Assert.That(interpreter.Parse("Name", parameter).Invoke(context), Is.EqualTo(Name)); 81 | Assert.That(interpreter.Parse("GetName()", parameter).Invoke(context), Is.EqualTo(Name)); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/DynamicExpresso.Core/Parsing/InterpreterExpression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using DynamicExpresso.Exceptions; 6 | using DynamicExpresso.Reflection; 7 | using DynamicExpresso.Resources; 8 | 9 | namespace DynamicExpresso.Parsing 10 | { 11 | internal class InterpreterExpression : Expression 12 | { 13 | private readonly Interpreter _interpreter; 14 | private readonly string _expressionText; 15 | private readonly IList _parameters; 16 | private Type _type; 17 | 18 | public InterpreterExpression(ParserArguments parserArguments, string expressionText, params ParameterWithPosition[] parameters) 19 | { 20 | var settings = parserArguments.Settings.Clone(); 21 | _interpreter = new Interpreter(settings); 22 | _expressionText = expressionText; 23 | _parameters = parameters; 24 | 25 | // Take the parent expression's parameters and set them as an identifier that 26 | // can be accessed by any lower call 27 | // note: this doesn't impact the initial settings, because they're cloned 28 | foreach (var dp in parserArguments.DeclaredParameters) 29 | { 30 | // Have to mark the parameter as "Used" otherwise we can get a compilation error. 31 | parserArguments.TryGetParameters(dp.Name, out var pe); 32 | _interpreter.SetIdentifier(new Identifier(dp.Name, pe)); 33 | } 34 | 35 | foreach (var myParameter in parameters) 36 | { 37 | if (settings.Identifiers.ContainsKey(myParameter.Name)) 38 | { 39 | throw new ParseException(string.Format(ErrorMessages.DuplicateLocalParameterDeclaration, myParameter.Name), myParameter.Position); 40 | } 41 | } 42 | 43 | // prior to evaluation, we don't know the generic arguments types 44 | _type = ReflectionExtensions.GetFuncType(parameters.Length); 45 | } 46 | 47 | public IList Parameters 48 | { 49 | get { return _parameters; } 50 | } 51 | 52 | public override Type Type 53 | { 54 | get { return _type; } 55 | } 56 | 57 | internal LambdaExpression EvalAs(Type delegateType) 58 | { 59 | if (!IsCompatibleWithDelegate(delegateType)) 60 | return null; 61 | 62 | var lambdaExpr = _interpreter.ParseAsExpression(delegateType, _expressionText, _parameters.Select(p => p.Name).ToArray()); 63 | _type = lambdaExpr.Type; 64 | return lambdaExpr; 65 | } 66 | 67 | internal bool IsCompatibleWithDelegate(Type target) 68 | { 69 | if (!target.IsGenericType || target.BaseType != typeof(MulticastDelegate)) 70 | return false; 71 | 72 | var genericTypeDefinition = target.GetGenericTypeDefinition(); 73 | return genericTypeDefinition == ReflectionExtensions.GetFuncType(_parameters.Count) 74 | || genericTypeDefinition == ReflectionExtensions.GetActionType(_parameters.Count); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/DynamicExpresso.Core/LanguageConstants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using DynamicExpresso.Parsing; 4 | 5 | namespace DynamicExpresso 6 | { 7 | public static class LanguageConstants 8 | { 9 | public const string This = "this"; 10 | 11 | public static readonly ReferenceType[] PrimitiveTypes = { 12 | new ReferenceType(typeof(object)), 13 | new ReferenceType(typeof(bool)), 14 | new ReferenceType(typeof(char)), 15 | new ReferenceType(typeof(string)), 16 | new ReferenceType(typeof(sbyte)), 17 | new ReferenceType(typeof(byte)), 18 | new ReferenceType(typeof(short)), 19 | new ReferenceType(typeof(ushort)), 20 | new ReferenceType(typeof(int)), 21 | new ReferenceType(typeof(uint)), 22 | new ReferenceType(typeof(long)), 23 | new ReferenceType(typeof(ulong)), 24 | new ReferenceType(typeof(float)), 25 | new ReferenceType(typeof(double)), 26 | new ReferenceType(typeof(decimal)), 27 | new ReferenceType(typeof(DateTime)), 28 | new ReferenceType(typeof(TimeSpan)), 29 | new ReferenceType(typeof(Guid)) 30 | }; 31 | 32 | /// 33 | /// Primitive types alias (string, int, ...) 34 | /// 35 | public static readonly ReferenceType[] CSharpPrimitiveTypes = { 36 | new ReferenceType("object", typeof(object)), 37 | new ReferenceType("string", typeof(string)), 38 | new ReferenceType("char", typeof(char)), 39 | new ReferenceType("bool", typeof(bool)), 40 | new ReferenceType("sbyte", typeof(sbyte)), 41 | new ReferenceType("byte", typeof(byte)), 42 | new ReferenceType("short", typeof(short)), 43 | new ReferenceType("ushort", typeof(ushort)), 44 | new ReferenceType("int", typeof(int)), 45 | new ReferenceType("uint", typeof(uint)), 46 | new ReferenceType("long", typeof(long)), 47 | new ReferenceType("ulong", typeof(ulong)), 48 | new ReferenceType("float", typeof(float)), 49 | new ReferenceType("double", typeof(double)), 50 | new ReferenceType("decimal", typeof(decimal)) 51 | }; 52 | 53 | /// 54 | /// Common .NET Types (Math, Convert, Enumerable) 55 | /// 56 | public static readonly ReferenceType[] CommonTypes = { 57 | new ReferenceType(typeof(Math)), 58 | new ReferenceType(typeof(Convert)), 59 | new ReferenceType(typeof(System.Linq.Enumerable)) 60 | }; 61 | 62 | /// 63 | /// true, false, null 64 | /// 65 | public static readonly Identifier[] Literals = { 66 | new Identifier("true", Expression.Constant(true)), 67 | new Identifier("false", Expression.Constant(false)), 68 | new Identifier("null", ParserConstants.NullLiteralExpression) 69 | }; 70 | 71 | [Obsolete("Use ReservedKeywords")] 72 | public static readonly string[] ReserverKeywords = ParserConstants.ReservedKeywords; 73 | 74 | public static readonly string[] ReservedKeywords = ParserConstants.ReservedKeywords; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | ################# 3 | ## Eclipse 4 | ################# 5 | 6 | *.pydevproject 7 | .project 8 | .metadata 9 | bin/ 10 | tmp/ 11 | *.tmp 12 | *.bak 13 | *.swp 14 | *~.nib 15 | local.properties 16 | .classpath 17 | .settings/ 18 | .loadpath 19 | 20 | # External tool builders 21 | .externalToolBuilders/ 22 | 23 | # Locally stored "Eclipse launch configurations" 24 | *.launch 25 | 26 | # CDT-specific 27 | .cproject 28 | 29 | # PDT-specific 30 | .buildpath 31 | 32 | 33 | ################# 34 | ## Visual Studio 35 | ################# 36 | 37 | ## Ignore Visual Studio temporary files, build results, and 38 | ## files generated by popular Visual Studio add-ons. 39 | 40 | # User-specific files 41 | *.suo 42 | *.user 43 | *.sln.docstates 44 | 45 | # Build results 46 | [Dd]ebug/ 47 | [Rr]elease/ 48 | *_i.c 49 | *_p.c 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.vspscc 64 | .builds 65 | *.dotCover 66 | 67 | .vs 68 | 69 | packages/ 70 | 71 | # Visual C++ cache files 72 | ipch/ 73 | *.aps 74 | *.ncb 75 | *.opensdf 76 | *.sdf 77 | 78 | # Visual Studio profiler 79 | *.psess 80 | *.vsp 81 | 82 | # ReSharper is a .NET coding add-in 83 | _ReSharper* 84 | 85 | # Installshield output folder 86 | [Ee]xpress 87 | 88 | # DocProject is a documentation generator add-in 89 | DocProject/buildhelp/ 90 | DocProject/Help/*.HxT 91 | DocProject/Help/*.HxC 92 | DocProject/Help/*.hhc 93 | DocProject/Help/*.hhk 94 | DocProject/Help/*.hhp 95 | DocProject/Help/Html2 96 | DocProject/Help/html 97 | 98 | # Click-Once directory 99 | publish 100 | 101 | # Others 102 | [Bb]in 103 | [Oo]bj 104 | sql 105 | TestResults 106 | *.Cache 107 | ClientBin 108 | stylecop.* 109 | ~$* 110 | *.dbmdl 111 | Generated_Code #added for RIA/Silverlight projects 112 | 113 | # Backup & report files from converting an old project file to a newer 114 | # Visual Studio version. Backup files are not needed, because we have git ;-) 115 | _UpgradeReport_Files/ 116 | Backup*/ 117 | UpgradeLog*.XML 118 | 119 | 120 | 121 | ############ 122 | ## Windows 123 | ############ 124 | 125 | # Windows image file caches 126 | Thumbs.db 127 | 128 | # Folder config file 129 | Desktop.ini 130 | 131 | 132 | ############# 133 | ## Python 134 | ############# 135 | 136 | *.py[co] 137 | 138 | # Packages 139 | *.egg 140 | *.egg-info 141 | dist 142 | build 143 | eggs 144 | parts 145 | bin 146 | var 147 | sdist 148 | develop-eggs 149 | .installed.cfg 150 | 151 | # Installer logs 152 | pip-log.txt 153 | 154 | # Unit test / coverage reports 155 | .coverage 156 | .tox 157 | 158 | #Translations 159 | *.mo 160 | 161 | #Mr Developer 162 | .mr.developer.cfg 163 | 164 | # Mac crap 165 | .DS_Store 166 | 167 | #Resharper 168 | _ReSharper* 169 | 170 | #Stylecop 171 | StyleCop.Cache 172 | 173 | #Other 174 | *.vsmdi 175 | *.InstallLog 176 | 177 | # NUnit console output 178 | TestResult.xml 179 | -------------------------------------------------------------------------------- /src/DynamicExpresso.Core/Parsing/ParserSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | 5 | namespace DynamicExpresso.Parsing 6 | { 7 | internal class ParserSettings 8 | { 9 | private readonly Dictionary _identifiers; 10 | private readonly Dictionary _knownTypes; 11 | private readonly HashSet _extensionMethods; 12 | 13 | public ParserSettings(bool caseInsensitive, bool lateBindObject) 14 | { 15 | CaseInsensitive = caseInsensitive; 16 | 17 | LateBindObject = lateBindObject; 18 | 19 | KeyComparer = CaseInsensitive ? StringComparer.InvariantCultureIgnoreCase : StringComparer.InvariantCulture; 20 | 21 | KeyComparison = CaseInsensitive ? StringComparison.InvariantCultureIgnoreCase : StringComparison.InvariantCulture; 22 | 23 | _identifiers = new Dictionary(KeyComparer); 24 | 25 | _knownTypes = new Dictionary(KeyComparer); 26 | 27 | _extensionMethods = new HashSet(); 28 | 29 | AssignmentOperators = AssignmentOperators.All; 30 | 31 | DefaultNumberType = DefaultNumberType.Default; 32 | 33 | LambdaExpressions = false; 34 | } 35 | 36 | private ParserSettings(ParserSettings other) : this(other.CaseInsensitive, other.LateBindObject) 37 | { 38 | _knownTypes = new Dictionary(other._knownTypes, other._knownTypes.Comparer); 39 | _identifiers = new Dictionary(other._identifiers, other._identifiers.Comparer); 40 | _extensionMethods = new HashSet(other._extensionMethods); 41 | 42 | AssignmentOperators = other.AssignmentOperators; 43 | DefaultNumberType = other.DefaultNumberType; 44 | LambdaExpressions = other.LambdaExpressions; 45 | } 46 | 47 | /// 48 | /// Creates a deep copy of the current settings, so that the identifiers/types/methods can be changed 49 | /// without impacting the existing settings. 50 | /// 51 | public ParserSettings Clone() 52 | { 53 | return new ParserSettings(this); 54 | } 55 | 56 | public IDictionary KnownTypes 57 | { 58 | get { return _knownTypes; } 59 | } 60 | 61 | public IDictionary Identifiers 62 | { 63 | get { return _identifiers; } 64 | } 65 | 66 | public HashSet ExtensionMethods 67 | { 68 | get { return _extensionMethods; } 69 | } 70 | 71 | public bool CaseInsensitive 72 | { 73 | get; 74 | } 75 | 76 | public bool LateBindObject 77 | { 78 | get; 79 | } 80 | 81 | public StringComparison KeyComparison 82 | { 83 | get; 84 | } 85 | 86 | public IEqualityComparer KeyComparer 87 | { 88 | get; 89 | } 90 | 91 | public DefaultNumberType DefaultNumberType 92 | { 93 | get; 94 | set; 95 | } 96 | 97 | public AssignmentOperators AssignmentOperators 98 | { 99 | get; 100 | set; 101 | } 102 | 103 | public bool LambdaExpressions 104 | { 105 | get; 106 | set; 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /test/DynamicExpresso.UnitTest/GenerateDelegatesTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using DynamicExpresso.Exceptions; 3 | using NUnit.Framework; 4 | 5 | namespace DynamicExpresso.UnitTest 6 | { 7 | [TestFixture] 8 | public class GenerateDelegatesTest 9 | { 10 | [Test] 11 | public void Parse_To_a_Delegate() 12 | { 13 | var target = new Interpreter(); 14 | 15 | var func = target.ParseAsDelegate>("Math.Pow(x, y) + 5", "x", "y"); 16 | 17 | Assert.That(func(10, 2), Is.EqualTo(Math.Pow(10, 2) + 5)); 18 | 19 | func = target.ParseAsDelegate>("Math.Pow(x, y) + .5", "x", "y"); 20 | Assert.That(func(10, 2), Is.EqualTo(Math.Pow(10, 2) + .5)); 21 | } 22 | 23 | [Test] 24 | public void Parse_To_a_Delegate_With_No_Parameters() 25 | { 26 | var target = new Interpreter(); 27 | 28 | var func = target.ParseAsDelegate>("50"); 29 | 30 | Assert.That(func(), Is.EqualTo(50)); 31 | } 32 | 33 | [Test] 34 | public void Parse_To_a_Delegate_With_One_Parameter() 35 | { 36 | var target = new Interpreter(); 37 | 38 | var func = target.ParseAsDelegate>("arg.Length"); 39 | 40 | Assert.That(func("ciao"), Is.EqualTo(4)); 41 | Assert.That(func("123456879"), Is.EqualTo(9)); 42 | } 43 | 44 | [Test] 45 | public void Parse_To_a_Delegate_With_One_Parameter_With_Custom_Name() 46 | { 47 | var target = new Interpreter(); 48 | 49 | var argumentName = "val"; // if not specified the delegate parameter is used which is "arg" 50 | var func = target.ParseAsDelegate>("val.Length", argumentName); 51 | 52 | Assert.That(func("ciao"), Is.EqualTo(4)); 53 | Assert.That(func("123456879"), Is.EqualTo(9)); 54 | } 55 | 56 | [Test] 57 | public void Parse_To_a_Delegate_With_Two_Parameters() 58 | { 59 | var target = new Interpreter(); 60 | 61 | var func = target.ParseAsDelegate>("arg1 * arg2"); 62 | 63 | Assert.That(func(3, 2), Is.EqualTo(6)); 64 | Assert.That(func(5, 10), Is.EqualTo(50)); 65 | } 66 | 67 | [Test] 68 | public void Parse_To_a_Delegate_With_Two_Parameters_With_Custom_Name() 69 | { 70 | var target = new Interpreter(); 71 | 72 | var argumentNames = new [] { "x", "y" }; 73 | var func = target.ParseAsDelegate>("x * y", argumentNames); 74 | 75 | Assert.That(func(3, 2), Is.EqualTo(6)); 76 | Assert.That(func(5, 10), Is.EqualTo(50)); 77 | } 78 | 79 | [Test] 80 | public void Parse_To_a_Custom_Delegate() 81 | { 82 | var target = new Interpreter(); 83 | 84 | var func = target.ParseAsDelegate("x + y.Length"); 85 | 86 | Assert.That(func(3, "ciao"), Is.EqualTo(7)); 87 | Assert.That(func(5, "mondo"), Is.EqualTo(10)); 88 | } 89 | 90 | private delegate int MyCustomDelegate(int x, string y); 91 | 92 | [Test] 93 | public void Return_Type_Mismatch_Cause_An_Exception() 94 | { 95 | var target = new Interpreter(); 96 | 97 | // expected a double but I return a string 98 | Assert.Throws(() => target.ParseAsDelegate>("\"ciao\"")); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /test/DynamicExpresso.UnitTest/StaticTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NUnit.Framework; 3 | 4 | namespace DynamicExpresso.UnitTest 5 | { 6 | [TestFixture] 7 | public class StaticTest 8 | { 9 | [Test] 10 | public void Static_Properties_of_Base_Types() 11 | { 12 | var target = new Interpreter(); 13 | 14 | Assert.That(target.Eval("Int32.MaxValue"), Is.EqualTo(int.MaxValue)); 15 | Assert.That(target.Eval("Double.MaxValue"), Is.EqualTo(double.MaxValue)); 16 | Assert.That(target.Eval("DateTime.MaxValue"), Is.EqualTo(DateTime.MaxValue)); 17 | Assert.That(target.Eval("DateTime.Today"), Is.EqualTo(DateTime.Today)); 18 | Assert.That(target.Eval("String.Empty"), Is.EqualTo(string.Empty)); 19 | Assert.That(target.Eval("Boolean.FalseString"), Is.EqualTo(bool.FalseString)); 20 | Assert.That(target.Eval("TimeSpan.TicksPerMillisecond"), Is.EqualTo(TimeSpan.TicksPerMillisecond)); 21 | Assert.That(target.Eval("Guid.Empty"), Is.EqualTo(Guid.Empty)); 22 | } 23 | 24 | [Test] 25 | public void Static_Methods_of_Base_Types() 26 | { 27 | var target = new Interpreter(); 28 | 29 | Assert.That(target.Eval("TimeSpan.FromMilliseconds(2000.49)"), Is.EqualTo(TimeSpan.FromMilliseconds(2000.49))); 30 | Assert.That(target.Eval("DateTime.DaysInMonth(2094, 11)"), Is.EqualTo(DateTime.DaysInMonth(2094, 11))); 31 | } 32 | 33 | [Test] 34 | public void Math_Class() 35 | { 36 | var target = new Interpreter(); 37 | 38 | Assert.That(target.Eval("Math.Pow(3, 4)"), Is.EqualTo(Math.Pow(3, 4))); 39 | Assert.That(target.Eval("Math.Sin(30.234)"), Is.EqualTo(Math.Sin(30.234))); 40 | } 41 | 42 | [Test] 43 | public void Convert_Class() 44 | { 45 | var target = new Interpreter(); 46 | 47 | Assert.That(target.Eval("Convert.ToString(3)"), Is.EqualTo(Convert.ToString(3))); 48 | Assert.That(target.Eval("Convert.ToInt16(\"23\")"), Is.EqualTo(Convert.ToInt16("23"))); 49 | } 50 | 51 | [Test] 52 | public void CSharp_Primitive_Type_Keywords() 53 | { 54 | var target = new Interpreter(); 55 | 56 | Assert.That(target.Eval("int.MaxValue"), Is.EqualTo(int.MaxValue)); 57 | Assert.That(target.Eval("double.MaxValue"), Is.EqualTo(double.MaxValue)); 58 | Assert.That(target.Eval("string.Empty"), Is.EqualTo(string.Empty)); 59 | Assert.That(target.Eval("bool.FalseString"), Is.EqualTo(bool.FalseString)); 60 | Assert.That(target.Eval("char.MinValue"), Is.EqualTo(char.MinValue)); 61 | Assert.That(target.Eval("byte.MinValue"), Is.EqualTo(byte.MinValue)); 62 | } 63 | 64 | [Test] 65 | public void Static_Properties_And_Methods_Of_Custom_Types() 66 | { 67 | var target = new Interpreter() 68 | .Reference(typeof(Uri)) 69 | .Reference(typeof(MyTestService)); 70 | 71 | Assert.That(target.Eval("Uri.UriSchemeHttp"), Is.EqualTo(Uri.UriSchemeHttp)); 72 | Assert.That(target.Eval("MyTestService.MyStaticMethod()"), Is.EqualTo(MyTestService.MyStaticMethod())); 73 | } 74 | 75 | [Test] 76 | public void Type_Related_Static_Methods() 77 | { 78 | var target = new Interpreter() 79 | .Reference(typeof(Type)); 80 | 81 | Assert.That(target.Eval("Type.GetType(\"System.Globalization.CultureInfo\")"), Is.EqualTo(Type.GetType("System.Globalization.CultureInfo"))); 82 | Assert.That(target.Eval("DateTime.Now.GetType()"), Is.EqualTo(DateTime.Now.GetType())); 83 | } 84 | 85 | private class MyTestService 86 | { 87 | public static int MyStaticMethod() 88 | { 89 | return 23; 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /test/DynamicExpresso.UnitTest/ExpressionTypeTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NUnit.Framework; 3 | 4 | namespace DynamicExpresso.UnitTest 5 | { 6 | [TestFixture] 7 | public class ExpressionTypeTest 8 | { 9 | [Test] 10 | public void If_no_expression_type_is_specified_the_return_type_is_inferred() 11 | { 12 | var target = new Interpreter(); 13 | 14 | Assert.That(target.Parse("\"ciao\"").ReturnType, Is.EqualTo(typeof(string))); 15 | Assert.That(target.Parse("45").ReturnType, Is.EqualTo(typeof(int))); 16 | Assert.That(target.Parse("45.4").ReturnType, Is.EqualTo(typeof(double))); 17 | Assert.That(target.Parse("null").ReturnType, Is.EqualTo(typeof(object))); 18 | } 19 | 20 | [Test] 21 | public void If_expression_type_doesn_t_match_a_conversion_is_performed_when_possible() 22 | { 23 | var target = new Interpreter(); 24 | var expressionType = typeof(double); 25 | 26 | var lambda = target.Parse("213", expressionType); 27 | 28 | Assert.That(lambda.ReturnType, Is.EqualTo(expressionType)); 29 | Assert.That(lambda.Invoke(), Is.EqualTo((double)213)); 30 | } 31 | 32 | [Test] 33 | public void If_expression_type_doesn_t_match_a_conversion_is_performed_eventually_loosing_precision() 34 | { 35 | var target = new Interpreter(); 36 | var expressionType = typeof(int); 37 | 38 | var lambda = target.Parse("213.46", expressionType); 39 | 40 | Assert.That(lambda.ReturnType, Is.EqualTo(expressionType)); 41 | Assert.That(lambda.Invoke(), Is.EqualTo((int)213.46)); 42 | } 43 | 44 | [Test] 45 | public void Can_convert_a_null_expression_to_any_reference_type() 46 | { 47 | var target = new Interpreter(); 48 | 49 | var lambda = target.Parse("null", typeof(string)); 50 | Assert.That(lambda.ReturnType, Is.EqualTo(typeof(string))); 51 | Assert.That(lambda.Invoke(), Is.Null); 52 | 53 | lambda = target.Parse("null", typeof(TestReferenceType)); 54 | Assert.That(lambda.ReturnType, Is.EqualTo(typeof(TestReferenceType))); 55 | Assert.That(lambda.Invoke(), Is.Null); 56 | } 57 | 58 | [Test] 59 | public void Can_convert_a_null_expression_to_any_nullable_type() 60 | { 61 | var target = new Interpreter(); 62 | 63 | var lambda = target.Parse("null", typeof(int?)); 64 | Assert.That(lambda.ReturnType, Is.EqualTo(typeof(int?))); 65 | Assert.That(lambda.Invoke(), Is.Null); 66 | 67 | lambda = target.Parse("null", typeof(DateTime?)); 68 | Assert.That(lambda.ReturnType, Is.EqualTo(typeof(DateTime?))); 69 | Assert.That(lambda.Invoke(), Is.Null); 70 | } 71 | 72 | [Test] 73 | public void A_nullable_type_can_be_a_value_or_null() 74 | { 75 | var target = new Interpreter(); 76 | 77 | var lambda = target.Parse("null", typeof(int?)); 78 | Assert.That(lambda.ReturnType, Is.EqualTo(typeof(int?))); 79 | Assert.That(lambda.Invoke(), Is.Null); 80 | 81 | lambda = target.Parse("4651", typeof(int?)); 82 | Assert.That(lambda.ReturnType, Is.EqualTo(typeof(int?))); 83 | Assert.That(lambda.Invoke(), Is.EqualTo(4651)); 84 | } 85 | 86 | [Test] 87 | public void Typed_eval() 88 | { 89 | var target = new Interpreter(); 90 | 91 | double result = target.Eval("Math.Pow(x, y) + 5", 92 | new Parameter("x", typeof(double), 10), 93 | new Parameter("y", typeof(double), 2)); 94 | 95 | Assert.That(result, Is.EqualTo(Math.Pow(10, 2) + 5)); 96 | } 97 | 98 | private class TestReferenceType { }; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /test/DynamicExpresso.UnitTest/ThreadingTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using NUnit.Framework; 8 | 9 | namespace DynamicExpresso.UnitTest 10 | { 11 | [TestFixture] 12 | public class ThreadingTest 13 | { 14 | private static void DynamicExpresso_Interpreter_Eval1() 15 | { 16 | var parser = new Interpreter(); 17 | parser.SetVariable("C0", 5); 18 | parser.SetVariable("x", 50); 19 | Assert.That(parser.Identifiers.First(i => i.Name == "C0").Expression.ToString(), Is.EqualTo("5")); 20 | Assert.That(parser.Identifiers.First(i => i.Name == "x").Expression.ToString(), Is.EqualTo("50")); 21 | 22 | var result = parser.Eval("x*C0"); 23 | Assert.That(result, Is.EqualTo(250d)); 24 | } 25 | 26 | private static void DynamicExpresso_Interpreter_Eval2() 27 | { 28 | var parser = new Interpreter(); 29 | parser.SetVariable("C0", 5); 30 | parser.SetVariable("y", 250); 31 | Assert.That(parser.Identifiers.First(i => i.Name == "C0").Expression.ToString(), Is.EqualTo("5")); 32 | Assert.That(parser.Identifiers.First(i => i.Name == "y").Expression.ToString(), Is.EqualTo("250")); 33 | 34 | var result = parser.Eval("y/C0"); 35 | Assert.That(result, Is.EqualTo(50d)); 36 | } 37 | 38 | private static void DynamicExpresso_Interpreter_Eval_Sequence() 39 | { 40 | for (var i = 0; i < 10; i++) 41 | { 42 | DynamicExpresso_Interpreter_Eval1(); 43 | DynamicExpresso_Interpreter_Eval2(); 44 | } 45 | } 46 | 47 | [Test] 48 | public async Task DynamicExpresso_Interpreter_Eval_Threading() 49 | { 50 | Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; 51 | 52 | const int NumTasks = 5; 53 | 54 | var tasksToRun = new List(); 55 | for (var i = 0; i < NumTasks; i++) 56 | { 57 | var taskToRunLater = new Task(DynamicExpresso_Interpreter_Eval_Sequence); 58 | tasksToRun.Add(taskToRunLater); 59 | } 60 | 61 | foreach (var taskToRunLater in tasksToRun) 62 | { 63 | taskToRunLater.Start(); 64 | } 65 | 66 | foreach (var taskToRunLater in tasksToRun) 67 | { 68 | await taskToRunLater; 69 | } 70 | } 71 | 72 | [Test] 73 | public void Should_Pass_Parallel_Eval() 74 | { 75 | var target = new Interpreter(); 76 | var conds = Enumerable.Repeat("Country != \"France\"", 10); 77 | var parameters = new[] { new Parameter("Country", "Italy") }; 78 | Parallel.ForEach(conds, exp => 79 | { 80 | Assert.That(target.Eval(exp, parameters), Is.True); 81 | }); 82 | } 83 | 84 | [Test] 85 | public void Should_Fail_Parallel_Eval() 86 | { 87 | var target = new Interpreter(); 88 | var conds = Enumerable.Repeat("Country != \"France\"", 10); 89 | 90 | Parallel.ForEach(conds, exp => 91 | { 92 | var parameters = new[] { new Parameter("Country", "Italy") }; 93 | Assert.That(target.Eval(exp, parameters), Is.True); 94 | }); 95 | } 96 | 97 | [Test] 98 | public void Should_Pass_Parallel_Invoke_MethodGroup() 99 | { 100 | var interpreter = new Interpreter(); 101 | 102 | Func, int> minFunc = Enumerable.Min; 103 | interpreter.SetFunction("min", minFunc); 104 | 105 | Parallel.ForEach(Enumerable.Range(1, 25), i => 106 | { 107 | var arguments = new[] { i, i + 1, i + 2 }; 108 | Assert.That(interpreter.Eval("min(x)", new Parameter("x", arguments)), Is.EqualTo(i)); 109 | }); 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /test/DynamicExpresso.UnitTest/DefaultOperatorTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace DynamicExpresso.UnitTest 4 | { 5 | [TestFixture] 6 | public class DefaultOperatorTest 7 | { 8 | [Test] 9 | public void Default_value_type() 10 | { 11 | var target = new Interpreter(); 12 | 13 | Assert.That(target.Eval("default(bool)"), Is.EqualTo(default(bool))); 14 | Assert.That(target.Eval("default(char)"), Is.EqualTo(default(char))); 15 | Assert.That(target.Eval("default(sbyte)"), Is.EqualTo(default(sbyte))); 16 | Assert.That(target.Eval("default(byte)"), Is.EqualTo(default(byte))); 17 | Assert.That(target.Eval("default(short)"), Is.EqualTo(default(short))); 18 | Assert.That(target.Eval("default(ushort)"), Is.EqualTo(default(ushort))); 19 | Assert.That(target.Eval("default(int)"), Is.EqualTo(default(int))); 20 | Assert.That(target.Eval("default(uint)"), Is.EqualTo(default(uint))); 21 | Assert.That(target.Eval("default(long)"), Is.EqualTo(default(long))); 22 | Assert.That(target.Eval("default(ulong)"), Is.EqualTo(default(ulong))); 23 | Assert.That(target.Eval("default(float)"), Is.EqualTo(default(float))); 24 | Assert.That(target.Eval("default(double)"), Is.EqualTo(default(double))); 25 | Assert.That(target.Eval("default(decimal)"), Is.EqualTo(default(decimal))); 26 | Assert.That(target.Eval("default(DateTime)"), Is.EqualTo(default(System.DateTime))); 27 | Assert.That(target.Eval("default(TimeSpan)"), Is.EqualTo(default(System.TimeSpan))); 28 | Assert.That(target.Eval("default(Guid)"), Is.EqualTo(default(System.Guid))); 29 | 30 | Assert.That(target.Eval("default(bool)").GetType(), Is.EqualTo(typeof(bool))); 31 | Assert.That(target.Eval("default(char)").GetType(), Is.EqualTo(typeof(char))); 32 | Assert.That(target.Eval("default(sbyte)").GetType(), Is.EqualTo(typeof(sbyte))); 33 | Assert.That(target.Eval("default(byte)").GetType(), Is.EqualTo(typeof(byte))); 34 | Assert.That(target.Eval("default(short)").GetType(), Is.EqualTo(typeof(short))); 35 | Assert.That(target.Eval("default(ushort)").GetType(), Is.EqualTo(typeof(ushort))); 36 | Assert.That(target.Eval("default(int)").GetType(), Is.EqualTo(typeof(int))); 37 | Assert.That(target.Eval("default(uint)").GetType(), Is.EqualTo(typeof(uint))); 38 | Assert.That(target.Eval("default(long)").GetType(), Is.EqualTo(typeof(long))); 39 | Assert.That(target.Eval("default(ulong)").GetType(), Is.EqualTo(typeof(ulong))); 40 | Assert.That(target.Eval("default(float)").GetType(), Is.EqualTo(typeof(float))); 41 | Assert.That(target.Eval("default(double)").GetType(), Is.EqualTo(typeof(double))); 42 | Assert.That(target.Eval("default(decimal)").GetType(), Is.EqualTo(typeof(decimal))); 43 | Assert.That(target.Eval("default(DateTime)").GetType(), Is.EqualTo(typeof(System.DateTime))); 44 | Assert.That(target.Eval("default(TimeSpan)").GetType(), Is.EqualTo(typeof(System.TimeSpan))); 45 | Assert.That(target.Eval("default(Guid)").GetType(), Is.EqualTo(typeof(System.Guid))); 46 | } 47 | 48 | [Test] 49 | public void Default_reference_type() 50 | { 51 | var target = new Interpreter(); 52 | 53 | Assert.That(target.Eval("default(object)"), Is.EqualTo(default(object))); 54 | Assert.That(target.Eval("default(string)"), Is.EqualTo(default(string))); 55 | } 56 | 57 | [Test] 58 | public void Default_nullable_type() 59 | { 60 | var target = new Interpreter(); 61 | 62 | Assert.That(target.Eval("default(int?)"), Is.EqualTo(default(int?))); 63 | Assert.That(target.Eval("default(double?)"), Is.EqualTo(default(double?))); 64 | Assert.That(target.Eval("default(DateTime?)"), Is.EqualTo(default(System.DateTime?))); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/DynamicExpresso.UnitTest/GenerateLambdaTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NUnit.Framework; 3 | using System.Linq.Expressions; 4 | 5 | namespace DynamicExpresso.UnitTest 6 | { 7 | [TestFixture] 8 | public class GenerateLambdaTest 9 | { 10 | [Test] 11 | public void Parse_as_LambdaExpression() 12 | { 13 | var target = new Interpreter(); 14 | 15 | Expression> lambdaExpression = target.ParseAsExpression>("arg + 5"); 16 | 17 | Assert.That(lambdaExpression.Compile()(10), Is.EqualTo(15)); 18 | } 19 | 20 | [Test] 21 | public void Parse_as_LambdaExpression_with_parameter() 22 | { 23 | var target = new Interpreter(); 24 | 25 | Expression> lambdaExpression = target.ParseAsExpression>("arg + 5"); 26 | 27 | Assert.That(lambdaExpression.Compile()(10), Is.EqualTo(15)); 28 | 29 | lambdaExpression = target.ParseAsExpression>("arg + .5"); 30 | Assert.That(lambdaExpression.Compile()(10), Is.EqualTo(10.5)); 31 | } 32 | 33 | [Test] 34 | public void Parse_To_a_Delegate_With_Two_Parameters() 35 | { 36 | var target = new Interpreter(); 37 | 38 | var lambdaExpression = target.ParseAsExpression>("arg1 * arg2"); 39 | 40 | Assert.That(lambdaExpression.Compile()(3, 2), Is.EqualTo(6)); 41 | Assert.That(lambdaExpression.Compile()(5, 10), Is.EqualTo(50)); 42 | } 43 | 44 | [Test] 45 | public void Parse_To_a_Delegate_With_Two_Parameters_With_Custom_Name() 46 | { 47 | var target = new Interpreter(); 48 | 49 | var argumentNames = new string[] { "x", "y" }; 50 | var lambdaExpression = target.ParseAsExpression>("x * y", argumentNames); 51 | 52 | Assert.That(lambdaExpression.Compile()(3, 2), Is.EqualTo(6)); 53 | Assert.That(lambdaExpression.Compile()(5, 10), Is.EqualTo(50)); 54 | } 55 | 56 | [Test] 57 | public void Generate_a_LambdaExpression_From_Lambda() 58 | { 59 | var target = new Interpreter(); 60 | 61 | var lambda = target.Parse("Math.Pow(x, y) + 5", 62 | new Parameter("x", typeof(double)), 63 | new Parameter("y", typeof(double)) 64 | ); 65 | 66 | Expression> lambdaExpression = lambda.LambdaExpression>(); 67 | 68 | Assert.That(lambdaExpression.Compile()(10, 2), Is.EqualTo(Math.Pow(10, 2) + 5)); 69 | } 70 | 71 | [Test] 72 | public void Cannot_Generate_a_LambdaExpression_From_Lambda_with_parameters_count_mismatch() 73 | { 74 | var target = new Interpreter(); 75 | 76 | // Func delegate has 2 inputs, I just use one 77 | 78 | var lambda = target.Parse("x + 5", 79 | new Parameter("x", typeof(double)) 80 | ); 81 | 82 | Assert.Throws(() => lambda.LambdaExpression>()); 83 | } 84 | 85 | [Test] 86 | public void Cannot_Generate_a_LambdaExpression_From_Lambda_with_parameters_type_mismatch() 87 | { 88 | var target = new Interpreter(); 89 | 90 | // Func delegate takes a string, I pass a double 91 | 92 | var lambda = target.Parse("x + 5", 93 | new Parameter("x", typeof(double)) 94 | ); 95 | 96 | Assert.Throws(() => lambda.LambdaExpression>()); 97 | } 98 | 99 | [Test] 100 | public void Cannot_generate_a_Lambda_with_return_type_mismatch() 101 | { 102 | var target = new Interpreter(); 103 | 104 | // Func delegate returns a string, I return a double 105 | 106 | var lambda = target.Parse("x + 5", 107 | new Parameter("x", typeof(double)) 108 | ); 109 | 110 | Assert.Throws(() => lambda.LambdaExpression>()); 111 | } 112 | 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/DynamicExpresso.Core/ParserArguments.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using System.Text.RegularExpressions; 7 | using DynamicExpresso.Exceptions; 8 | using DynamicExpresso.Parsing; 9 | 10 | namespace DynamicExpresso 11 | { 12 | internal class ParserArguments 13 | { 14 | private readonly Dictionary _declaredParameters; 15 | 16 | private readonly HashSet _usedParameters = new HashSet(); 17 | private readonly HashSet _usedTypes = new HashSet(); 18 | private readonly HashSet _usedIdentifiers = new HashSet(); 19 | 20 | public ParserArguments( 21 | string expressionText, 22 | ParserSettings settings, 23 | Type expressionReturnType, 24 | IEnumerable declaredParameters 25 | ) 26 | { 27 | ExpressionText = expressionText; 28 | ExpressionReturnType = expressionReturnType; 29 | 30 | Settings = settings; 31 | _declaredParameters = new Dictionary(settings.KeyComparer); 32 | foreach (var pe in declaredParameters) 33 | { 34 | try 35 | { 36 | _declaredParameters.Add(pe.Name, pe); 37 | } 38 | catch (ArgumentException) 39 | { 40 | throw new DuplicateParameterException(pe.Name); 41 | } 42 | } 43 | } 44 | 45 | public ParserSettings Settings { get; private set; } 46 | public string ExpressionText { get; private set; } 47 | public Type ExpressionReturnType { get; private set; } 48 | public IEnumerable DeclaredParameters { get { return _declaredParameters.Values; } } 49 | 50 | public IEnumerable UsedParameters 51 | { 52 | get { return _usedParameters; } 53 | } 54 | 55 | public IEnumerable UsedTypes 56 | { 57 | get { return _usedTypes; } 58 | } 59 | 60 | public IEnumerable UsedIdentifiers 61 | { 62 | get { return _usedIdentifiers; } 63 | } 64 | 65 | public bool TryGetKnownType(string name, out Type type) 66 | { 67 | if (Settings.KnownTypes.TryGetValue(name, out var reference)) 68 | { 69 | _usedTypes.Add(reference); 70 | type = reference.Type; 71 | return true; 72 | } 73 | 74 | type = null; 75 | return false; 76 | } 77 | 78 | /// 79 | /// Returns true if the known types contain a generic type definition with the given name + any arity (e.g. name`1). 80 | /// 81 | internal bool HasKnownGenericTypeDefinition(string name) 82 | { 83 | var regex = new Regex("^" + name + "`\\d+$"); 84 | return Settings.KnownTypes.Values.Any(refType => regex.IsMatch(refType.Name) && refType.Type.IsGenericTypeDefinition); 85 | } 86 | 87 | public bool TryGetIdentifier(string name, out Expression expression) 88 | { 89 | if (Settings.Identifiers.TryGetValue(name, out var identifier)) 90 | { 91 | _usedIdentifiers.Add(identifier); 92 | expression = identifier.Expression; 93 | return true; 94 | } 95 | 96 | expression = null; 97 | return false; 98 | } 99 | 100 | /// 101 | /// Get the parameter and mark is as used. 102 | /// 103 | public bool TryGetParameters(string name, out ParameterExpression expression) 104 | { 105 | if (_declaredParameters.TryGetValue(name, out var parameter)) 106 | { 107 | _usedParameters.Add(parameter); 108 | expression = parameter.Expression; 109 | return true; 110 | } 111 | 112 | expression = null; 113 | return false; 114 | } 115 | 116 | public IEnumerable GetExtensionMethods(string methodName) 117 | { 118 | var comparer = Settings.KeyComparer; 119 | return Settings.ExtensionMethods.Where(p => comparer.Equals(p.Name, methodName)); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/DynamicExpresso.Core/Reflection/ReflectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | 6 | namespace DynamicExpresso.Reflection 7 | { 8 | internal static class ReflectionExtensions 9 | { 10 | public static readonly MethodInfo StringConcatMethod = GetStringConcatMethod(); 11 | public static readonly MethodInfo ObjectToStringMethod = GetObjectToStringMethod(); 12 | 13 | public static DelegateInfo GetDelegateInfo(Type delegateType, params string[] parametersNames) 14 | { 15 | var method = delegateType.GetMethod("Invoke"); 16 | if (method == null) 17 | throw new ArgumentException("The specified type is not a delegate"); 18 | 19 | var delegateParameters = method.GetParameters(); 20 | var parameters = new Parameter[delegateParameters.Length]; 21 | 22 | var useCustomNames = parametersNames != null && parametersNames.Length > 0; 23 | 24 | if (useCustomNames && parametersNames.Length != parameters.Length) 25 | throw new ArgumentException(string.Format("Provided parameters names doesn't match delegate parameters, {0} parameters expected.", parameters.Length)); 26 | 27 | for (var i = 0; i < parameters.Length; i++) 28 | { 29 | var paramName = useCustomNames ? parametersNames[i] : delegateParameters[i].Name; 30 | var paramType = delegateParameters[i].ParameterType; 31 | 32 | parameters[i] = new Parameter(paramName, paramType); 33 | } 34 | 35 | return new DelegateInfo(method.ReturnType, parameters); 36 | } 37 | 38 | public static IEnumerable GetExtensionMethods(Type type) 39 | { 40 | if (type.IsSealed && type.IsAbstract && !type.IsGenericType && !type.IsNested) 41 | { 42 | var query = from method in type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) 43 | where method.IsDefined(typeof(System.Runtime.CompilerServices.ExtensionAttribute), false) 44 | select method; 45 | return query; 46 | } 47 | 48 | return Enumerable.Empty(); 49 | } 50 | 51 | public class DelegateInfo 52 | { 53 | public DelegateInfo(Type returnType, Parameter[] parameters) 54 | { 55 | ReturnType = returnType; 56 | Parameters = parameters; 57 | } 58 | 59 | public Type ReturnType { get; private set; } 60 | public Parameter[] Parameters { get; private set; } 61 | } 62 | 63 | private static MethodInfo GetStringConcatMethod() 64 | { 65 | var methodInfo = typeof(string).GetMethod("Concat", new[] { typeof(string), typeof(string) }); 66 | if (methodInfo == null) 67 | { 68 | throw new Exception("String concat method not found"); 69 | } 70 | 71 | return methodInfo; 72 | } 73 | 74 | private static MethodInfo GetObjectToStringMethod() 75 | { 76 | var toStringMethod = typeof(object).GetMethod("ToString", Type.EmptyTypes); 77 | if (toStringMethod == null) 78 | { 79 | throw new Exception("ToString method not found"); 80 | } 81 | 82 | return toStringMethod; 83 | } 84 | 85 | public static Type GetFuncType(int parameterCount) 86 | { 87 | // +1 for the return type 88 | return typeof(Func<>).Assembly.GetType($"System.Func`{parameterCount + 1}"); 89 | } 90 | 91 | public static Type GetActionType(int parameterCount) 92 | { 93 | return typeof(Action<>).Assembly.GetType($"System.Action`{parameterCount}"); 94 | } 95 | 96 | public static bool HasParamsArrayType(ParameterInfo parameterInfo) 97 | { 98 | return parameterInfo.IsDefined(typeof(ParamArrayAttribute), false); 99 | } 100 | 101 | public static Type GetParameterType(ParameterInfo parameterInfo) 102 | { 103 | var isParamsArray = HasParamsArrayType(parameterInfo); 104 | var type = isParamsArray 105 | ? parameterInfo.ParameterType.GetElementType() 106 | : parameterInfo.ParameterType; 107 | return type; 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /test/DynamicExpresso.UnitTest/ExtensionsMethodsTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using NUnit.Framework; 4 | using System.Collections.Generic; 5 | 6 | namespace DynamicExpresso.UnitTest 7 | { 8 | [TestFixture] 9 | public class ExtensionsMethodsTest 10 | { 11 | [Test] 12 | public void Invoke_extension_method() 13 | { 14 | var x = new MyClass(); 15 | 16 | var target = new Interpreter() 17 | .Reference(typeof(TestExtensionsMethods)) 18 | .SetVariable("x", x); 19 | 20 | Assert.That(target.Eval("x.HelloWorld()"), Is.EqualTo(x.HelloWorld())); 21 | Assert.That(target.Eval("x.HelloWorldWithParam(DateTime.Now)"), Is.EqualTo(x.HelloWorldWithParam(DateTime.Now))); 22 | } 23 | 24 | [Test] 25 | public void Invoke_generic_extension_method() 26 | { 27 | var x = new MyClass(); 28 | 29 | var target = new Interpreter() 30 | .Reference(typeof(TestExtensionsMethods)) 31 | .SetVariable("x", x); 32 | 33 | Assert.That(target.Eval("x.GenericHello()"), Is.EqualTo(x.GenericHello())); 34 | } 35 | 36 | [Test] 37 | public void Invoke_generic_parameter_extension_method() 38 | { 39 | var x = new MyClass[0]; 40 | 41 | var target = new Interpreter() 42 | .Reference(typeof(TestExtensionsMethods)) 43 | .SetVariable("x", x); 44 | 45 | Assert.That(target.Eval("x.GenericParamHello()"), Is.EqualTo(x.GenericParamHello())); 46 | } 47 | 48 | [Test] 49 | public void Invoke_generic_parameter_extension_method_with_2_parameters() 50 | { 51 | var x = new MyClass[0]; 52 | 53 | var target = new Interpreter() 54 | .Reference(typeof(TestExtensionsMethods)) 55 | .Reference(typeof(MyClass)) 56 | .SetVariable("x", x); 57 | 58 | Assert.That(target.Eval("x.GenericWith2Params(new MyClass())"), Is.EqualTo(x.GenericWith2Params(new MyClass()))); 59 | } 60 | 61 | [Test] 62 | public void Invoke_generic_with_2_parameters_and_output_extension_method() 63 | { 64 | var x = new Dictionary(); 65 | x.Add("i1", new MyClass()); 66 | 67 | var target = new Interpreter() 68 | .Reference(typeof(TestExtensionsMethods)) 69 | .SetVariable("x", x); 70 | 71 | Assert.That(target.Eval("x.GenericWith2Args()"), Is.EqualTo(x.GenericWith2Args())); 72 | } 73 | 74 | [Test] 75 | public void Invoke_generic_mixed_parameter_extension_method() 76 | { 77 | var x = new Dictionary(); 78 | 79 | var target = new Interpreter() 80 | .Reference(typeof(TestExtensionsMethods)) 81 | .SetVariable("x", x); 82 | 83 | Assert.That(target.Eval("x.GenericMixedParamHello()"), Is.EqualTo(x.GenericMixedParamHello())); 84 | } 85 | 86 | public class MyClass 87 | { 88 | } 89 | } 90 | 91 | public static class TestExtensionsMethods 92 | { 93 | public static string HelloWorld(this ExtensionsMethodsTest.MyClass test) 94 | { 95 | return "Hello Test Class"; 96 | } 97 | 98 | public static string HelloWorldWithParam(this ExtensionsMethodsTest.MyClass test, DateTime date) 99 | { 100 | return "Hello Test Class " + date.Year; 101 | } 102 | 103 | public static string GenericHello(this T test) 104 | where T : ExtensionsMethodsTest.MyClass 105 | { 106 | return "Hello with generic!"; 107 | } 108 | 109 | public static string GenericParamHello(this IEnumerable test) 110 | where T : ExtensionsMethodsTest.MyClass 111 | { 112 | return "Hello with generic param!"; 113 | } 114 | 115 | public static string GenericWith2Params(this IEnumerable test, T another) 116 | where T : ExtensionsMethodsTest.MyClass 117 | { 118 | return "Hello with 2 generic param!"; 119 | } 120 | 121 | public static string GenericMixedParamHello(this IDictionary test) 122 | where T : ExtensionsMethodsTest.MyClass 123 | { 124 | return "Hello with generic param!"; 125 | } 126 | 127 | public static T2 GenericWith2Args(this IDictionary test) 128 | where T2 : ExtensionsMethodsTest.MyClass 129 | { 130 | return test.First().Value; 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/DynamicExpresso.Core/Identifier.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | using DynamicExpresso.Reflection; 6 | 7 | namespace DynamicExpresso 8 | { 9 | public class Identifier 10 | { 11 | public Expression Expression { get; private set; } 12 | public string Name { get; private set; } 13 | 14 | public Identifier(string name, Expression expression) 15 | { 16 | if (string.IsNullOrWhiteSpace(name)) 17 | throw new ArgumentNullException("name"); 18 | 19 | if (expression == null) 20 | throw new ArgumentNullException("expression"); 21 | 22 | Expression = expression; 23 | Name = name; 24 | } 25 | } 26 | 27 | internal class FunctionIdentifier : Identifier 28 | { 29 | internal FunctionIdentifier(string name, Delegate value) : base(name, new MethodGroupExpression(value)) 30 | { 31 | } 32 | 33 | internal void AddOverload(Delegate overload) 34 | { 35 | ((MethodGroupExpression)Expression).AddOverload(overload); 36 | } 37 | } 38 | 39 | /// 40 | /// Custom expression that simulates a method group (ie. a group of methods with the same name). 41 | /// It's used when custom functions are added to the interpreter via . 42 | /// 43 | internal class MethodGroupExpression : Expression 44 | { 45 | public class Overload 46 | { 47 | public Delegate Delegate { get; } 48 | 49 | private MethodBase _method; 50 | public MethodBase Method 51 | { 52 | get 53 | { 54 | if (_method == null) 55 | _method = Delegate.Method; 56 | 57 | return _method; 58 | } 59 | } 60 | 61 | // we'll most likely never need this: it was needed before https://github.com/dotnet/roslyn/pull/53402 62 | private MethodBase _invokeMethod; 63 | public MethodBase InvokeMethod 64 | { 65 | get 66 | { 67 | if (_invokeMethod == null) 68 | _invokeMethod = MemberFinder.FindInvokeMethod(Delegate.GetType()); 69 | 70 | return _invokeMethod; 71 | } 72 | } 73 | 74 | public Overload(Delegate @delegate) 75 | { 76 | Delegate = @delegate; 77 | } 78 | } 79 | 80 | private readonly List _overloads = new List(); 81 | 82 | internal IReadOnlyCollection Overloads 83 | { 84 | get 85 | { 86 | return _overloads.AsReadOnly(); 87 | } 88 | } 89 | 90 | internal MethodGroupExpression(Delegate overload) 91 | { 92 | AddOverload(overload); 93 | } 94 | 95 | internal void AddOverload(Delegate overload) 96 | { 97 | // remove any existing delegate with the exact same signature 98 | RemoveDelegateSignature(overload); 99 | _overloads.Add(new Overload(overload)); 100 | } 101 | 102 | private void RemoveDelegateSignature(Delegate overload) 103 | { 104 | _overloads.RemoveAll(del => HasSameSignature(overload.Method, del.Delegate.Method)); 105 | } 106 | 107 | private static bool HasSameSignature(MethodInfo method, MethodInfo other) 108 | { 109 | if (method.ReturnType != other.ReturnType) 110 | return false; 111 | 112 | var param = method.GetParameters(); 113 | var oParam = other.GetParameters(); 114 | if (param.Length != oParam.Length) 115 | return false; 116 | 117 | for (var i = 0; i < param.Length; i++) 118 | { 119 | var p = param[i]; 120 | var q = oParam[i]; 121 | if (p.ParameterType != q.ParameterType || p.HasDefaultValue != q.HasDefaultValue) 122 | return false; 123 | } 124 | 125 | return true; 126 | } 127 | 128 | /// 129 | /// The resolution process will find the best overload for the given arguments, 130 | /// which we then need to match to the correct delegate. 131 | /// 132 | internal Delegate FindUsedOverload(bool usedInvokeMethod, MethodData methodData) 133 | { 134 | foreach (var overload in _overloads) 135 | { 136 | if (usedInvokeMethod) 137 | { 138 | if (methodData.MethodBase == overload.InvokeMethod) 139 | return overload.Delegate; 140 | } 141 | else 142 | { 143 | if (methodData.MethodBase == overload.Method) 144 | return overload.Delegate; 145 | } 146 | } 147 | 148 | // this should never happen 149 | throw new InvalidOperationException("No overload matches the method"); 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/DynamicExpresso.Core/Reflection/MemberFinder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using DynamicExpresso.Resolution; 7 | 8 | namespace DynamicExpresso.Reflection 9 | { 10 | internal class MemberFinder 11 | { 12 | private readonly ParserArguments _arguments; 13 | private readonly BindingFlags _bindingCase; 14 | private readonly MemberFilter _memberFilterCase; 15 | 16 | public MemberFinder(ParserArguments arguments) 17 | { 18 | _arguments = arguments; 19 | _bindingCase = arguments.Settings.CaseInsensitive ? BindingFlags.IgnoreCase : BindingFlags.Default; 20 | _memberFilterCase = arguments.Settings.CaseInsensitive ? Type.FilterNameIgnoreCase : Type.FilterName; 21 | } 22 | 23 | public MemberInfo FindPropertyOrField(Type type, string memberName, bool staticAccess) 24 | { 25 | var flags = BindingFlags.Public | BindingFlags.DeclaredOnly | 26 | (staticAccess ? BindingFlags.Static : BindingFlags.Instance) | _bindingCase; 27 | 28 | foreach (var t in SelfAndBaseTypes(type)) 29 | { 30 | var members = t.FindMembers(MemberTypes.Property | MemberTypes.Field, flags, _memberFilterCase, memberName); 31 | if (members.Length != 0) 32 | return members[0]; 33 | } 34 | return null; 35 | } 36 | 37 | public IList FindMethods(Type type, string methodName, bool staticAccess, Expression[] args) 38 | { 39 | var flags = BindingFlags.Public | BindingFlags.DeclaredOnly | 40 | (staticAccess ? BindingFlags.Static : BindingFlags.Instance) | _bindingCase; 41 | foreach (var t in SelfAndBaseTypes(type)) 42 | { 43 | var members = t.FindMembers(MemberTypes.Method, flags, _memberFilterCase, methodName); 44 | var applicableMethods = MethodResolution.FindBestMethod(members.Cast(), args); 45 | 46 | if (applicableMethods.Count > 0) 47 | return applicableMethods; 48 | } 49 | 50 | return Array.Empty(); 51 | } 52 | 53 | // this method is static, because we know that the Invoke method of a delegate always has this exact name 54 | // and therefore we never need to search for it in case-insensitive mode 55 | public static MethodBase FindInvokeMethod(Type type) 56 | { 57 | var flags = BindingFlags.Public | BindingFlags.DeclaredOnly | 58 | BindingFlags.Instance; 59 | foreach (var t in SelfAndBaseTypes(type)) 60 | { 61 | var method = t.FindMembers(MemberTypes.Method, flags, Type.FilterName, "Invoke") 62 | .Cast() 63 | .SingleOrDefault(); 64 | 65 | if (method != null) 66 | return method; 67 | } 68 | 69 | return null; 70 | } 71 | 72 | public IList FindExtensionMethods(string methodName, Expression[] args) 73 | { 74 | var matchMethods = _arguments.GetExtensionMethods(methodName); 75 | 76 | return MethodResolution.FindBestMethod(matchMethods, args); 77 | } 78 | 79 | public IList FindIndexer(Type type, Expression[] args) 80 | { 81 | foreach (var t in SelfAndBaseTypes(type)) 82 | { 83 | var members = t.GetDefaultMembers(); 84 | if (members.Length != 0) 85 | { 86 | var methods = members. 87 | OfType(). 88 | Select(p => (MethodData)new IndexerData(p)); 89 | 90 | var applicableMethods = MethodResolution.FindBestMethod(methods, args); 91 | if (applicableMethods.Count > 0) 92 | return applicableMethods; 93 | } 94 | } 95 | 96 | return Array.Empty(); 97 | } 98 | 99 | private static IEnumerable SelfAndBaseTypes(Type type) 100 | { 101 | if (type.IsInterface) 102 | { 103 | var types = new List(); 104 | AddInterface(types, type); 105 | 106 | types.Add(typeof(object)); 107 | 108 | return types; 109 | } 110 | return SelfAndBaseClasses(type); 111 | } 112 | 113 | private static IEnumerable SelfAndBaseClasses(Type type) 114 | { 115 | while (type != null) 116 | { 117 | yield return type; 118 | type = type.BaseType; 119 | } 120 | } 121 | 122 | private static void AddInterface(List types, Type type) 123 | { 124 | if (!types.Contains(type)) 125 | { 126 | types.Add(type); 127 | foreach (var t in type.GetInterfaces()) 128 | { 129 | AddInterface(types, t); 130 | } 131 | } 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /sample/DynamicExpressoWebShell/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = null; 3 | 4 | string assemblyVersion = typeof(DynamicExpresso.Interpreter).Assembly.GetName().Version.ToString(); 5 | } 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Web Shell - Dynamic Expresso 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 |

Dynamic Expresso Web Shell

26 |

27 | Dynamic Expresso is an expression interpreter for simple C# statements. 28 |
29 | Try it now directly in your browser. 30 |

31 |
32 |
33 | 34 |
35 |
36 |
37 |

Dynamic Expresso version: @assemblyVersion

38 | @*

type "help" for help

*@ 39 | 40 |
41 |
42 | 43 |
44 |

45 | Dynamic Expresso understands a subset of C# syntax (numeric and string operators, comparison operators, typeof, new, primitive types, Math and Convert classes, ...). 46 | It supports single statement expression only. See documentation for more information.
47 | In this example all the results are serialized using json to be more human readable. 48 |

49 |
50 | 51 |
52 |
53 |
54 |

Commands examples

55 |

56 | Here some expressions that you can try: 57 |

58 |
    59 |
  • 5 + 2 * 8 / (80 + 9.9)
  • 60 |
  • "Hello" + " " + "world!"
  • 61 |
  • DateTime.Now.AddDays(30)
  • 62 |
  • new DateTime(2020, 2, 13)
  • 63 |
  • string.Format("My name is {0}. Today is {1}", "R2-D2", DateTime.Now.ToShortDateString())
  • 64 |
  • Commands.Count
  • 65 |
  • Commands.Clear()
  • 66 |
  • Commands.GetLastCommands()
  • 67 |
68 | 69 |

70 | The Commands variable is just for demostration purpose. 71 |

72 |
73 |
74 | 75 |
76 |
77 |

credits

78 |

79 | 80 | shell javascript code is based on the mongulator project (https://github.com/banker/mongulator)
81 | Copyright (c) 2009 Kyle Banker 82 |
83 |

84 |
85 |
86 |
87 | 88 |
89 | 90 | 91 | 92 | 93 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /test/DynamicExpresso.UnitTest/InvalidExpressionTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using DynamicExpresso.Exceptions; 3 | using NUnit.Framework; 4 | 5 | namespace DynamicExpresso.UnitTest 6 | { 7 | [TestFixture] 8 | public class InvalidExpressionTest 9 | { 10 | [Test] 11 | public void Not_existing_variable() 12 | { 13 | var target = new Interpreter(); 14 | 15 | Assert.Throws(() => target.Eval("not_existing")); 16 | } 17 | 18 | [Test] 19 | public void Invalid_equal_assignment_operator_left() 20 | { 21 | var target = new Interpreter(); 22 | 23 | Assert.Throws(() => target.Eval("=234")); 24 | } 25 | 26 | [Test] 27 | public void Invalid_equal_assignment_operator_left_is_literal() 28 | { 29 | var target = new Interpreter(); 30 | 31 | Assert.Throws(() => target.Eval("352=234")); 32 | } 33 | 34 | [Test] 35 | public void Unkonwn_operator_triple_equal() 36 | { 37 | var target = new Interpreter(); 38 | 39 | Assert.Throws(() => target.Eval("352===234")); 40 | } 41 | 42 | [Test] 43 | public void Not_existing_function() 44 | { 45 | var target = new Interpreter(); 46 | 47 | Assert.Throws(() => target.Eval("pippo()")); 48 | } 49 | 50 | [Test] 51 | public void Not_valid_function() 52 | { 53 | var target = new Interpreter(); 54 | 55 | 56 | Assert.Throws(() => target.Eval("2()")); 57 | } 58 | 59 | [Test] 60 | public void Not_valid_expression() 61 | { 62 | var target = new Interpreter(); 63 | 64 | Assert.Throws(() => target.Eval("'5' + 3 /(asda")); 65 | } 66 | 67 | [Test] 68 | public void TryParse_an_invalid_expression_unknown_identifier_x() 69 | { 70 | var target = new Interpreter(); 71 | 72 | var ex = Assert.Throws(() => target.Parse("x + y * Math.Pow(x, 2)", typeof(void))); 73 | 74 | Assert.That(ex.Message, Is.EqualTo("Unknown identifier 'x' (at index 0).")); 75 | Assert.That(ex.Identifier, Is.EqualTo("x")); 76 | Assert.That(ex.Position, Is.EqualTo(0)); 77 | } 78 | 79 | [Test] 80 | public void Parse_an_invalid_expression_unknown_identifier_y() 81 | { 82 | var target = new Interpreter() 83 | .SetVariable("x", 10.0); 84 | 85 | var ex = Assert.Throws(() => target.Parse("x + y * Math.Pow(x, 2)", typeof(void))); 86 | 87 | Assert.That(ex.Identifier, Is.EqualTo("y")); 88 | Assert.That(ex.Position, Is.EqualTo(4)); 89 | } 90 | 91 | [Test] 92 | public void Parse_an_invalid_expression_unknown_method() 93 | { 94 | var target = new Interpreter() 95 | .SetVariable("x", 10.0); 96 | 97 | var ex = Assert.Throws(() => target.Parse("Math.NotExistingMethod(x, 2)", typeof(void))); 98 | 99 | Assert.That(ex.MethodName, Is.EqualTo("NotExistingMethod")); 100 | Assert.That(ex.MethodTypeName, Is.EqualTo("Math")); 101 | Assert.That(ex.Position, Is.EqualTo(5)); 102 | } 103 | 104 | [Test] 105 | public void SystemExceptions_are_preserved_using_delegate_variable() 106 | { 107 | var target = new Interpreter(); 108 | 109 | Func testException = () => 110 | { 111 | throw new InvalidOperationException("Test"); 112 | }; 113 | 114 | target.SetVariable("testException", testException); 115 | 116 | Assert.Throws(() => target.Eval("testException()")); 117 | } 118 | 119 | [Test] 120 | public void CustomExceptions_WithoutSerializationConstructor_are_preserved() 121 | { 122 | var target = new Interpreter(); 123 | 124 | Func testException = () => 125 | { 126 | throw new MyException("Test"); 127 | }; 128 | 129 | target.SetVariable("testException", testException); 130 | 131 | Assert.Throws(() => target.Eval("testException()")); 132 | } 133 | 134 | [Test] 135 | public void SystemExceptions_are_preserved_using_method_invocation() 136 | { 137 | var target = new Interpreter(); 138 | target.SetVariable("a", new MyTestService()); 139 | 140 | Assert.Throws(() => target.Eval("a.ThrowException()")); 141 | } 142 | 143 | public class MyException : Exception 144 | { 145 | public MyException(string message) : base(message) { } 146 | } 147 | 148 | // ReSharper disable once UnusedMember.Local 149 | private class MyTestService 150 | { 151 | // ReSharper disable once UnusedMember.Local 152 | public string ThrowException() 153 | { 154 | throw new NotImplementedException("AppException"); 155 | } 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/DynamicExpresso.Core/Detector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Text.RegularExpressions; 6 | using DynamicExpresso.Parsing; 7 | 8 | namespace DynamicExpresso 9 | { 10 | internal class Detector 11 | { 12 | private readonly ParserSettings _settings; 13 | 14 | private static readonly Regex RootIdentifierDetectionRegex = 15 | new Regex(@"(?<=[^\w@]|^)(?@?[\p{L}\p{Nl}_][\p{L}\p{Nl}\p{Nd}\p{Mn}\p{Mc}\p{Pc}\p{Cf}_]*)", RegexOptions.Compiled); 16 | 17 | private static readonly string Id = RootIdentifierDetectionRegex.ToString(); 18 | private static readonly string Type = Id.Replace("", ""); 19 | 20 | private static readonly Regex LambdaDetectionRegex = 21 | new Regex($@"(\((((?({Type}\s+)?{Id}))(\s*,\s*)?)+\)|(?{Id}))\s*=>", 22 | RegexOptions.Compiled); 23 | 24 | private static readonly Regex StringDetectionRegex = 25 | new Regex(@"(?(_settings.KeyComparer); 40 | var knownIdentifiers = new HashSet(); 41 | var knownTypes = new HashSet(); 42 | 43 | // find lambda parameters 44 | var lambdaParameters = new Dictionary(); 45 | foreach (Match match in LambdaDetectionRegex.Matches(expression)) 46 | { 47 | var withtypes = match.Groups["withtype"].Captures; 48 | var types = match.Groups["type"].Captures; 49 | var identifiers = match.Groups["id"].Captures; 50 | 51 | // match identifier with its type 52 | var t = 0; 53 | for (var i = 0; i < withtypes.Count; i++) 54 | { 55 | var withtype = withtypes[i].Value; 56 | var identifier = identifiers[i].Value; 57 | var type = typeof(object); 58 | if (withtype != identifier) 59 | { 60 | var typeName = types[t].Value; 61 | if (_settings.KnownTypes.TryGetValue(typeName, out var knownType)) 62 | type = knownType.Type; 63 | 64 | t++; 65 | } 66 | 67 | // there might be several lambda parameters with the same name 68 | // -> in that case, we ignore the detected type 69 | if (lambdaParameters.TryGetValue(identifier, out var already) && 70 | already.Expression.Type != type) 71 | type = typeof(object); 72 | 73 | var defaultValue = type.IsValueType ? Activator.CreateInstance(type) : null; 74 | lambdaParameters[identifier] = new Identifier(identifier, Expression.Constant(defaultValue, type)); 75 | } 76 | } 77 | 78 | foreach (Match match in RootIdentifierDetectionRegex.Matches(expression)) 79 | { 80 | var idGroup = match.Groups["id"]; 81 | var identifier = idGroup.Value; 82 | 83 | if (IsReservedKeyword(identifier)) 84 | continue; 85 | 86 | if (option == DetectorOptions.None && idGroup.Index > 0) 87 | { 88 | var previousChar = expression[idGroup.Index - 1]; 89 | 90 | // don't consider member accesses as identifiers (e.g. "x.Length" will only return x but not Length) 91 | if (previousChar == '.') 92 | continue; 93 | 94 | // don't consider number literals as identifiers 95 | if (char.IsDigit(previousChar)) 96 | continue; 97 | } 98 | 99 | if (_settings.Identifiers.TryGetValue(identifier, out var knownIdentifier)) 100 | knownIdentifiers.Add(knownIdentifier); 101 | else if (lambdaParameters.TryGetValue(identifier, out var knownLambdaParam)) 102 | knownIdentifiers.Add(knownLambdaParam); 103 | else if (_settings.KnownTypes.TryGetValue(identifier, out var knownType)) 104 | knownTypes.Add(knownType); 105 | else 106 | unknownIdentifiers.Add(identifier); 107 | } 108 | 109 | return new IdentifiersInfo(unknownIdentifiers, knownIdentifiers, knownTypes); 110 | } 111 | 112 | private static string PrepareExpression(string expression) 113 | { 114 | expression = expression ?? string.Empty; 115 | 116 | expression = RemoveStringLiterals(expression); 117 | 118 | expression = RemoveCharLiterals(expression); 119 | 120 | return expression; 121 | } 122 | 123 | private static string RemoveStringLiterals(string expression) 124 | { 125 | return StringDetectionRegex.Replace(expression, ""); 126 | } 127 | 128 | private static string RemoveCharLiterals(string expression) 129 | { 130 | return CharDetectionRegex.Replace(expression, ""); 131 | } 132 | 133 | private bool IsReservedKeyword(string identifier) 134 | { 135 | return ParserConstants.ReservedKeywords.Contains(identifier, _settings.KeyComparer); 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /sample/DynamicExpressoWebShell/wwwroot/Scripts/webshell.js: -------------------------------------------------------------------------------- 1 | // Dynamic Expresso Web Shell javascript code is 2 | // based on the mongulator project (https://github.com/banker/mongulator) 3 | // 4 | // Copyright (c) 2009 Kyle Banker 5 | // Licensed under the MIT Licence. 6 | // http://www.opensource.org/licenses/mit-license.php 7 | 8 | 9 | var DefaultInputHtml = function (stack) { 10 | var linePrompt = ""; 11 | if (stack == 0) { 12 | linePrompt += " >"; 13 | } 14 | else { 15 | for (var i = 0; i <= stack; i++) { 16 | linePrompt += "."; 17 | } 18 | } 19 | return "
" + 20 | linePrompt + 21 | "" + 22 | "
"; 23 | } 24 | 25 | var EnterKeyCode = 13; 26 | var UpArrowKeyCode = 38; 27 | var DownArrowKeyCode = 40; 28 | 29 | var PTAG = function (str) { 30 | return "
" + str + "
"; 31 | } 32 | 33 | var BR = function () { 34 | return "
"; 35 | } 36 | 37 | // Readline class to handle line input. 38 | var ReadLine = function (options) { 39 | this.options = options || {}; 40 | this.htmlForInput = this.options.htmlForInput; 41 | this.inputHandler = this.options.handler; 42 | //this.scoper = this.options.scoper; 43 | // this.connection = new Connection(); 44 | this.terminal = $(this.options.terminalId || "#terminal"); 45 | this.lineClass = this.options.lineClass || '.readLine'; 46 | this.history = []; 47 | this.historyPtr = 0; 48 | 49 | this.initialize(); 50 | }; 51 | 52 | ReadLine.prototype = { 53 | 54 | initialize: function () { 55 | this.addInputLine(); 56 | 57 | var inputElement = this.lineClass + '.active'; 58 | var $terminal = this.terminal; 59 | this.terminal.click(function (event) { 60 | var $target = $(event.target); 61 | if ($target.is($terminal)) 62 | $(inputElement).focus(); 63 | }); 64 | }, 65 | 66 | // Enter a new input line with proper behavior. 67 | addInputLine: function (stackLevel) { 68 | stackLevel = stackLevel || 0; 69 | this.terminal.append(this.htmlForInput(stackLevel)); 70 | var ctx = this; 71 | ctx.activeLine = $(this.lineClass + '.active'); 72 | 73 | // Bind key events for entering and navigting history. 74 | ctx.activeLine.bind("keydown", function (ev) { 75 | switch (ev.keyCode) { 76 | case EnterKeyCode: 77 | ctx.processInput(this.value); 78 | break; 79 | case UpArrowKeyCode: 80 | ctx.getCommand('previous'); 81 | break; 82 | case DownArrowKeyCode: 83 | ctx.getCommand('next'); 84 | break; 85 | } 86 | }); 87 | 88 | this.activeLine.focus(); 89 | }, 90 | 91 | // Returns the 'next' or 'previous' command in this history. 92 | getCommand: function (direction) { 93 | if (this.history.length === 0) { 94 | return; 95 | } 96 | this.adjustHistoryPointer(direction); 97 | this.activeLine[0].value = this.history[this.historyPtr]; 98 | $(this.activeLine[0]).focus(); 99 | //this.activeLine[0].value = this.activeLine[0].value; 100 | }, 101 | 102 | // Moves the history pointer to the 'next' or 'previous' position. 103 | adjustHistoryPointer: function (direction) { 104 | if (direction == 'previous') { 105 | if (this.historyPtr - 1 >= 0) { 106 | this.historyPtr -= 1; 107 | } 108 | } 109 | else { 110 | if (this.historyPtr + 1 < this.history.length) { 111 | this.historyPtr += 1; 112 | } 113 | } 114 | }, 115 | 116 | // Return the handler's response. 117 | processInput: function (expression) { 118 | var me = this; 119 | this.inputHandler.eval(expression, function (response) { 120 | me.processOutput(expression, response); 121 | }); 122 | }, 123 | 124 | processOutput: function (expression, response) { 125 | this.insertResponse(response.result); 126 | 127 | // Save to the command history... 128 | if ((lineValue = expression.trim()) !== "") { 129 | this.history.push(lineValue); 130 | this.historyPtr = this.history.length; 131 | } 132 | 133 | // deactivate the line... 134 | this.activeLine.value = ""; 135 | this.activeLine.attr({ disabled: true }); 136 | this.activeLine.removeClass('active'); 137 | 138 | // and add a new command line. 139 | this.addInputLine(response.stack); 140 | }, 141 | 142 | insertResponse: function (response) { 143 | var pClass = "response"; 144 | if (!response.success) 145 | pClass += " error"; 146 | 147 | var $newP = $("
")
148 |         $newP.text(response.result);
149 | 
150 |         this.activeLine.parent().append($newP);
151 |     }
152 | };
153 | 
154 | 
155 | $htmlFormat = function (obj) {
156 |     return tojson(obj, ' ', ' ', true);
157 | }
158 | 
159 | 
160 | 
161 | var DynamicExpressoHandler = function (options) {
162 |     this.options = options || {};
163 |     this._commandStack = 0;
164 |     this._interpreterUrl = this.options.interpreterUrl;
165 | };
166 | 
167 | DynamicExpressoHandler.prototype = {
168 | 
169 |     eval: function (inputString, onSuccedeed) {
170 | 
171 |         $.ajax({
172 |             type: "POST",
173 |             url: this._interpreterUrl,
174 |             data: { expression: inputString },
175 |             dataType: "json"
176 |         }).done(function (result) {
177 |             onSuccedeed({ stack: this._commandStack, result: result });
178 |         });
179 |     }
180 | };
181 | 
182 | 


--------------------------------------------------------------------------------
/src/DynamicExpresso.Core/Resolution/LateBinders.cs:
--------------------------------------------------------------------------------
  1 | using System;
  2 | using System.Collections.ObjectModel;
  3 | using System.Linq;
  4 | using System.Linq.Expressions;
  5 | using System.Runtime.CompilerServices;
  6 | using DynamicExpresso.Reflection;
  7 | using Microsoft.CSharp.RuntimeBinder;
  8 | 
  9 | namespace DynamicExpresso.Resolution
 10 | {
 11 | 	internal interface IConvertibleToWritableBinder
 12 | 	{
 13 | 		CallSiteBinder ToWritableBinder();
 14 | 	}
 15 | 
 16 | 	internal class LateGetMemberCallSiteBinder : CallSiteBinder, IConvertibleToWritableBinder
 17 | 	{
 18 | 		private readonly string _propertyOrFieldName;
 19 | 
 20 | 		public LateGetMemberCallSiteBinder(string propertyOrFieldName)
 21 | 		{
 22 | 			_propertyOrFieldName = propertyOrFieldName;
 23 | 		}
 24 | 
 25 | 		public override Expression Bind(object[] args, ReadOnlyCollection parameters, LabelTarget returnLabel)
 26 | 		{
 27 | 			// there's only one argument: the instance on which the member is accessed
 28 | 			var binder = Binder.GetMember(
 29 | 				CSharpBinderFlags.None,
 30 | 				_propertyOrFieldName,
 31 | 				TypeUtils.RemoveArrayType(args[0]?.GetType()),
 32 | 				parameters.Select(x => CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null))
 33 | 			);
 34 | 			return binder.Bind(args, parameters, returnLabel);
 35 | 		}
 36 | 
 37 | 		public CallSiteBinder ToWritableBinder()
 38 | 		{
 39 | 			return new LateSetMemberCallSiteBinder(_propertyOrFieldName);
 40 | 		}
 41 | 	}
 42 | 
 43 | 	internal class LateSetMemberCallSiteBinder : CallSiteBinder
 44 | 	{
 45 | 		private readonly string _propertyOrFieldName;
 46 | 
 47 | 		public LateSetMemberCallSiteBinder(string propertyOrFieldName)
 48 | 		{
 49 | 			_propertyOrFieldName = propertyOrFieldName;
 50 | 		}
 51 | 
 52 | 		public override Expression Bind(object[] args, ReadOnlyCollection parameters, LabelTarget returnLabel)
 53 | 		{
 54 | 			// there are two arguments: the instance on which the member is set and the value to set
 55 | 			var binder = Binder.SetMember(
 56 | 				CSharpBinderFlags.None,
 57 | 				_propertyOrFieldName,
 58 | 				TypeUtils.RemoveArrayType(args[0]?.GetType()),
 59 | 				parameters.Select(x => CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null))
 60 | 			);
 61 | 			return binder.Bind(args, parameters, returnLabel);
 62 | 		}
 63 | 	}
 64 | 
 65 | 	/// 
 66 | 	/// Binds to a method invocation of an instance as late as possible.  This allows the use of anonymous types on dynamic values.
 67 | 	/// 
 68 | 	internal class LateInvokeMethodCallSiteBinder : CallSiteBinder
 69 | 	{
 70 | 		private readonly string _methodName;
 71 | 		private readonly bool _isStatic;
 72 | 
 73 | 		public LateInvokeMethodCallSiteBinder(string methodName, bool isStatic)
 74 | 		{
 75 | 			_methodName = methodName;
 76 | 			_isStatic = isStatic;
 77 | 		}
 78 | 
 79 | 		public override Expression Bind(object[] args, ReadOnlyCollection parameters, LabelTarget returnLabel)
 80 | 		{
 81 | 			// if the method is static, the first argument is the type containing the method,
 82 | 			// otherwise it's the instance on which the method is called
 83 | 			var context = _isStatic ? (Type)args[0] : args[0]?.GetType();
 84 | 			var argumentInfo = parameters.Select(x => CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)).ToArray();
 85 | 			if (_isStatic)
 86 | 			{
 87 | 				// instruct the compiler that we already know the containing type of the method
 88 | 				argumentInfo[0] = CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType | CSharpArgumentInfoFlags.IsStaticType, null);
 89 | 			}
 90 | 
 91 | 			var binderM = Binder.InvokeMember(
 92 | 				CSharpBinderFlags.None,
 93 | 				_methodName,
 94 | 				null,
 95 | 				TypeUtils.RemoveArrayType(context),
 96 | 				argumentInfo
 97 | 			);
 98 | 			return binderM.Bind(args, parameters, returnLabel);
 99 | 		}
100 | 	}
101 | 
102 | 	/// 
103 | 	/// Binds to a delegate invocation as late as possible.  This allows the use of delegates with dynamic arguments.
104 | 	/// 
105 | 	internal class LateInvokeDelegateCallSiteBinder : CallSiteBinder
106 | 	{
107 | 		public LateInvokeDelegateCallSiteBinder()
108 | 		{
109 | 		}
110 | 
111 | 		public override Expression Bind(object[] args, ReadOnlyCollection parameters, LabelTarget returnLabel)
112 | 		{
113 | 			var argumentInfo = parameters.Select(x => CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)).ToArray();
114 | 
115 | 			// the first argument is the delegate to invoke: instruct the compiler that we already know its type
116 | 			argumentInfo[0] = CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null);
117 | 
118 | 			var binderM = Binder.Invoke(
119 | 				CSharpBinderFlags.None,
120 | 				null,
121 | 				argumentInfo
122 | 			);
123 | 			return binderM.Bind(args, parameters, returnLabel);
124 | 		}
125 | 	}
126 | 
127 | 	/// 
128 | 	/// Binds to an items invocation of an instance as late as possible.  This allows the use of anonymous types on dynamic values.
129 | 	/// 
130 | 	internal class LateGetIndexCallSiteBinder : CallSiteBinder, IConvertibleToWritableBinder
131 | 	{
132 | 		public override Expression Bind(object[] args, ReadOnlyCollection parameters, LabelTarget returnLabel)
133 | 		{
134 | 			// there are two arguments: the instance on which the member is set and the value of the indexer
135 | 			var binder = Binder.GetIndex(
136 | 				CSharpBinderFlags.None,
137 | 				TypeUtils.RemoveArrayType(args[0]?.GetType()),
138 | 				parameters.Select(x => CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null))
139 | 			);
140 | 			return binder.Bind(args, parameters, returnLabel);
141 | 		}
142 | 
143 | 		public CallSiteBinder ToWritableBinder()
144 | 		{
145 | 			return new LateSetIndexCallSiteBinder();
146 | 		}
147 | 	}
148 | 
149 | 	internal class LateSetIndexCallSiteBinder : CallSiteBinder
150 | 	{
151 | 		public override Expression Bind(object[] args, ReadOnlyCollection parameters, LabelTarget returnLabel)
152 | 		{
153 | 			// there are three arguments: the instance on which the member is set, the value of the indexer, and the value to set
154 | 			var binder = Binder.SetIndex(
155 | 				CSharpBinderFlags.None,
156 | 				TypeUtils.RemoveArrayType(args[0]?.GetType()),
157 | 				parameters.Select(x => CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null))
158 | 			);
159 | 			return binder.Bind(args, parameters, returnLabel);
160 | 		}
161 | 	}
162 | }
163 | 


--------------------------------------------------------------------------------
/test/DynamicExpresso.UnitTest/TypesTest.cs:
--------------------------------------------------------------------------------
  1 | using System;
  2 | using NUnit.Framework;
  3 | using System.Collections.Generic;
  4 | using System.Linq;
  5 | using DynamicExpresso.Exceptions;
  6 | 
  7 | namespace DynamicExpresso.UnitTest
  8 | {
  9 | 	[TestFixture]
 10 | 	public class TypesTest
 11 | 	{
 12 | 		[Test]
 13 | 		public void Default_Types()
 14 | 		{
 15 | 			var target = new Interpreter();
 16 | 
 17 | 			var predefinedTypes = new Dictionary{
 18 |                     {"Object", typeof(object)},
 19 |                     {"object", typeof(object)},
 20 |                     {"Boolean", typeof(bool)},
 21 |                     {"bool", typeof(bool)},
 22 |                     {"Char", typeof(char)},
 23 |                     {"char", typeof(char)},
 24 |                     {"String", typeof(string)},
 25 |                     {"string", typeof(string)},
 26 |                     {"SByte", typeof(sbyte)},
 27 |                     {"Byte", typeof(byte)},
 28 |                     {"byte", typeof(byte)},
 29 |                     {"Int16", typeof(short)},
 30 |                     {"UInt16", typeof(ushort)},
 31 |                     {"Int32", typeof(int)},
 32 |                     {"int", typeof(int)},
 33 |                     {"UInt32", typeof(uint)},
 34 |                     {"Int64", typeof(long)},
 35 |                     {"long", typeof(long)},
 36 |                     {"UInt64", typeof(ulong)},
 37 |                     {"Single", typeof(float)},
 38 |                     {"Double", typeof(double)},
 39 |                     {"double", typeof(double)},
 40 |                     {"Decimal", typeof(decimal)},
 41 |                     {"decimal", typeof(decimal)},
 42 |                     {"DateTime", typeof(DateTime)},
 43 |                     {"TimeSpan", typeof(TimeSpan)},
 44 |                     {"Guid", typeof(Guid)},
 45 |                     {"Math", typeof(Math)},
 46 |                     {"Convert", typeof(Convert)},
 47 |                 };
 48 | 
 49 | 			foreach (var t in predefinedTypes)
 50 | 				Assert.That(target.Eval(string.Format("typeof({0})", t.Key)), Is.EqualTo(t.Value));
 51 | 		}
 52 | 
 53 | 		[Test]
 54 | 		public void Load_interpreter_without_any_configuration_doesn_t_recognize_types()
 55 | 		{
 56 | 			var target = new Interpreter(InterpreterOptions.None);
 57 | 
 58 | 			Assert.Throws(() => target.Eval("typeof(string)"));
 59 | 		}
 60 | 
 61 | 		[Test]
 62 | 		public void Custom_Types()
 63 | 		{
 64 | 			var target = new Interpreter()
 65 | 											.Reference(typeof(Uri));
 66 | 
 67 | 			Assert.That(target.Eval("typeof(Uri)"), Is.EqualTo(typeof(Uri)));
 68 | 			Assert.That(target.Eval("new Uri(\"http://test\")"), Is.EqualTo(new Uri("http://test")));
 69 | 		}
 70 | 
 71 | 		[Test]
 72 | 		public void Reference_the_same_type_multiple_times_doesn_t_have_effect()
 73 | 		{
 74 | 			var target = new Interpreter()
 75 | 											.Reference(typeof(string))
 76 | 											.Reference(typeof(string))
 77 | 											.Reference(typeof(Uri))
 78 | 											.Reference(typeof(Uri))
 79 | 											.Reference(typeof(Uri));
 80 | 
 81 | 			Assert.That(target.Eval("typeof(Uri)"), Is.EqualTo(typeof(Uri)));
 82 | 			Assert.That(target.Eval("new Uri(\"http://test\")"), Is.EqualTo(new Uri("http://test")));
 83 | 		}
 84 | 
 85 | 		[Test]
 86 | 		public void References_can_be_case_insensitive()
 87 | 		{
 88 | 			var target = new Interpreter(InterpreterOptions.DefaultCaseInsensitive)
 89 | 											.Reference(typeof(string))
 90 | 											.Reference(typeof(Uri));
 91 | 
 92 | 			Assert.That(target.Eval("typeof(Uri)"), Is.EqualTo(typeof(Uri)));
 93 | 			Assert.That(target.Eval("typeof(uri)"), Is.EqualTo(typeof(Uri)));
 94 | 			Assert.That(target.Eval("typeof(URI)"), Is.EqualTo(typeof(Uri)));
 95 | 			Assert.That(target.Eval("STRING.Empty"), Is.EqualTo(string.Empty));
 96 | 		}
 97 | 
 98 | 		[Test]
 99 | 		public void Custom_Type_Constructor()
100 | 		{
101 | 			var target = new Interpreter()
102 | 											.Reference(typeof(MyDataContract));
103 | 
104 | 			Assert.That(target.Eval("new MyDataContract(\"davide\").Name"), Is.EqualTo(new MyDataContract("davide").Name));
105 | 			Assert.That(target.Eval("new MyDataContract(44 , 88).Name"), Is.EqualTo(new MyDataContract(44, 88).Name));
106 | 		}
107 | 
108 | 		[Test]
109 | 		public void Custom_Type_Alias()
110 | 		{
111 | 			var target = new Interpreter()
112 | 											.Reference(typeof(MyDataContract), "DC");
113 | 
114 | 			Assert.That(target.Parse("new DC(\"davide\")").ReturnType, Is.EqualTo(typeof(MyDataContract)));
115 | 		}
116 | 
117 | 		[Test]
118 | 		public void Custom_Enum()
119 | 		{
120 | 			var target = new Interpreter()
121 | 											.Reference(typeof(MyCustomEnum));
122 | 
123 | 			Assert.That(target.Eval("MyCustomEnum.Value1"), Is.EqualTo(MyCustomEnum.Value1));
124 | 			Assert.That(target.Eval("MyCustomEnum.Value2"), Is.EqualTo(MyCustomEnum.Value2));
125 | 		}
126 | 
127 | 		[Test]
128 | 		public void Enum_are_case_sensitive_by_default()
129 | 		{
130 | 			var target = new Interpreter()
131 | 											.Reference(typeof(EnumCaseSensitive));
132 | 
133 | 			Assert.That(target.Eval("EnumCaseSensitive.Test"), Is.EqualTo(EnumCaseSensitive.Test));
134 | 			Assert.That(target.Eval("EnumCaseSensitive.TEST"), Is.EqualTo(EnumCaseSensitive.TEST));
135 | 		}
136 | 
137 | 		[Test]
138 | 		public void Getting_the_list_of_used_types()
139 | 		{
140 | 			var target = new Interpreter();
141 | 
142 | 			var lambda = target.Parse("Math.Max(a, typeof(string).Name.Length)", new Parameter("a", 1));
143 | 
144 | 			Assert.That(lambda.Types.Count(), Is.EqualTo(2));
145 | 			Assert.That(lambda.Types.ElementAt(0).Name, Is.EqualTo("Math"));
146 | 			Assert.That(lambda.Types.ElementAt(1).Name, Is.EqualTo("string"));
147 | 		}
148 | 
149 | 		public enum EnumCaseSensitive
150 | 		{
151 | 			Test = 1,
152 | 			// ReSharper disable once InconsistentNaming
153 | 			TEST = 2
154 | 		}
155 | 
156 | 		public enum MyCustomEnum
157 | 		{
158 | 			Value1,
159 | 			Value2
160 | 		}
161 | 
162 | 		private class MyDataContract
163 | 		{
164 | 			public MyDataContract(string name)
165 | 			{
166 | 				Name = name;
167 | 			}
168 | 
169 | 			public MyDataContract(int x, int y)
170 | 			{
171 | 				Name = string.Format("{0} - {1}", x, y);
172 | 			}
173 | 
174 | 			public string Name { get; set; }
175 | 		}
176 | 
177 | 		public class MyTestClass
178 | 		{
179 | 			private T _val;
180 | 			public MyTestClass(T val)
181 | 			{
182 | 				_val = val;
183 | 			}
184 | 
185 | 			public override string ToString()
186 | 			{
187 | 				if (_val == null)
188 | 					return "null";
189 | 
190 | 				return _val.ToString();
191 | 			}
192 | 		}
193 | 	}
194 | }
195 | 


--------------------------------------------------------------------------------
/src/DynamicExpresso.Core/Parsing/ParseSignatures.cs:
--------------------------------------------------------------------------------
  1 | using System;
  2 | using System.Collections.Generic;
  3 | using System.Linq;
  4 | using System.Reflection;
  5 | using DynamicExpresso.Reflection;
  6 | 
  7 | namespace DynamicExpresso.Parsing
  8 | {
  9 | 	/// 
 10 | 	/// Contains all the signatures for the binary and unary operators supported by DynamicExpresso.
 11 | 	/// It allows to reuse the existing method resolutions logic in .
 12 | 	/// 
 13 | 	internal static class ParseSignatures
 14 | 	{
 15 | 		private static MethodBase[] MakeUnarySignatures(params Type[] possibleOperandTypes)
 16 | 		{
 17 | 			var signatures = new MethodBase[possibleOperandTypes.Length];
 18 | 			for (var i = 0; i < possibleOperandTypes.Length; i++)
 19 | 			{
 20 | 				signatures[i] = new SimpleMethodSignature(possibleOperandTypes[i]);
 21 | 			}
 22 | 			return signatures;
 23 | 		}
 24 | 
 25 | 		private static MethodBase[] MakeBinarySignatures(IList<(Type, Type)> possibleOperandTypes)
 26 | 		{
 27 | 			var signatures = new MethodBase[possibleOperandTypes.Count];
 28 | 			for (var i = 0; i < possibleOperandTypes.Count; i++)
 29 | 			{
 30 | 				var (left, right) = possibleOperandTypes[i];
 31 | 				signatures[i] = new SimpleMethodSignature(left, right);
 32 | 			}
 33 | 			return signatures;
 34 | 		}
 35 | 
 36 | 		/// 
 37 | 		/// Signatures for the binary logical operators.
 38 | 		/// 
 39 | 		public static MethodBase[] LogicalSignatures = MakeBinarySignatures(new[]
 40 | 		{
 41 | 			(typeof(bool),  typeof(bool) ),
 42 | 			(typeof(bool?), typeof(bool?)),
 43 | 		});
 44 | 
 45 | 		/// 
 46 | 		/// Signatures for the binary arithmetic operators.
 47 | 		/// 
 48 | 		public static MethodBase[] ArithmeticSignatures = MakeBinarySignatures(new[]
 49 | 		{
 50 | 			(typeof(int),      typeof(int)     ),
 51 | 			(typeof(uint),     typeof(uint)    ),
 52 | 			(typeof(long),     typeof(long)    ),
 53 | 			(typeof(ulong),    typeof(ulong)   ),
 54 | 			(typeof(float),    typeof(float)   ),
 55 | 			(typeof(double),   typeof(double)  ),
 56 | 			(typeof(decimal),  typeof(decimal) ),
 57 | 			(typeof(int?),     typeof(int?)    ),
 58 | 			(typeof(uint?),    typeof(uint?)   ),
 59 | 			(typeof(long?),    typeof(long?)   ),
 60 | 			(typeof(ulong?),   typeof(ulong?)  ),
 61 | 			(typeof(float?),   typeof(float?)  ),
 62 | 			(typeof(double?),  typeof(double?) ),
 63 | 			(typeof(decimal?), typeof(decimal?)),
 64 | 		});
 65 | 
 66 | 		/// 
 67 | 		/// Signatures for the binary relational operators.
 68 | 		/// 
 69 | 		public static MethodBase[] RelationalSignatures = ArithmeticSignatures.Concat(MakeBinarySignatures(new[]
 70 | 		{
 71 | 			(typeof(string),    typeof(string)   ),
 72 | 			(typeof(char),      typeof(char)     ),
 73 | 			(typeof(DateTime),  typeof(DateTime) ),
 74 | 			(typeof(TimeSpan),  typeof(TimeSpan) ),
 75 | 			(typeof(char?),     typeof(char?)    ),
 76 | 			(typeof(DateTime?), typeof(DateTime?)),
 77 | 			(typeof(TimeSpan?), typeof(TimeSpan?)),
 78 | 		})).ToArray();
 79 | 
 80 | 		/// 
 81 | 		/// Signatures for the binary equality operators.
 82 | 		/// 
 83 | 		public static MethodBase[] EqualitySignatures = RelationalSignatures.Concat(LogicalSignatures).ToArray();
 84 | 
 85 | 		/// 
 86 | 		/// Signatures for the binary + operators.
 87 | 		/// 
 88 | 		public static MethodBase[] AddSignatures = ArithmeticSignatures.Concat(MakeBinarySignatures(new[]
 89 | 		{
 90 | 			(typeof(DateTime),  typeof(TimeSpan) ),
 91 | 			(typeof(TimeSpan),  typeof(TimeSpan) ),
 92 | 			(typeof(DateTime?), typeof(TimeSpan?)),
 93 | 			(typeof(TimeSpan?), typeof(TimeSpan?)),
 94 | 		})).ToArray();
 95 | 
 96 | 		/// 
 97 | 		/// Signatures for the binary - operators.
 98 | 		/// 
 99 | 		public static MethodBase[] SubtractSignatures = AddSignatures.Concat(MakeBinarySignatures(new[]
100 | 		{
101 | 			(typeof(DateTime),  typeof(DateTime)),
102 | 			(typeof(DateTime?), typeof(DateTime?)),
103 | 		})).ToArray();
104 | 
105 | 		/// 
106 | 		/// Signatures for the unary - operators.
107 | 		/// 
108 | 		public static MethodBase[] NegationSignatures = MakeUnarySignatures(
109 | 			typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(float), typeof(double), typeof(decimal),
110 | 			typeof(int?), typeof(uint?), typeof(long?), typeof(ulong?), typeof(float?), typeof(double?), typeof(decimal?)
111 | 		);
112 | 
113 | 		/// 
114 | 		/// Signatures for the unary not (!) operator.
115 | 		/// 
116 | 		public static MethodBase[] NotSignatures = MakeUnarySignatures(typeof(bool), typeof(bool?));
117 | 
118 | 		/// 
119 | 		/// Signatures for the bitwise completement operators.
120 | 		/// 
121 | 		public static MethodBase[] BitwiseComplementSignatures = MakeUnarySignatures(
122 | 			typeof(int), typeof(uint), typeof(long), typeof(ulong),
123 | 			typeof(int?), typeof(uint?), typeof(long?), typeof(ulong?)
124 | 		);
125 | 
126 | 		/// 
127 | 		/// Signatures for the left and right shift operators.
128 | 		/// 
129 | 		public static MethodBase[] ShiftSignatures = MakeBinarySignatures(new[]
130 | 		{
131 | 			(typeof(int),   typeof(int) ),
132 | 			(typeof(uint),  typeof(int) ),
133 | 			(typeof(long),  typeof(int) ),
134 | 			(typeof(ulong), typeof(int) ),
135 | 			(typeof(int?),  typeof(int?) ),
136 | 			(typeof(uint?), typeof(int?) ),
137 | 			(typeof(long?), typeof(int?) ),
138 | 			(typeof(ulong?),typeof(int?) ),
139 | 		});
140 | 
141 | 		//interface IEnumerableSignatures
142 | 		//{
143 | 		//    void Where(bool predicate);
144 | 		//    void Any();
145 | 		//    void Any(bool predicate);
146 | 		//    void All(bool predicate);
147 | 		//    void Count();
148 | 		//    void Count(bool predicate);
149 | 		//    void Min(object selector);
150 | 		//    void Max(object selector);
151 | 		//    void Sum(int selector);
152 | 		//    void Sum(int? selector);
153 | 		//    void Sum(long selector);
154 | 		//    void Sum(long? selector);
155 | 		//    void Sum(float selector);
156 | 		//    void Sum(float? selector);
157 | 		//    void Sum(double selector);
158 | 		//    void Sum(double? selector);
159 | 		//    void Sum(decimal selector);
160 | 		//    void Sum(decimal? selector);
161 | 		//    void Average(int selector);
162 | 		//    void Average(int? selector);
163 | 		//    void Average(long selector);
164 | 		//    void Average(long? selector);
165 | 		//    void Average(float selector);
166 | 		//    void Average(float? selector);
167 | 		//    void Average(double selector);
168 | 		//    void Average(double? selector);
169 | 		//    void Average(decimal selector);
170 | 		//    void Average(decimal? selector);
171 | 		//}
172 | 	}
173 | }
174 | 


--------------------------------------------------------------------------------
/src/DynamicExpresso.Core/Lambda.cs:
--------------------------------------------------------------------------------
  1 | using System;
  2 | using System.Collections.Generic;
  3 | using System.Linq;
  4 | using System.Linq.Expressions;
  5 | using System.Reflection;
  6 | using System.Runtime.ExceptionServices;
  7 | using DynamicExpresso.Reflection;
  8 | using DynamicExpresso.Resources;
  9 | 
 10 | namespace DynamicExpresso
 11 | {
 12 | 	/// 
 13 | 	/// Represents a lambda expression that can be invoked. This class is thread safe.
 14 | 	/// 
 15 | 	public class Lambda
 16 | 	{
 17 | 		private readonly Expression _expression;
 18 | 		private readonly ParserArguments _parserArguments;
 19 | 		private readonly Lazy _delegate;
 20 | 
 21 | 		internal Lambda(Expression expression, ParserArguments parserArguments)
 22 | 		{
 23 | 			_expression = expression ?? throw new ArgumentNullException(nameof(expression));
 24 | 			_parserArguments = parserArguments ?? throw new ArgumentNullException(nameof(parserArguments));
 25 | 
 26 | 			// 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.
 27 | 			_delegate = new Lazy(() =>
 28 | 				Expression.Lambda(_expression, _parserArguments.UsedParameters.Select(p => p.Expression).ToArray()).Compile());
 29 | 		}
 30 | 
 31 | 		public Expression Expression { get { return _expression; } }
 32 | 		public bool CaseInsensitive { get { return _parserArguments.Settings.CaseInsensitive; } }
 33 | 		public string ExpressionText { get { return _parserArguments.ExpressionText; } }
 34 | 		public Type ReturnType { get { return Expression.Type; } }
 35 | 
 36 | 		/// 
 37 | 		/// Gets the parameters actually used in the expression parsed.
 38 | 		/// 
 39 | 		/// The used parameters.
 40 | 		[Obsolete("Use UsedParameters or DeclaredParameters")]
 41 | 		public IEnumerable Parameters { get { return _parserArguments.UsedParameters; } }
 42 | 
 43 | 		/// 
 44 | 		/// Gets the parameters actually used in the expression parsed.
 45 | 		/// 
 46 | 		/// The used parameters.
 47 | 		public IEnumerable UsedParameters { get { return _parserArguments.UsedParameters; } }
 48 | 		/// 
 49 | 		/// Gets the parameters declared when parsing the expression.
 50 | 		/// 
 51 | 		/// The declared parameters.
 52 | 		public IEnumerable DeclaredParameters { get { return _parserArguments.DeclaredParameters; } }
 53 | 
 54 | 		public IEnumerable Types { get { return _parserArguments.UsedTypes; } }
 55 | 		public IEnumerable Identifiers { get { return _parserArguments.UsedIdentifiers; } }
 56 | 
 57 | 		public object Invoke()
 58 | 		{
 59 | 			return InvokeWithUsedParameters(new object[0]);
 60 | 		}
 61 | 
 62 | 		public object Invoke(params Parameter[] parameters)
 63 | 		{
 64 | 			return Invoke((IEnumerable)parameters);
 65 | 		}
 66 | 
 67 | 		public object Invoke(IEnumerable parameters)
 68 | 		{
 69 | 			var args = (from usedParameter in UsedParameters
 70 | 						from actualParameter in parameters
 71 | 						where usedParameter.Name.Equals(actualParameter.Name, _parserArguments.Settings.KeyComparison)
 72 | 						select actualParameter.Value)
 73 | 				.ToArray();
 74 | 
 75 | 			return InvokeWithUsedParameters(args);
 76 | 		}
 77 | 
 78 | 		/// 
 79 | 		/// Invoke the expression with the given parameters values.
 80 | 		/// 
 81 | 		/// Order of parameters must be the same of the parameters used during parse (DeclaredParameters).
 82 | 		/// 
 83 | 		public object Invoke(params object[] args)
 84 | 		{
 85 | 			var parameters = new List();
 86 | 			var declaredParameters = DeclaredParameters.ToArray();
 87 | 
 88 | 			if (args != null)
 89 | 			{
 90 | 				if (declaredParameters.Length != args.Length)
 91 | 					throw new InvalidOperationException(ErrorMessages.ArgumentCountMismatch);
 92 | 
 93 | 				for (var i = 0; i < args.Length; i++)
 94 | 				{
 95 | 					var parameter = new Parameter(
 96 | 						declaredParameters[i].Name,
 97 | 						declaredParameters[i].Type,
 98 | 						args[i]);
 99 | 
100 | 					parameters.Add(parameter);
101 | 				}
102 | 			}
103 | 
104 | 			return Invoke(parameters);
105 | 		}
106 | 
107 | 		private object InvokeWithUsedParameters(object[] orderedArgs)
108 | 		{
109 | 			try
110 | 			{
111 | 				return _delegate.Value.DynamicInvoke(orderedArgs);
112 | 			}
113 | 			catch (TargetInvocationException exc)
114 | 			{
115 | 				if (exc.InnerException != null)
116 | 					ExceptionDispatchInfo.Capture(exc.InnerException).Throw();
117 | 
118 | 				throw;
119 | 			}
120 | 		}
121 | 
122 | 		public override string ToString()
123 | 		{
124 | 			return ExpressionText;
125 | 		}
126 | 
127 | 		/// 
128 | 		/// Generate the given delegate by compiling the lambda expression.
129 | 		/// 
130 | 		/// The delegate to generate. Delegate parameters must match the one defined when creating the expression, see UsedParameters.
131 | 		public TDelegate Compile()
132 | 		{
133 | 			var lambdaExpression = LambdaExpression();
134 | 			return lambdaExpression.Compile();
135 | 		}
136 | 
137 | 		[Obsolete("Use Compile()")]
138 | 		public TDelegate Compile(IEnumerable parameters)
139 | 		{
140 | 			var lambdaExpression = Expression.Lambda(_expression, parameters.Select(p => p.Expression).ToArray());
141 | 			return lambdaExpression.Compile();
142 | 		}
143 | 
144 | 		/// 
145 | 		/// Generate a lambda expression.
146 | 		/// 
147 | 		/// The lambda expression.
148 | 		/// The delegate to generate. Delegate parameters must match the one defined when creating the expression, see UsedParameters.
149 | 		public Expression LambdaExpression()
150 | 		{
151 | 			return Expression.Lambda(_expression, DeclaredParameters.Select(p => p.Expression).ToArray());
152 | 		}
153 | 
154 | 		internal LambdaExpression LambdaExpression(Type delegateType)
155 | 		{
156 | 			var parameterExpressions = DeclaredParameters.Select(p => p.Expression).ToArray();
157 | 			var types = delegateType.GetGenericArguments();
158 | 
159 | 			// return type
160 | 			if (delegateType.GetGenericTypeDefinition() == ReflectionExtensions.GetFuncType(parameterExpressions.Length))
161 | 				types[types.Length - 1] = _expression.Type;
162 | 
163 | 			var genericType = delegateType.GetGenericTypeDefinition();
164 | 			var inferredDelegateType = genericType.MakeGenericType(types);
165 | 			return Expression.Lambda(inferredDelegateType, _expression, parameterExpressions);
166 | 		}
167 | 	}
168 | }
169 | 


--------------------------------------------------------------------------------
/test/DynamicExpresso.UnitTest/OtherTests.cs:
--------------------------------------------------------------------------------
  1 | using System;
  2 | using System.Collections.Generic;
  3 | using System.Linq;
  4 | using System.Linq.Expressions;
  5 | using NUnit.Framework;
  6 | 
  7 | namespace DynamicExpresso.UnitTest
  8 | {
  9 | 	[TestFixture]
 10 | 	public class OtherTests
 11 | 	{
 12 | 		[Test]
 13 | 		public void Space_Characters_Are_Ignored()
 14 | 		{
 15 | 			var target = new Interpreter();
 16 | 
 17 | 			Assert.That(target.Eval("     45\t\t  + 1 \r  \n"), Is.EqualTo(46));
 18 | 		}
 19 | 
 20 | 		[Test]
 21 | 		public void Empty_Null_Withespace_Expression()
 22 | 		{
 23 | 			var target = new Interpreter();
 24 | 
 25 | 			Assert.That(target.Eval(""), Is.Null);
 26 | 			Assert.That(target.Parse("").ReturnType, Is.EqualTo(typeof(void)));
 27 | 
 28 | 			Assert.That(target.Eval(null), Is.Null);
 29 | 			Assert.That(target.Parse(null).ReturnType, Is.EqualTo(typeof(void)));
 30 | 
 31 | 			Assert.That(target.Eval("  \t\t\r\n  \t   "), Is.Null);
 32 | 			Assert.That(target.Parse("  \t\t\r\n  \t   ").ReturnType, Is.EqualTo(typeof(void)));
 33 | 		}
 34 | 
 35 | 		[Test]
 36 | 		public void Complex_expression()
 37 | 		{
 38 | 			var target = new Interpreter();
 39 | 
 40 | 			var x = new MyTestService();
 41 | 			var y = 5;
 42 | 			var parameters = new[] {
 43 | 							new Parameter("x", x.GetType(), x),
 44 | 							new Parameter("y", y.GetType(), y),
 45 | 							};
 46 | 
 47 | 			Assert.That(target.Eval("x.AProperty      >\t y && \r\n x.HelloWorld().Length == 10", parameters), Is.EqualTo(x.AProperty > y && x.HelloWorld().Length == 10));
 48 | 			Assert.That(target.Eval("x.AProperty * (4 + 65) / x.AProperty", parameters), Is.EqualTo(x.AProperty * (4 + 65) / x.AProperty));
 49 | 
 50 | 			Assert.That(target.Eval("Convert.ToString(x.AProperty * (4 + 65) / x.AProperty)", parameters), Is.EqualTo(Convert.ToString(x.AProperty * (4 + 65) / x.AProperty)));
 51 | 		}
 52 | 
 53 | 		[Test]
 54 | 		public void Parse_An_Expression_And_Invoke_It_With_Different_Parameters()
 55 | 		{
 56 | 			var service = new MyTestService();
 57 | 
 58 | 			var target = new Interpreter()
 59 | 													.SetVariable("service", service);
 60 | 
 61 | 			var func = target.Parse("x > 4 ? service.VoidMethod() : service.VoidMethod2()",
 62 | 															new Parameter("x", typeof(int)));
 63 | 
 64 | 			Assert.That(func.ReturnType, Is.EqualTo(typeof(void)));
 65 | 
 66 | 			Assert.That(service.VoidMethodCalled, Is.EqualTo(0));
 67 | 			Assert.That(service.VoidMethod2Called, Is.EqualTo(0));
 68 | 
 69 | 			func.Invoke(new Parameter("x", 5));
 70 | 			Assert.That(service.VoidMethodCalled, Is.EqualTo(1));
 71 | 			Assert.That(service.VoidMethod2Called, Is.EqualTo(0));
 72 | 
 73 | 			func.Invoke(new Parameter("x", 2));
 74 | 			Assert.That(service.VoidMethodCalled, Is.EqualTo(1));
 75 | 			Assert.That(service.VoidMethod2Called, Is.EqualTo(1));
 76 | 		}
 77 | 
 78 | 		[Test]
 79 | 		public void Should_Understand_ReturnType_Of_expressions()
 80 | 		{
 81 | 			var target = new Interpreter();
 82 | 
 83 | 			var x = new MyTestService();
 84 | 			var y = 5;
 85 | 			var parameters = new[] {
 86 | 							new Parameter("x", x.GetType(), x),
 87 | 							new Parameter("y", y.GetType(), y),
 88 | 							};
 89 | 
 90 | 			Assert.That(target.Parse("x.AProperty > y && x.HelloWorld().Length == 10", parameters).ReturnType, Is.EqualTo(typeof(bool)));
 91 | 			Assert.That(target.Parse("x.AProperty * (4 + 65) / x.AProperty", parameters).ReturnType, Is.EqualTo(typeof(int)));
 92 | 		}
 93 | 
 94 | 		[Test]
 95 | 		public void Execute_the_same_function_multiple_times()
 96 | 		{
 97 | 			var target = new Interpreter();
 98 | 
 99 | 			var functionX = target.Parse("Math.Pow(x, y) + 5",
100 | 													new Parameter("x", typeof(double)),
101 | 													new Parameter("y", typeof(double)));
102 | 
103 | 			Assert.That(functionX.Invoke(15, 12), Is.EqualTo(Math.Pow(15, 12) + 5));
104 | 			Assert.That(functionX.Invoke(5, 1), Is.EqualTo(Math.Pow(5, 1) + 5));
105 | 			Assert.That(functionX.Invoke(11, 8), Is.EqualTo(Math.Pow(11, 8) + 5));
106 | 			Assert.That(functionX.Invoke(new Parameter("x", 3.0),
107 | 																													new Parameter("y", 4.0)), Is.EqualTo(Math.Pow(3, 4) + 5));
108 | 			Assert.That(functionX.Invoke(new Parameter("x", 9.0),
109 | 																													new Parameter("y", 2.0)), Is.EqualTo(Math.Pow(9, 2) + 5));
110 | 			Assert.That(functionX.Invoke(new Parameter("x", 1.0),
111 | 																													new Parameter("y", 3.0)), Is.EqualTo(Math.Pow(1, 3) + 5));
112 | 		}
113 | 
114 | 		[Test]
115 | 		public void Linq_Where()
116 | 		{
117 | 			var customers = new List {
118 | 									new Customer() { Name = "David", Age = 31, Gender = 'M' },
119 | 									new Customer() { Name = "Mary", Age = 29, Gender = 'F' },
120 | 									new Customer() { Name = "Jack", Age = 2, Gender = 'M' },
121 | 									new Customer() { Name = "Marta", Age = 1, Gender = 'F' },
122 | 									new Customer() { Name = "Moses", Age = 120, Gender = 'M' },
123 | 									};
124 | 
125 | 			string whereExpression = "customer.Age > 18 && customer.Gender == 'F'";
126 | 
127 | 			var interpreter = new Interpreter();
128 | 			Func dynamicWhere = interpreter.ParseAsDelegate>(whereExpression, "customer");
129 | 
130 | 			Assert.That(customers.Where(dynamicWhere).Count(), Is.EqualTo(1));
131 | 		}
132 | 
133 | 		[Test]
134 | 		public void Linq_Where2()
135 | 		{
136 | 			var prices = new[] { 5, 8, 6, 2 };
137 | 
138 | 			var whereFunction = new Interpreter()
139 | 				.ParseAsDelegate>("arg > 5");
140 | 
141 | 			Assert.That(prices.Where(whereFunction).Count(), Is.EqualTo(2));
142 | 		}
143 | 
144 | 		[Test]
145 | 		public void Linq_Queryable_Expression_Where()
146 | 		{
147 | 			IQueryable customers = (new List {
148 | 				new Customer() { Name = "David", Age = 31, Gender = 'M' },
149 | 				new Customer() { Name = "Mary", Age = 29, Gender = 'F' },
150 | 				new Customer() { Name = "Jack", Age = 2, Gender = 'M' },
151 | 				new Customer() { Name = "Marta", Age = 1, Gender = 'F' },
152 | 				new Customer() { Name = "Moses", Age = 120, Gender = 'M' },
153 | 			}).AsQueryable();
154 | 
155 | 			string whereExpression = "customer.Age > 18 && customer.Gender == 'F'";
156 | 
157 | 			var interpreter = new Interpreter();
158 | 			Expression> expression = interpreter.ParseAsExpression>(whereExpression, "customer");
159 | 
160 | 			Assert.That(customers.Where(expression).Count(), Is.EqualTo(1));
161 | 		}
162 | 
163 | 		[Test]
164 | 		public void Multiple_Parentheses_Math_Expression()
165 | 		{
166 | 			var interpreter = new Interpreter();
167 | 
168 | 			Assert.That(interpreter.Eval("2 + ((1 + 5) * (2 - 1))"), Is.EqualTo(2 + ((1 + 5) * (2 - 1))));
169 | 			Assert.That(interpreter.Eval("2 + ((((1 + 5))) * (((2 - 1))+5.5))"), Is.EqualTo(2 + ((((1 + 5))) * (((2 - 1)) + 5.5))));
170 | 		}
171 | 
172 | 		[Test]
173 | 		public void Multiple_Parentheses_Cast_Expression()
174 | 		{
175 | 			var interpreter = new Interpreter();
176 | 
177 | 			Assert.That(interpreter.Eval("((double)5).GetType().Name"), Is.EqualTo(((double)5).GetType().Name));
178 | 		}
179 | 
180 | 		private class MyTestService
181 | 		{
182 | 			public DateTime AField = DateTime.Now;
183 | 			public DateTime AFIELD = DateTime.UtcNow;
184 | 
185 | 			public int AProperty
186 | 			{
187 | 				get { return 769; }
188 | 			}
189 | 
190 | 			public int APROPERTY
191 | 			{
192 | 				get { return 887; }
193 | 			}
194 | 
195 | 			public string HelloWorld()
196 | 			{
197 | 				return "Ciao mondo";
198 | 			}
199 | 
200 | 			public string HELLOWORLD()
201 | 			{
202 | 				return "HELLO";
203 | 			}
204 | 
205 | 			public int VoidMethodCalled { get; set; }
206 | 			public void VoidMethod()
207 | 			{
208 | 				System.Diagnostics.Debug.WriteLine("VoidMethod called");
209 | 				VoidMethodCalled++;
210 | 			}
211 | 
212 | 			public int VoidMethod2Called { get; set; }
213 | 			public void VoidMethod2()
214 | 			{
215 | 				System.Diagnostics.Debug.WriteLine("VoidMethod2 called");
216 | 				VoidMethod2Called++;
217 | 			}
218 | 		}
219 | 
220 | 		private class Customer
221 | 		{
222 | 			public string Name { get; set; }
223 | 			public int Age { get; set; }
224 | 			public char Gender { get; set; }
225 | 		}
226 | 	}
227 | }
228 | 


--------------------------------------------------------------------------------
/test/DynamicExpresso.UnitTest/VariablesTest.cs:
--------------------------------------------------------------------------------
  1 | using System;
  2 | using System.Linq;
  3 | using System.Linq.Expressions;
  4 | using System.Reflection;
  5 | using DynamicExpresso.Exceptions;
  6 | using NUnit.Framework;
  7 | 
  8 | namespace DynamicExpresso.UnitTest
  9 | {
 10 | 	[TestFixture]
 11 | 	public class VariablesTest
 12 | 	{
 13 | 		[Test]
 14 | 		public void Cannot_create_variables_with_reserved_keywords()
 15 | 		{
 16 | 			var target = new Interpreter();
 17 | 
 18 | 			foreach (var keyword in LanguageConstants.ReservedKeywords)
 19 | 				Assert.Throws(() => target.SetVariable(keyword, 1));
 20 | 		}
 21 | 
 22 | 		[Test]
 23 | 		public void Can_create_variables_that_override_known_types()
 24 | 		{
 25 | 			// Note that in C# some of these keywords are not permitted, like `bool`,
 26 | 			// but other , like `Boolean` is permitted. (c# keywords are not permitted, .NET types yes)
 27 | 			// In my case are all considered in the same way, so now are permitted.
 28 | 			// But in real case scenarios try to use variables names with a more ubiquitous name to reduce possible conflict for the future.
 29 | 
 30 | 			var target = new Interpreter()
 31 | 				.SetVariable("bool", 1) // this in c# is not permitted
 32 | 				.SetVariable("Int32", 2)
 33 | 				.SetVariable("Math", 3);
 34 | 
 35 | 			Assert.That(target.Eval("bool"), Is.EqualTo(1));
 36 | 			Assert.That(target.Eval("Int32"), Is.EqualTo(2));
 37 | 			Assert.That(target.Eval("Math"), Is.EqualTo(3));
 38 | 		}
 39 | 
 40 | 		[Test]
 41 | 		public void Assign_and_use_variables()
 42 | 		{
 43 | 			var target = new Interpreter()
 44 | 											.SetVariable("myk", 23);
 45 | 
 46 | 			Assert.That(target.Eval("myk"), Is.EqualTo(23));
 47 | 			Assert.That(target.Parse("myk").ReturnType, Is.EqualTo(typeof(int)));
 48 | 		}
 49 | 
 50 | 		[Test]
 51 | 		public void Variables_by_default_are_case_sensitive()
 52 | 		{
 53 | 			var target = new Interpreter()
 54 | 											.SetVariable("x", 23)
 55 | 											.SetVariable("X", 50);
 56 | 
 57 | 			Assert.That(target.Eval("x"), Is.EqualTo(23));
 58 | 			Assert.That(target.Eval("X"), Is.EqualTo(50));
 59 | 		}
 60 | 
 61 | 		[Test]
 62 | 		public void Variables_can_be_case_insensitive()
 63 | 		{
 64 | 			var target = new Interpreter(InterpreterOptions.DefaultCaseInsensitive)
 65 | 											.SetVariable("x", 23);
 66 | 
 67 | 			Assert.That(target.Eval("x"), Is.EqualTo(23));
 68 | 			Assert.That(target.Eval("X"), Is.EqualTo(23));
 69 | 		}
 70 | 
 71 | 		[Test]
 72 | 		public void Variables_can_be_overwritten()
 73 | 		{
 74 | 			var target = new Interpreter()
 75 | 											.SetVariable("myk", 23);
 76 | 
 77 | 			Assert.That(target.Eval("myk"), Is.EqualTo(23));
 78 | 
 79 | 			target.SetVariable("myk", 3489);
 80 | 
 81 | 			Assert.That(target.Eval("myk"), Is.EqualTo(3489));
 82 | 
 83 | 			Assert.That(target.Parse("myk").ReturnType, Is.EqualTo(typeof(int)));
 84 | 		}
 85 | 
 86 | 		[Test]
 87 | 		public void Variables_can_be_overwritten_in_a_case_insensitive_setting()
 88 | 		{
 89 | 			var target = new Interpreter(InterpreterOptions.DefaultCaseInsensitive)
 90 | 											.SetVariable("myk", 23);
 91 | 
 92 | 			Assert.That(target.Eval("myk"), Is.EqualTo(23));
 93 | 
 94 | 			target.SetVariable("MYK", 3489);
 95 | 
 96 | 			Assert.That(target.Eval("myk"), Is.EqualTo(3489));
 97 | 
 98 | 			Assert.That(target.Parse("myk").ReturnType, Is.EqualTo(typeof(int)));
 99 | 		}
100 | 
101 | 		[Test]
102 | 		public void Null_Variables()
103 | 		{
104 | 			var target = new Interpreter()
105 | 											.SetVariable("myk", null);
106 | 
107 | 			Assert.That(target.Eval("myk"), Is.Null);
108 | 			Assert.That(target.Eval("myk == null"), Is.EqualTo(true));
109 | 			Assert.That(target.Parse("myk").ReturnType, Is.EqualTo(typeof(object)));
110 | 		}
111 | 
112 | 		[Test]
113 | 		public void Null_Variables_With_Type_Specified()
114 | 		{
115 | 			var target = new Interpreter()
116 | 											.SetVariable("myk", null, typeof(string));
117 | 
118 | 			Assert.That(target.Eval("myk"), Is.Null);
119 | 			Assert.That(target.Eval("myk == null"), Is.EqualTo(true));
120 | 			Assert.That(target.Parse("myk").ReturnType, Is.EqualTo(typeof(string)));
121 | 		}
122 | 
123 | 		[Test]
124 | 		public void Keywords_with_lambda()
125 | 		{
126 | 			Expression> pow = (x, y) => Math.Pow(x, y);
127 | 			var target = new Interpreter()
128 | 									.SetExpression("pow", pow);
129 | 
130 | 			Assert.That(target.Eval("pow(3, 2)"), Is.EqualTo(9.0));
131 | 		}
132 | 
133 | 		[Test]
134 | 		public void Keywords_with_delegate()
135 | 		{
136 | 			Func pow = (x, y) => Math.Pow(x, y);
137 | 			var target = new Interpreter()
138 | 									.SetFunction("pow", pow);
139 | 
140 | 			Assert.That(target.Eval("pow(3, 2)"), Is.EqualTo(9.0));
141 | 		}
142 | 
143 | 		[Test]
144 | 		public void Keywords_with_same_overload_twice()
145 | 		{
146 | 			Func pow = (x, y) => Math.Pow(x, y);
147 | 			var target = new Interpreter()
148 | 									.SetFunction("pow", pow)
149 | 									.SetFunction("pow", pow);
150 | 
151 | 			Assert.That(target.Eval("pow(3, 2)"), Is.EqualTo(9.0));
152 | 		}
153 | 
154 | 		[Test]
155 | 		public void Replace_same_overload_signature()
156 | 		{
157 | 			Func f1 = d => 1;
158 | 			Func f2 = d => 2;
159 | 
160 | 			var target = new Interpreter()
161 | 									.SetFunction("f", f1)
162 | 									.SetFunction("f", f2);
163 | 
164 | 			// f2 should override the f1 registration, because both delegates have the same signature
165 | 			Assert.That(target.Eval("f(0d)"), Is.EqualTo(2));
166 | 		}
167 | 
168 | 		[Test]
169 | 		public void Keywords_with_invalid_delegate_call()
170 | 		{
171 | 			Func pow = (x, y) => Math.Pow(x, y);
172 | 			var target = new Interpreter()
173 | 									.SetFunction("pow", pow);
174 | 
175 | 			Assert.Throws(() => target.Eval("pow(3)"));
176 | 		}
177 | 
178 | 		[Test]
179 | 		public void Keywords_with_overloaded_delegates()
180 | 		{
181 | 			Func roundFunction1 = (userNumber) => Math.Round(userNumber);
182 | 			Func roundFunction2 = (userNumber, decimals) => Math.Round(userNumber, decimals);
183 | 
184 | 			var interpreter = new Interpreter();
185 | 			interpreter.SetFunction("ROUND", roundFunction1);
186 | 			interpreter.SetFunction("ROUND", roundFunction2);
187 | 
188 | 			Assert.That(interpreter.Eval("ROUND(3.12789M, 2)"), Is.EqualTo(3.13M));
189 | 			Assert.That(interpreter.Eval("ROUND(3.12789M)"), Is.EqualTo(3M));
190 | 		}
191 | 
192 | 		[Test]
193 | 		public void Keywords_with_ambiguous_delegates()
194 | 		{
195 | 			Func ambiguous1 = (val) => val;
196 | 			Func ambiguous2 = (val) => "integer";
197 | 
198 | 			var interpreter = new Interpreter();
199 | 			interpreter.SetFunction("MyFunc", ambiguous1);
200 | 			interpreter.SetFunction("MyFunc", ambiguous2);
201 | 
202 | 			// ambiguous call: null can either be a string or an object
203 | 			// note: if there's no ambiguous exception, it means that the resolution
204 | 			// lifted the parameters from the string overload, which prevented the int? overload 
205 | 			// from being considered
206 | 			Assert.Throws(() => interpreter.Eval("MyFunc(null)"));
207 | 
208 | 			// call resolved to the string overload
209 | 			Assert.That(interpreter.Eval("MyFunc(\"test\")"), Is.EqualTo("test"));
210 | 		}
211 | 
212 | 		[Test]
213 | 		public void Keywords_with_non_ambiguous_delegates()
214 | 		{
215 | 			Func ambiguous1 = (val) => "double";
216 | 			Func ambiguous2 = (val) => "integer";
217 | 
218 | 			var interpreter = new Interpreter();
219 | 			interpreter.SetFunction("MyFunc", ambiguous1);
220 | 			interpreter.SetFunction("MyFunc", ambiguous2);
221 | 
222 | 			// there should be no ambiguous exception: int can implicitly be converted to double, 
223 | 			// but there's a perfect match 
224 | 			Assert.That(interpreter.Eval("MyFunc(5)"), Is.EqualTo("integer"));
225 | 		}
226 | 
227 | 		[Test]
228 | 		public void Set_function_With_Object_Params()
229 | 		{
230 | 			var target = new Interpreter();
231 | 
232 | 			// import static method with params array 
233 | 			var methodInfo = typeof(VariablesTest).GetMethod("Sum", BindingFlags.Static | BindingFlags.NonPublic);
234 | 			var types = methodInfo.GetParameters().Select(p => p.ParameterType).Concat(new[] { methodInfo.ReturnType });
235 | 			var del = methodInfo.CreateDelegate(Expression.GetDelegateType(types.ToArray()));
236 | 			target.SetFunction(methodInfo.Name, del);
237 | 
238 | 			Assert.That(del.Method.GetParameters()[0].GetCustomAttribute(), Is.Not.Null);
239 | 
240 | 			var flags = BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance;
241 | 			var invokeMethod = (MethodInfo)(del.GetType().FindMembers(MemberTypes.Method, flags, Type.FilterName, "Invoke")[0]);
242 | 			Assert.That(invokeMethod.GetParameters()[0].GetCustomAttribute(), Is.Null); // should be not null!
243 | 
244 | 			// the imported Sum function can be called with any parameters
245 | 			Assert.That(target.Eval("Sum(1, 2, 3)"), Is.EqualTo(6));
246 | 		}
247 | 
248 | 		internal static int Sum(params int[] integers)
249 | 		{
250 | 			return integers.Sum();
251 | 		}
252 | 	}
253 | }
254 | 


--------------------------------------------------------------------------------
/test/DynamicExpresso.UnitTest/NullableTest.cs:
--------------------------------------------------------------------------------
  1 | using System;
  2 | using NUnit.Framework;
  3 | // ReSharper disable ConvertNullableToShortForm
  4 | // ReSharper disable PossibleNullReferenceException
  5 | 
  6 | // ReSharper disable ConvertToConstant.Local
  7 | // ReSharper disable BuiltInTypeReferenceStyle
  8 | // ReSharper disable SuggestVarOrType_BuiltInTypes
  9 | 
 10 | namespace DynamicExpresso.UnitTest
 11 | {
 12 | 	[TestFixture]
 13 | 	public class NullableTest
 14 | 	{
 15 | 		[Test]
 16 | 		public void NullableInt32_NullableInt32()
 17 | 		{
 18 | 			var a = 5;
 19 | 			var b = 43;
 20 | 
 21 | 			var interpreter = new Interpreter();
 22 | 			interpreter.SetVariable("a", a, typeof(Nullable));
 23 | 			interpreter.SetVariable("b", b, typeof(Nullable));
 24 | 			var expectedReturnType = typeof(Nullable);
 25 | 
 26 | 			// Addition
 27 | 			var expected = a + b;
 28 | 			var lambda = interpreter.Parse("a + b");
 29 | 			Assert.That(lambda.Invoke(), Is.EqualTo(expected));
 30 | 			Assert.That(lambda.ReturnType, Is.EqualTo(expectedReturnType));
 31 | 
 32 | 			// Subtraction
 33 | 			expected = a - b;
 34 | 			lambda = interpreter.Parse("a - b");
 35 | 			Assert.That(lambda.Invoke(), Is.EqualTo(expected));
 36 | 			Assert.That(lambda.ReturnType, Is.EqualTo(expectedReturnType));
 37 | 
 38 | 			// Division
 39 | 			expected = a / b;
 40 | 			lambda = interpreter.Parse("a / b");
 41 | 			Assert.That(lambda.Invoke(), Is.EqualTo(expected));
 42 | 			Assert.That(lambda.ReturnType, Is.EqualTo(expectedReturnType));
 43 | 
 44 | 			// Multiplication
 45 | 			expected = a * b;
 46 | 			lambda = interpreter.Parse("a * b");
 47 | 			Assert.That(lambda.Invoke(), Is.EqualTo(expected));
 48 | 			Assert.That(lambda.ReturnType, Is.EqualTo(expectedReturnType));
 49 | 		}
 50 | 
 51 | 		[Test]
 52 | 		public void NullableInt32_NullableInt32_with_left_null()
 53 | 		{
 54 | 			Nullable a = null;
 55 | 			var b = 43;
 56 | 
 57 | 			var interpreter = new Interpreter();
 58 | 			interpreter.SetVariable("a", a, typeof(Nullable));
 59 | 			interpreter.SetVariable("b", b, typeof(Nullable));
 60 | 			var expectedReturnType = typeof(Nullable);
 61 | 
 62 | 			// Addition
 63 | 			var expected = a + b;
 64 | 			var lambda = interpreter.Parse("a + b");
 65 | 			Assert.That(lambda.Invoke(), Is.EqualTo(expected));
 66 | 			Assert.That(lambda.ReturnType, Is.EqualTo(expectedReturnType));
 67 | 
 68 | 			// Subtraction
 69 | 			expected = a - b;
 70 | 			lambda = interpreter.Parse("a - b");
 71 | 			Assert.That(lambda.Invoke(), Is.EqualTo(expected));
 72 | 			Assert.That(lambda.ReturnType, Is.EqualTo(expectedReturnType));
 73 | 
 74 | 			// Division
 75 | 			expected = a / b;
 76 | 			lambda = interpreter.Parse("a / b");
 77 | 			Assert.That(lambda.Invoke(), Is.EqualTo(expected));
 78 | 			Assert.That(lambda.ReturnType, Is.EqualTo(expectedReturnType));
 79 | 
 80 | 			// Multiplication
 81 | 			expected = a * b;
 82 | 			lambda = interpreter.Parse("a * b");
 83 | 			Assert.That(lambda.Invoke(), Is.EqualTo(expected));
 84 | 			Assert.That(lambda.ReturnType, Is.EqualTo(expectedReturnType));
 85 | 		}
 86 | 
 87 | 		[Test]
 88 | 		public void NullableInt32_NullableInt32_with_right_null()
 89 | 		{
 90 | 			var a = 85;
 91 | 			Nullable b = null;
 92 | 
 93 | 			var interpreter = new Interpreter();
 94 | 			interpreter.SetVariable("a", a, typeof(Nullable));
 95 | 			interpreter.SetVariable("b", b, typeof(Nullable));
 96 | 			var expectedReturnType = typeof(Nullable);
 97 | 
 98 | 			// Addition
 99 | 			var expected = a + b;
100 | 			var lambda = interpreter.Parse("a + b");
101 | 			Assert.That(lambda.Invoke(), Is.EqualTo(expected));
102 | 			Assert.That(lambda.ReturnType, Is.EqualTo(expectedReturnType));
103 | 
104 | 			// Subtraction
105 | 			expected = a - b;
106 | 			lambda = interpreter.Parse("a - b");
107 | 			Assert.That(lambda.Invoke(), Is.EqualTo(expected));
108 | 			Assert.That(lambda.ReturnType, Is.EqualTo(expectedReturnType));
109 | 
110 | 			// Division
111 | 			expected = a / b;
112 | 			lambda = interpreter.Parse("a / b");
113 | 			Assert.That(lambda.Invoke(), Is.EqualTo(expected));
114 | 			Assert.That(lambda.ReturnType, Is.EqualTo(expectedReturnType));
115 | 
116 | 			// Multiplication
117 | 			expected = a * b;
118 | 			lambda = interpreter.Parse("a * b");
119 | 			Assert.That(lambda.Invoke(), Is.EqualTo(expected));
120 | 			Assert.That(lambda.ReturnType, Is.EqualTo(expectedReturnType));
121 | 		}
122 | 
123 | 		[Test]
124 | 		public void NullableDouble_NullableDouble()
125 | 		{
126 | 			var a = 5.7;
127 | 			var b = 43.2;
128 | 
129 | 			var interpreter = new Interpreter();
130 | 			interpreter.SetVariable("a", a, typeof(Nullable));
131 | 			interpreter.SetVariable("b", b, typeof(Nullable));
132 | 			var expectedReturnType = typeof(Nullable);
133 | 
134 | 			// Addition
135 | 			var expected = a + b;
136 | 			var lambda = interpreter.Parse("a + b");
137 | 			Assert.That(lambda.Invoke(), Is.EqualTo(expected));
138 | 			Assert.That(lambda.ReturnType, Is.EqualTo(expectedReturnType));
139 | 
140 | 			// Subtraction
141 | 			expected = a - b;
142 | 			lambda = interpreter.Parse("a - b");
143 | 			Assert.That(lambda.Invoke(), Is.EqualTo(expected));
144 | 			Assert.That(lambda.ReturnType, Is.EqualTo(expectedReturnType));
145 | 
146 | 			// Division
147 | 			expected = a / b;
148 | 			lambda = interpreter.Parse("a / b");
149 | 			Assert.That(lambda.Invoke(), Is.EqualTo(expected));
150 | 			Assert.That(lambda.ReturnType, Is.EqualTo(expectedReturnType));
151 | 
152 | 			// Multiplication
153 | 			expected = a * b;
154 | 			lambda = interpreter.Parse("a * b");
155 | 			Assert.That(lambda.Invoke(), Is.EqualTo(expected));
156 | 			Assert.That(lambda.ReturnType, Is.EqualTo(expectedReturnType));
157 | 		}
158 | 
159 | 		[Test]
160 | 		public void Int32_NullableDouble()
161 | 		{
162 | 			var a = 5;
163 | 			var b = 43.5;
164 | 
165 | 			var interpreter = new Interpreter();
166 | 			interpreter.SetVariable("a", a, typeof(Int32));
167 | 			interpreter.SetVariable("b", b, typeof(Nullable));
168 | 			var expectedReturnType = typeof(Nullable);
169 | 
170 | 			// Addition
171 | 			var expected = a + b;
172 | 			var lambda = interpreter.Parse("a + b");
173 | 			Assert.That(lambda.Invoke(), Is.EqualTo(expected));
174 | 			Assert.That(lambda.ReturnType, Is.EqualTo(expectedReturnType));
175 | 
176 | 			// Subtraction
177 | 			expected = a - b;
178 | 			lambda = interpreter.Parse("a - b");
179 | 			Assert.That(lambda.Invoke(), Is.EqualTo(expected));
180 | 			Assert.That(lambda.ReturnType, Is.EqualTo(expectedReturnType));
181 | 
182 | 			// Division
183 | 			expected = a / b;
184 | 			lambda = interpreter.Parse("a / b");
185 | 			Assert.That(lambda.Invoke(), Is.EqualTo(expected));
186 | 			Assert.That(lambda.ReturnType, Is.EqualTo(expectedReturnType));
187 | 
188 | 			// Multiplication
189 | 			expected = a * b;
190 | 			lambda = interpreter.Parse("a * b");
191 | 			Assert.That(lambda.Invoke(), Is.EqualTo(expected));
192 | 			Assert.That(lambda.ReturnType, Is.EqualTo(expectedReturnType));
193 | 		}
194 | 
195 | 		[Test]
196 | 		public void NullableInt32_Double()
197 | 		{
198 | 			var a = 5;
199 | 			var b = 43.5;
200 | 
201 | 			var interpreter = new Interpreter();
202 | 			interpreter.SetVariable("a", a, typeof(Nullable));
203 | 			interpreter.SetVariable("b", b, typeof(Double));
204 | 			var expectedReturnType = typeof(Nullable);
205 | 
206 | 			// Addition
207 | 			var expected = a + b;
208 | 			var lambda = interpreter.Parse("a + b");
209 | 			Assert.That(lambda.Invoke(), Is.EqualTo(expected));
210 | 			Assert.That(lambda.ReturnType, Is.EqualTo(expectedReturnType));
211 | 
212 | 			// Subtraction
213 | 			expected = a - b;
214 | 			lambda = interpreter.Parse("a - b");
215 | 			Assert.That(lambda.Invoke(), Is.EqualTo(expected));
216 | 			Assert.That(lambda.ReturnType, Is.EqualTo(expectedReturnType));
217 | 
218 | 			// Division
219 | 			expected = a / b;
220 | 			lambda = interpreter.Parse("a / b");
221 | 			Assert.That(lambda.Invoke(), Is.EqualTo(expected));
222 | 			Assert.That(lambda.ReturnType, Is.EqualTo(expectedReturnType));
223 | 
224 | 			// Multiplication
225 | 			expected = a * b;
226 | 			lambda = interpreter.Parse("a * b");
227 | 			Assert.That(lambda.Invoke(), Is.EqualTo(expected));
228 | 			Assert.That(lambda.ReturnType, Is.EqualTo(expectedReturnType));
229 | 		}
230 | 
231 | 		[Test]
232 | 		public void NullableDateTimeOffset_DatetimeOffset()
233 | 		{
234 | 			var a = DateTimeOffset.Now;
235 | 			DateTimeOffset? b = DateTimeOffset.Now.AddDays(1);
236 | 			var c = b.Value;
237 | 
238 | 			var interpreter = new Interpreter();
239 | 			interpreter.SetVariable("a", a, typeof(DateTimeOffset));
240 | 			interpreter.SetVariable("b", b, typeof(DateTimeOffset?));
241 | 			interpreter.SetVariable("c", c, typeof(DateTimeOffset));
242 | 			var expectedReturnType = typeof(bool);
243 | 
244 | 			var expected = a < b;
245 | 			var lambda = interpreter.Parse("a < b");
246 | 			Assert.That(lambda.Invoke(), Is.EqualTo(expected));
247 | 			Assert.That(lambda.ReturnType, Is.EqualTo(expectedReturnType));
248 | 
249 | 			expected = a > b;
250 | 			lambda = interpreter.Parse("a > b");
251 | 			Assert.That(lambda.Invoke(), Is.EqualTo(expected));
252 | 			Assert.That(lambda.ReturnType, Is.EqualTo(expectedReturnType));
253 | 
254 | 			expected = a == b;
255 | 			lambda = interpreter.Parse("a == b");
256 | 			Assert.That(lambda.Invoke(), Is.EqualTo(expected));
257 | 			Assert.That(lambda.ReturnType, Is.EqualTo(expectedReturnType));
258 | 
259 | 			expected = a != b;
260 | 			lambda = interpreter.Parse("a != b");
261 | 			Assert.That(lambda.Invoke(), Is.EqualTo(expected));
262 | 			Assert.That(lambda.ReturnType, Is.EqualTo(expectedReturnType));
263 | 
264 | 			expected = b == c;
265 | 			lambda = interpreter.Parse("b == b");
266 | 			Assert.That(lambda.Invoke(), Is.EqualTo(expected));
267 | 			Assert.That(lambda.ReturnType, Is.EqualTo(expectedReturnType));
268 | 
269 | 			expected = b != c;
270 | 			lambda = interpreter.Parse("b != c");
271 | 			Assert.That(lambda.Invoke(), Is.EqualTo(expected));
272 | 			Assert.That(lambda.ReturnType, Is.EqualTo(expectedReturnType));
273 | 
274 | 			lambda = interpreter.Parse("a - b");
275 | 			Assert.That(lambda.Invoke(), Is.EqualTo(a - b));
276 | 			Assert.That(lambda.ReturnType, Is.EqualTo(typeof(TimeSpan?)));
277 | 
278 | 			b = null;
279 | 			interpreter.SetVariable("b", b, typeof(DateTimeOffset?));
280 | 			lambda = interpreter.Parse("a - b");
281 | 			Assert.That(lambda.Invoke(), Is.EqualTo(a - b));
282 | 			Assert.That(lambda.ReturnType, Is.EqualTo(typeof(TimeSpan?)));
283 | 		}
284 | 	}
285 | }
286 | 


--------------------------------------------------------------------------------
/src/DynamicExpresso.Core/Reflection/TypeUtils.cs:
--------------------------------------------------------------------------------
  1 | using System;
  2 | using System.Collections.Generic;
  3 | using System.Dynamic;
  4 | using System.Linq;
  5 | using System.Linq.Expressions;
  6 | 
  7 | namespace DynamicExpresso.Reflection
  8 | {
  9 | 	internal static class TypeUtils
 10 | 	{
 11 | 		public static bool IsNullableType(Type type)
 12 | 		{
 13 | 			return TryGetNonNullableType(type, out _);
 14 | 		}
 15 | 
 16 | 		public static bool IsDynamicType(Type type)
 17 | 		{
 18 | 			return typeof(IDynamicMetaObjectProvider).IsAssignableFrom(type);
 19 | 		}
 20 | 
 21 | 		/// 
 22 | 		/// Returns true if  is nullable, and set  to the underlying type for value types.
 23 | 		/// In case of reference types, the method will return false, and  is set to null.
 24 | 		/// 
 25 | 		public static bool TryGetNonNullableType(Type type, out Type nonNullableType)
 26 | 		{
 27 | 			nonNullableType = Nullable.GetUnderlyingType(type);
 28 | 			return nonNullableType != null;
 29 | 		}
 30 | 
 31 | 		/// 
 32 | 		/// If  is a nullable value type, returns the underlying type.
 33 | 		/// If  is a reference type, or a non-nullable value type, the method will return .
 34 | 		private static Type GetNonNullableType(Type type)
 35 | 		{
 36 | 			return TryGetNonNullableType(type, out var underlyingType) ? underlyingType : type;
 37 | 		}
 38 | 
 39 | 		public static Type MakeNullable(Type type)
 40 | 		{
 41 | 			return typeof(Nullable<>).MakeGenericType(type);
 42 | 		}
 43 | 
 44 | 		public static string GetTypeName(Type type)
 45 | 		{
 46 | 			var baseType = GetNonNullableType(type);
 47 | 			var s = baseType.Name;
 48 | 			if (type != baseType) s += '?';
 49 | 			return s;
 50 | 		}
 51 | 
 52 | 		public static bool IsNumericType(Type type)
 53 | 		{
 54 | 			return GetNumericTypeKind(type) != 0;
 55 | 		}
 56 | 
 57 | 		public static bool IsSignedIntegralType(Type type)
 58 | 		{
 59 | 			return GetNumericTypeKind(type) == 2;
 60 | 		}
 61 | 
 62 | 		public static bool IsUnsignedIntegralType(Type type)
 63 | 		{
 64 | 			return GetNumericTypeKind(type) == 3;
 65 | 		}
 66 | 
 67 | 		private static int GetNumericTypeKind(Type type)
 68 | 		{
 69 | 			type = GetNonNullableType(type);
 70 | 			if (type.IsEnum) return 0;
 71 | 			switch (Type.GetTypeCode(type))
 72 | 			{
 73 | 				case TypeCode.Char:
 74 | 				case TypeCode.Single:
 75 | 				case TypeCode.Double:
 76 | 				case TypeCode.Decimal:
 77 | 					return 1;
 78 | 				case TypeCode.SByte:
 79 | 				case TypeCode.Int16:
 80 | 				case TypeCode.Int32:
 81 | 				case TypeCode.Int64:
 82 | 					return 2;
 83 | 				case TypeCode.Byte:
 84 | 				case TypeCode.UInt16:
 85 | 				case TypeCode.UInt32:
 86 | 				case TypeCode.UInt64:
 87 | 					return 3;
 88 | 				default:
 89 | 					return 0;
 90 | 			}
 91 | 		}
 92 | 
 93 | 		public static bool IsCompatibleWith(Type source, Type target)
 94 | 		{
 95 | 			if (source == target)
 96 | 			{
 97 | 				return true;
 98 | 			}
 99 | 
100 | 			if (target.IsGenericParameter)
101 | 			{
102 | 				return true;
103 | 			}
104 | 
105 | 			if (!target.IsValueType)
106 | 			{
107 | 				return target.IsAssignableFrom(source);
108 | 			}
109 | 			var st = GetNonNullableType(source);
110 | 			var tt = GetNonNullableType(target);
111 | 			if (st != source && tt == target) return false;
112 | 			var sc = st.IsEnum ? TypeCode.Object : Type.GetTypeCode(st);
113 | 			var tc = tt.IsEnum ? TypeCode.Object : Type.GetTypeCode(tt);
114 | 			switch (sc)
115 | 			{
116 | 				case TypeCode.SByte:
117 | 					switch (tc)
118 | 					{
119 | 						case TypeCode.SByte:
120 | 						case TypeCode.Int16:
121 | 						case TypeCode.Int32:
122 | 						case TypeCode.Int64:
123 | 						case TypeCode.Single:
124 | 						case TypeCode.Double:
125 | 						case TypeCode.Decimal:
126 | 							return true;
127 | 					}
128 | 					break;
129 | 				case TypeCode.Byte:
130 | 					switch (tc)
131 | 					{
132 | 						case TypeCode.Byte:
133 | 						case TypeCode.Int16:
134 | 						case TypeCode.UInt16:
135 | 						case TypeCode.Int32:
136 | 						case TypeCode.UInt32:
137 | 						case TypeCode.Int64:
138 | 						case TypeCode.UInt64:
139 | 						case TypeCode.Single:
140 | 						case TypeCode.Double:
141 | 						case TypeCode.Decimal:
142 | 							return true;
143 | 					}
144 | 					break;
145 | 				case TypeCode.Int16:
146 | 					switch (tc)
147 | 					{
148 | 						case TypeCode.Int16:
149 | 						case TypeCode.Int32:
150 | 						case TypeCode.Int64:
151 | 						case TypeCode.Single:
152 | 						case TypeCode.Double:
153 | 						case TypeCode.Decimal:
154 | 							return true;
155 | 					}
156 | 					break;
157 | 				case TypeCode.UInt16:
158 | 					switch (tc)
159 | 					{
160 | 						case TypeCode.UInt16:
161 | 						case TypeCode.Int32:
162 | 						case TypeCode.UInt32:
163 | 						case TypeCode.Int64:
164 | 						case TypeCode.UInt64:
165 | 						case TypeCode.Single:
166 | 						case TypeCode.Double:
167 | 						case TypeCode.Decimal:
168 | 							return true;
169 | 					}
170 | 					break;
171 | 				case TypeCode.Int32:
172 | 					switch (tc)
173 | 					{
174 | 						case TypeCode.Int32:
175 | 						case TypeCode.Int64:
176 | 						case TypeCode.Single:
177 | 						case TypeCode.Double:
178 | 						case TypeCode.Decimal:
179 | 							return true;
180 | 					}
181 | 					break;
182 | 				case TypeCode.UInt32:
183 | 					switch (tc)
184 | 					{
185 | 						case TypeCode.UInt32:
186 | 						case TypeCode.Int64:
187 | 						case TypeCode.UInt64:
188 | 						case TypeCode.Single:
189 | 						case TypeCode.Double:
190 | 						case TypeCode.Decimal:
191 | 							return true;
192 | 					}
193 | 					break;
194 | 				case TypeCode.Int64:
195 | 					switch (tc)
196 | 					{
197 | 						case TypeCode.Int64:
198 | 						case TypeCode.Single:
199 | 						case TypeCode.Double:
200 | 						case TypeCode.Decimal:
201 | 							return true;
202 | 					}
203 | 					break;
204 | 				case TypeCode.UInt64:
205 | 					switch (tc)
206 | 					{
207 | 						case TypeCode.UInt64:
208 | 						case TypeCode.Single:
209 | 						case TypeCode.Double:
210 | 						case TypeCode.Decimal:
211 | 							return true;
212 | 					}
213 | 					break;
214 | 				case TypeCode.Single:
215 | 					switch (tc)
216 | 					{
217 | 						case TypeCode.Single:
218 | 						case TypeCode.Double:
219 | 							return true;
220 | 					}
221 | 					break;
222 | 				default:
223 | 					if (st == tt) return true;
224 | 					break;
225 | 			}
226 | 			return false;
227 | 		}
228 | 
229 | 		// Return 1 if s -> t1 is a better conversion than s -> t2
230 | 		// Return -1 if s -> t2 is a better conversion than s -> t1
231 | 		// Return 0 if neither conversion is better
232 | 		public static int CompareConversions(Type s, Type t1, Type t2)
233 | 		{
234 | 			if (t1 == t2) return 0;
235 | 			if (s == t1) return 1;
236 | 			if (s == t2) return -1;
237 | 
238 | 			var assignableT1 = t1.IsAssignableFrom(s);
239 | 			var assignableT2 = t2.IsAssignableFrom(s);
240 | 			if (assignableT1 && !assignableT2) return 1;
241 | 			if (assignableT2 && !assignableT1) return -1;
242 | 
243 | 			var compatibleT1T2 = IsCompatibleWith(t1, t2);
244 | 			var compatibleT2T1 = IsCompatibleWith(t2, t1);
245 | 			if (compatibleT1T2 && !compatibleT2T1) return 1;
246 | 			if (compatibleT2T1 && !compatibleT1T2) return -1;
247 | 
248 | 			if (IsSignedIntegralType(t1) && IsUnsignedIntegralType(t2)) return 1;
249 | 			if (IsSignedIntegralType(t2) && IsUnsignedIntegralType(t1)) return -1;
250 | 
251 | 			return 0;
252 | 		}
253 | 
254 | 		/// 
255 | 		/// Returns null if  is an Array type.  Needed because the  lookup methods fail with a  if the array type is used.
256 | 		/// Everything still miraculously works on the array if null is given for the type.
257 | 		/// 
258 | 		/// 
259 | 		/// 
260 | 		public static Type RemoveArrayType(Type t)
261 | 		{
262 | 			if (t == null || t.IsArray)
263 | 			{
264 | 				return null;
265 | 			}
266 | 			return t;
267 | 		}
268 | 
269 | 		// from http://stackoverflow.com/a/1075059/209727
270 | 		public static Type FindAssignableGenericType(Type givenType, Type constructedGenericType)
271 | 		{
272 | 			var genericTypeDefinition = constructedGenericType.GetGenericTypeDefinition();
273 | 			var interfaceTypes = givenType.GetInterfaces();
274 | 
275 | 			foreach (var it in interfaceTypes)
276 | 			{
277 | 				if (it.IsGenericType && it.GetGenericTypeDefinition() == genericTypeDefinition)
278 | 				{
279 | 					return it;
280 | 				}
281 | 			}
282 | 
283 | 			if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericTypeDefinition)
284 | 			{
285 | 				// the given type has the same generic type of the fully constructed generic type
286 | 				//  => check if the generic arguments are compatible (e.g. Nullable and Nullable: int is not compatible with DateTime)
287 | 				var givenTypeGenericsArgs = givenType.GenericTypeArguments;
288 | 				var constructedGenericsArgs = constructedGenericType.GenericTypeArguments;
289 | 				if (givenTypeGenericsArgs.Zip(constructedGenericsArgs, (g, c) => TypeUtils.IsCompatibleWith(g, c)).Any(compatible => !compatible))
290 | 					return null;
291 | 
292 | 				return givenType;
293 | 			}
294 | 
295 | 			var baseType = givenType.BaseType;
296 | 			if (baseType == null)
297 | 				return null;
298 | 
299 | 			return FindAssignableGenericType(baseType, genericTypeDefinition);
300 | 		}
301 | 
302 | 		public static Type GetConcreteTypeForGenericMethod(Type type, List promotedArgs, MethodData method)
303 | 		{
304 | 			if (type.IsGenericType)
305 | 			{
306 | 				//Generic type
307 | 				var genericArguments = type.GetGenericArguments();
308 | 				var concreteTypeParameters = new Type[genericArguments.Length];
309 | 
310 | 				for (var i = 0; i < genericArguments.Length; i++)
311 | 				{
312 | 					concreteTypeParameters[i] = GetConcreteTypeForGenericMethod(genericArguments[i], promotedArgs, method);
313 | 				}
314 | 
315 | 				return type.GetGenericTypeDefinition().MakeGenericType(concreteTypeParameters);
316 | 			}
317 | 			else if (type.ContainsGenericParameters)
318 | 			{
319 | 				//T case
320 | 				//try finding an actual parameter for the generic
321 | 				for (var i = 0; i < promotedArgs.Count; i++)
322 | 				{
323 | 					if (method.Parameters[i].ParameterType == type)
324 | 					{
325 | 						return promotedArgs[i].Type;
326 | 					}
327 | 				}
328 | 			}
329 | 
330 | 			return type;//already a concrete type
331 | 		}
332 | 	}
333 | }
334 | 


--------------------------------------------------------------------------------
/src/DynamicExpresso.Core/Resources/ErrorMessages.de.resx:
--------------------------------------------------------------------------------
  1 | 
  2 | 
  3 | 
  4 |   
  5 |     
  6 | 
  7 |     
  8 |   
  9 |   
 10 |     text/microsoft-resx
 11 |   
 12 |   
 13 |     1.3
 14 |   
 15 |   
 16 |     System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
 17 |   
 18 |   
 19 |     System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
 20 |   
 21 |   
 22 |     Mehrdeutiger Aufruf des Nutzer-definierten Opeators '{0}' mit Typen '{1}' und '{2}'
 23 |   
 24 |   
 25 |     Mehrdeutiger Aufruf eines Konstruktors des Typen '{0}'
 26 |   
 27 |   
 28 |     Mehrdeutiger Aufruf eines Delegats (mehrere Überladungen gefunden)
 29 |   
 30 |   
 31 |     Mehrdeutiger Aufruf eines Indexers des Typen '{0}'
 32 |   
 33 |   
 34 |     Mehrdeutiger Aufruf der Methode '{0}' mit Typen '{1}'
 35 |   
 36 |   
 37 |     Mehrdeutiger Aufruf des Nutzer-definierten Operators '{0}' mit Typen '{1}'
 38 |   
 39 |   
 40 |     Argument-Liste inkompatibel mit Delegaten-Ausdruck
 41 |   
 42 |   
 43 |     Argument-Liste inkompatibel mit Lambda-Ausdruck
 44 |   
 45 |   
 46 |     Beide Typen '{0}' und '{1}' können zueinander konvertiert werden
 47 |   
 48 |   
 49 |     Ein Wert mit Typ '{0}' kann nicht zum Typ '{1}' konvertiert werden
 50 |   
 51 |   
 52 |     ']' oder ',' erwartet
 53 |   
 54 |   
 55 |     '}}' erwartet
 56 |   
 57 |   
 58 |     ')' oder ',' erwartet
 59 |   
 60 |   
 61 |     ')' oder Operator erwartet
 62 |   
 63 |   
 64 |     '>' erwartet
 65 |   
 66 |   
 67 |     Der Typ '{0}' kann nicht mit einem Auflistungsinitialisierer initialisiert werden, da es '{1}' nicht implementiert
 68 |   
 69 |   
 70 |     ':' erwartet
 71 |   
 72 |   
 73 |     Ziffer erwartet
 74 |   
 75 |   
 76 |     '.' oder '(' erwartet
 77 |   
 78 |   
 79 |     '=' erwartet
 80 |   
 81 |   
 82 |     Ausdruck erwartet
 83 |   
 84 |   
 85 |     Ausdruck muss schreibbar sein
 86 |   
 87 |   
 88 |     Der erste Ausdruck muss vom Typ 'Boolean' sein
 89 |   
 90 |   
 91 |     {0} (an Stelle {1})
 92 |   
 93 |   
 94 |     Bezeichner erwartet
 95 |   
 96 |   
 97 |     Operator '{0}' ist nicht kompatibel mit Operand von Typ '{1}'
 98 |   
 99 |   
100 |     Operator '{0}' ist nicht kompatibel mit Operanden von Typen '{1}' und '{2}'
101 |   
102 |   
103 |     Falsche Anzahl an Indexen
104 |   
105 |   
106 |     Syntax Fehler '{0}'
107 |   
108 |   
109 |     Zeichenliteral muss genau ein Zeichen beinhalten
110 |   
111 |   
112 |     Invalide Escapezeichen-Sequenz
113 |   
114 |   
115 |     Array-Index muss ein Integer-Ausdruck sein
116 |   
117 |   
118 |     Invalide Deklaration eines Member-Intialisierers
119 |   
120 |   
121 |     Invalides Integer-Literal '{0}'
122 |   
123 |   
124 |     Keine zutreffende Methode gefunden im Typ '{0}'
125 |   
126 |   
127 |     Invalide Operation
128 |   
129 |   
130 |     Invalides Kommazahl-Literal '{0}'
131 |   
132 |   
133 |     Die Argumenttypen für die Methode '{0}' kann nicht von ihrer Nutzung erschlossen werden.
134 |   
135 |   
136 |     Keine der beiden Typen '{0}' und '{1}' kann zueinander konvertiert werden
137 |   
138 |   
139 |     Kein zutreffender Konstruktor existiert im Typ '{0}'
140 |   
141 |   
142 |     Kein zutreffender Indexer existiert im Typ '{0}'
143 |   
144 |   
145 |     '{{' erwartet
146 |   
147 |   
148 |     '(' erwartet
149 |   
150 |   
151 |     Params Array Typ ist kein Array, Element nicht gefunden
152 |   
153 |   
154 |     Syntax Fehler
155 |   
156 |   
157 |     Typ '{0}' hat keine Nullable-Form
158 |   
159 |   
160 |     Typ-Bezeichner erwartet
161 |   
162 |   
163 |     Das 'typeof'-Schlüsselwort benötigt einen Typen als Argument
164 |   
165 |   
166 |     Das 'typeof'-Schlüsselwort benötigt 1 Argument
167 |   
168 |   
169 |     Die beste Überladung der Add-Methode '{0}.Add' für den Auflistungsinitialisierer hat invalide Argumente
170 |   
171 |   
172 |     Keine Eigenschaft oder Feld '{0}' existiert am Typ '{1}'
173 |   
174 |   
175 |     Mehrdimensionale Arrays werden nicht unterstützt
176 |   
177 |   
178 |     Nicht-beendetes Zeichenfolgenliteral
179 |   
180 |   
181 |     {0} ist ein reserviertes Wort
182 |   
183 |   
184 |     Argumentzahl unpassend
185 |   
186 |   
187 |     Generische Typen müssen mittels generischer Definition referenziert werden: {0}
188 |   
189 |   
190 |     Zuweisungsoperator '{0}' ist nicht erlaubt
191 |   
192 |   
193 |     Der Parameter '{0}' wurde mehrmals definiert
194 |   
195 |   
196 |     Keine zutreffende Methode '{0}' gefunden im Typ '{1}'
197 |   
198 |   
199 |     Spiegelungsausdrücke sind nicht erlaubt. Aktivieren Sie Spiegelung mittels Interpreter.EnableReflection().
200 |   
201 |   
202 |     Unbekannter Bezeichner '{0}'
203 |   
204 |   
205 |     Eine locale Variable oder Parameter '{0}' kann nicht in dieser Zugriffsebene deklariert werden, da dieser Name bereits in einer höheren Zugriffsebene verwendet wird, um eine lokale Variable oder Parameter zu definieren
206 |   
207 |   
208 |     Mehrere Lambda-Funktionsparameter erkannt, aber keine umgebenden Klammern gegeben
209 |   
210 |   
211 |     Die Anzahl an Typargumenten für den generischen Typ stimmt nicht mit der Stelligkeit der Definition überein
212 |   
213 |   
214 |     NextToken wurde am Ende des Ausdrucks aufgerufen
215 |   
216 | 
217 | 


--------------------------------------------------------------------------------