├── .gitignore ├── Demo ├── BaseRobotAction.cs ├── Demo.csproj ├── IRobotAction.cs ├── Program.cs ├── actions │ ├── Command1.cs │ └── Command2.cs ├── attribute │ └── RobotAction.cs └── models │ ├── ResultJsonBase.cs │ └── VxRobotVm.cs ├── FastExpressionEngine.sln ├── FastExpressionEngine.sln.DotSettings.user ├── LICENSE.md ├── README.md ├── UnitTest ├── UnitTest.csproj └── UnitTest1.cs └── src ├── CustomTypeProvider.cs ├── EngineSetting.cs ├── Exceptions ├── ExpressionParserException.cs ├── RuleException.cs └── ScopedParamException.cs ├── ExpressionBuilders ├── LambdaExpressionBuilder.cs ├── RuleExpressionBuilderBase.cs └── RuleExpressionParser.cs ├── ExpressionEngine.cs ├── Extensions └── EnumerableExtensions.cs ├── FastExpressionEngine.csproj ├── HelperFunctions ├── ExpressionUtils.cs ├── HashUtils.cs ├── Helpers.cs └── Utils.cs ├── Models ├── Rule.cs ├── RuleDelegate.cs ├── RuleExpressionParameter.cs ├── RuleExpressionType.cs ├── RuleParameter.cs ├── RuleResultTree.cs └── ScopedParam.cs ├── RuleCompiler.cs └── RuleExpressionBuilderFactory.cs /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | /packages/ 4 | riderModule.iml 5 | /_ReSharper.Caches/ 6 | .idea/ 7 | -------------------------------------------------------------------------------- /Demo/BaseRobotAction.cs: -------------------------------------------------------------------------------- 1 |  2 | using System.Reflection; 3 | using Demo.attribute; 4 | using Demo.models; 5 | 6 | namespace Demo; 7 | public abstract class BaseRobotAction : IRobotAction 8 | { 9 | internal void Init() 10 | { 11 | RobotAction = GetType().GetCustomAttribute()!; 12 | } 13 | public abstract Task Do(HttpContext context,VxRobotVm model); 14 | public RobotAction RobotAction { get; set; } 15 | 16 | public async Task DoAction(HttpContext context,VxRobotVm? model) 17 | { 18 | await Do(context,model!); 19 | } 20 | 21 | public RobotAction getRobotActionAttr() 22 | { 23 | return RobotAction; 24 | } 25 | } -------------------------------------------------------------------------------- /Demo/Demo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Demo/IRobotAction.cs: -------------------------------------------------------------------------------- 1 | using Demo.attribute; 2 | using Demo.models; 3 | 4 | namespace Demo; 5 | 6 | public interface IRobotAction 7 | { 8 | Task DoAction(HttpContext context,VxRobotVm? model); 9 | 10 | RobotAction getRobotActionAttr(); 11 | } -------------------------------------------------------------------------------- /Demo/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using Autofac; 3 | using Autofac.Annotation; 4 | using Autofac.Extensions.DependencyInjection; 5 | using Demo; 6 | using Demo.models; 7 | using FastExpressionEngine; 8 | 9 | var builder = WebApplication.CreateBuilder(); 10 | 11 | // 设置使用autofac作为DI容器 12 | builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); 13 | // 设置DI容器 添加注解功能 14 | builder.Host.ConfigureContainer((c, containerBuilder) => 15 | { 16 | // 在这里 在这里 在这里 17 | containerBuilder.RegisterModule(new AutofacAnnotationModule().SetDefaultAutofacScopeToSingleInstance()); 18 | containerBuilder.RegisterInstance(new ExpressionEngine()); 19 | }); 20 | 21 | builder.WebHost.UseUrls("http://0.0.0.0:5555"); 22 | 23 | var app = builder.Build(); 24 | app.MapPost("/robot", async (context) => 25 | { 26 | var result = new ResultJsonInfo 27 | { 28 | Status = 1 29 | }; 30 | try 31 | { 32 | // 读取post body 33 | var robotMsg = await ReadBodyAsync(context.Request.Body); 34 | // 从容器中拿到表达式引擎 35 | var engine = context.RequestServices.GetAutofacRoot().Resolve(); 36 | // 从容器中拿到注册为robotAction的所有实例 37 | var actions = context.RequestServices.GetAutofacRoot().Resolve>(); 38 | foreach (var action in actions) 39 | { 40 | var robotActionAttr = action.getRobotActionAttr(); 41 | if (!engine.Execute(robotActionAttr.Expression, robotMsg).IsSuccess) continue; 42 | // 找到满足条件的action 43 | await action.DoAction(context,robotMsg); 44 | break; 45 | } 46 | } 47 | finally 48 | { 49 | await context.Response.WriteAsJsonAsync(result); 50 | } 51 | }); 52 | 53 | Console.WriteLine(AppContext.BaseDirectory); 54 | app.Run(); 55 | static async Task ReadBodyAsync( Stream bodyStream) 56 | { 57 | try 58 | { 59 | var body = ""; 60 | using (StreamReader stream = new StreamReader(bodyStream)) 61 | { 62 | body = await stream.ReadToEndAsync(); 63 | } 64 | return JsonSerializer.Deserialize(body); 65 | } 66 | catch (Exception e) 67 | { 68 | return default; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Demo/actions/Command1.cs: -------------------------------------------------------------------------------- 1 | using Demo.attribute; 2 | using Demo.models; 3 | 4 | namespace Demo.actions; 5 | 6 | [RobotAction("Context.StartsWith(\"Command1\") AND From == \"123\"")] 7 | public class Command1 : BaseRobotAction 8 | { 9 | public override Task Do(HttpContext context, VxRobotVm model) 10 | { 11 | Console.WriteLine($"{model.From} : {model.Context}"); 12 | return Task.CompletedTask; 13 | } 14 | } -------------------------------------------------------------------------------- /Demo/actions/Command2.cs: -------------------------------------------------------------------------------- 1 | using Demo.attribute; 2 | using Demo.models; 3 | 4 | namespace Demo.actions; 5 | 6 | [RobotAction("Context.StartsWith(\"Command2\") OR From == \"234\"")] 7 | public class Command2 : BaseRobotAction 8 | { 9 | public override Task Do(HttpContext context, VxRobotVm model) 10 | { 11 | Console.WriteLine($"{model.From} : {model.Context}"); 12 | return Task.CompletedTask; 13 | } 14 | } -------------------------------------------------------------------------------- /Demo/attribute/RobotAction.cs: -------------------------------------------------------------------------------- 1 | using Autofac.Annotation; 2 | 3 | namespace Demo.attribute; 4 | 5 | /// 6 | /// 自定义注解 7 | /// 8 | [Component] 9 | public class RobotAction : Attribute 10 | { 11 | 12 | /// 13 | /// 默认注册到容器为IRobotAction类型 14 | /// 15 | [AliasFor(typeof(Component), "Services")] 16 | public Type[] Services { get; set; } = new[] { typeof(IRobotAction) }; 17 | 18 | public RobotAction(string expression) 19 | { 20 | Expression = expression; 21 | } 22 | 23 | /// 24 | /// 容器中拿此类的时候执行的方法 25 | /// 26 | [AliasFor(typeof(Component), "InitMethod")] 27 | 28 | public string InitMethod { get; set; } = nameof(BaseRobotAction.Init); 29 | 30 | /// 31 | /// 表达式 32 | /// 33 | public string Expression { get; set; } 34 | } -------------------------------------------------------------------------------- /Demo/models/ResultJsonBase.cs: -------------------------------------------------------------------------------- 1 | namespace Demo.models; 2 | 3 | public class ResultJsonInfo : ResultJsonBase 4 | { 5 | public ResultJsonInfo() 6 | { 7 | 8 | } 9 | 10 | #region field 11 | 12 | private T data; 13 | 14 | #endregion 15 | 16 | #region property 17 | 18 | /// 19 | /// 返回数据 20 | /// 21 | public T Data 22 | { 23 | get { return data; } 24 | set { data = value; } 25 | } 26 | 27 | #endregion 28 | } 29 | 30 | 31 | public class ResultJsonBase 32 | { 33 | 34 | public ResultJsonBase() 35 | { 36 | 37 | } 38 | 39 | #region field 40 | 41 | private int status; 42 | private string info; 43 | 44 | #endregion 45 | 46 | #region property 47 | 48 | /// 49 | /// 状态 50 | /// 51 | public int Status 52 | { 53 | get { return status; } 54 | set { status = value; } 55 | } 56 | 57 | /// 58 | /// 提示信息 59 | /// 60 | public string Info 61 | { 62 | get { return info; } 63 | set { info = value; } 64 | } 65 | 66 | #endregion 67 | } 68 | 69 | -------------------------------------------------------------------------------- /Demo/models/VxRobotVm.cs: -------------------------------------------------------------------------------- 1 | namespace Demo.models; 2 | 3 | public class VxRobotVm 4 | { 5 | /// 6 | /// 发送内容 7 | /// 8 | public string Context { get; set; } 9 | 10 | /// 11 | /// 群id 12 | /// 13 | public string From { get; set; } 14 | 15 | /// 16 | /// 发送微信id 17 | /// 18 | public string To { get; set; } 19 | } 20 | -------------------------------------------------------------------------------- /FastExpressionEngine.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FastExpressionEngine", "src\FastExpressionEngine.csproj", "{9DDC5047-42A7-4C47-8F72-F9DA73179EED}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTest", "UnitTest\UnitTest.csproj", "{25EC861F-C526-40C1-A007-F50FBB04E434}" 6 | EndProject 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Demo", "Demo\Demo.csproj", "{10818517-AD82-4AD6-A65D-D8551FB07D1B}" 8 | EndProject 9 | Global 10 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 11 | Debug|Any CPU = Debug|Any CPU 12 | Release|Any CPU = Release|Any CPU 13 | EndGlobalSection 14 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 15 | {9DDC5047-42A7-4C47-8F72-F9DA73179EED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 16 | {9DDC5047-42A7-4C47-8F72-F9DA73179EED}.Debug|Any CPU.Build.0 = Debug|Any CPU 17 | {9DDC5047-42A7-4C47-8F72-F9DA73179EED}.Release|Any CPU.ActiveCfg = Release|Any CPU 18 | {9DDC5047-42A7-4C47-8F72-F9DA73179EED}.Release|Any CPU.Build.0 = Release|Any CPU 19 | {25EC861F-C526-40C1-A007-F50FBB04E434}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {25EC861F-C526-40C1-A007-F50FBB04E434}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {25EC861F-C526-40C1-A007-F50FBB04E434}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {25EC861F-C526-40C1-A007-F50FBB04E434}.Release|Any CPU.Build.0 = Release|Any CPU 23 | {10818517-AD82-4AD6-A65D-D8551FB07D1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {10818517-AD82-4AD6-A65D-D8551FB07D1B}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {10818517-AD82-4AD6-A65D-D8551FB07D1B}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {10818517-AD82-4AD6-A65D-D8551FB07D1B}.Release|Any CPU.Build.0 = Release|Any CPU 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /FastExpressionEngine.sln.DotSettings.user: -------------------------------------------------------------------------------- 1 |  2 | <SessionState ContinuousTestingMode="0" IsActive="True" Name="Test1" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> 3 | <TestAncestor> 4 | <TestId>NUnit3x::25EC861F-C526-40C1-A007-F50FBB04E434::.NETCoreApp,Version=v3.1::UnitTest.Tests.Test1</TestId> 5 | <TestId>NUnit3x::25EC861F-C526-40C1-A007-F50FBB04E434::.NETCoreApp,Version=v3.1::UnitTest.Tests.Test2</TestId> 6 | </TestAncestor> 7 | </SessionState> -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 yuzd 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 字符串表达式执行引擎 2 | 3 | 支持netcore2.0 + 4 | 5 | # NUGET 6 | 7 | Install-Package FastExpressionEngine 8 | 9 | ## Document 10 | 11 | 12 | 13 | ```csharp 14 | var bre = new ExpressionEngine(); 15 | dynamic datas = new ExpandoObject(); 16 | datas.count = 1; 17 | datas.name = "avqqq"; 18 | var inputs = new dynamic[] 19 | { 20 | datas 21 | }; 22 | 23 | var resultList = 24 | bre.Execute("count < 3 AND name.Contains(\"av\") AND name.StartsWith(\"av\")", inputs); 25 | 26 | var resultListIsSuccess = resultList.IsSuccess; 27 | ``` 28 | 29 | 项目参考 https://github.com/microsoft/RulesEngine 30 | 表达式编译采用:https://github.com/dadhi/FastExpressionCompiler 31 | 缓存采用LRU默认1000size:https://github.com/bitfaster/BitFaster.Caching 32 | -------------------------------------------------------------------------------- /UnitTest/UnitTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /UnitTest/UnitTest1.cs: -------------------------------------------------------------------------------- 1 | using System.Dynamic; 2 | using FastExpressionEngine; 3 | using NUnit.Framework; 4 | using static NUnit.Framework.Assert; 5 | 6 | namespace UnitTest 7 | { 8 | public class Tests 9 | { 10 | [Test] 11 | public void Test1() 12 | { 13 | 14 | var bre = new ExpressionEngine(); 15 | 16 | dynamic datas = new ExpandoObject(); 17 | datas.count = 1; 18 | datas.name = "avqqq"; 19 | var inputs = new dynamic[] 20 | { 21 | datas 22 | }; 23 | 24 | var resultList = 25 | bre.Execute("count < 3 AND name.Contains(\"av\") AND name.StartsWith(\"av\")", inputs); 26 | 27 | var resultListIsSuccess = resultList.IsSuccess; 28 | That(resultListIsSuccess, Is.True); 29 | } 30 | 31 | [Test] 32 | public void Test2() 33 | { 34 | 35 | var bre = new ExpressionEngine(); 36 | dynamic datas = new ExpandoObject(); 37 | datas.count = 1; 38 | datas.name = "avqqq"; 39 | var inputs = new dynamic[] 40 | { 41 | datas 42 | }; 43 | 44 | var resultList = 45 | bre.Execute("input1.count < 3", inputs); 46 | 47 | var resultListIsSuccess = resultList.IsSuccess; 48 | That(resultListIsSuccess, Is.True); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /src/CustomTypeProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq.Dynamic.Core.CustomTypeProviders; 7 | using FastExpressionEngine.HelperFunctions; 8 | 9 | namespace FastExpressionEngine 10 | { 11 | public class CustomTypeProvider : DefaultDynamicLinqCustomTypeProvider 12 | { 13 | private HashSet _types; 14 | 15 | public CustomTypeProvider(Type[] types) : base() 16 | { 17 | _types = new HashSet(types ?? new Type[] { }); 18 | _types.Add(typeof(ExpressionUtils)); 19 | } 20 | 21 | public override HashSet GetCustomTypes() 22 | { 23 | return _types; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/EngineSetting.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) Company. All Rights Reserved. 4 | // 5 | // nainaigu 6 | // $Date$ 7 | // 8 | //----------------------------------------------------------------------- 9 | 10 | using System; 11 | using System.Collections.Generic; 12 | using System.Diagnostics.CodeAnalysis; 13 | using FastExpressionEngine.Models; 14 | 15 | namespace FastExpressionEngine 16 | { 17 | [ExcludeFromCodeCoverage] 18 | public class EngineSetting 19 | { 20 | public EngineSetting() 21 | { 22 | } 23 | 24 | // create a copy of settings 25 | internal EngineSetting(EngineSetting reSettings) 26 | { 27 | CustomTypes = reSettings.CustomTypes; 28 | EnableScopedParams = reSettings.EnableScopedParams; 29 | AutoRegisterInputType = reSettings.AutoRegisterInputType; 30 | } 31 | 32 | /// 33 | /// Gets or Sets the global params which will be applicable to all rules 34 | /// 35 | public IEnumerable GlobalParams { get; set; } 36 | 37 | 38 | /// 39 | /// Get/Set the custom types to be used in Rule expressions 40 | /// 41 | public Type[] CustomTypes { get; set; } 42 | 43 | 44 | /// 45 | /// Enables Global params and local params for rules 46 | /// 47 | public bool EnableScopedParams { get; set; } = true; 48 | 49 | 50 | /// 51 | /// Auto Registers input type in Custom Type to allow calling method on type. 52 | /// Default : true 53 | /// 54 | public bool AutoRegisterInputType { get; set; } = true; 55 | 56 | /// 57 | /// Sets the mode for Nested rule execution, Default: All 58 | /// 59 | public int CacheSize { get; set; } = 1000; 60 | } 61 | } -------------------------------------------------------------------------------- /src/Exceptions/ExpressionParserException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | 6 | namespace FastExpressionEngine.Exceptions 7 | { 8 | public class ExpressionParserException : Exception 9 | { 10 | public ExpressionParserException(string message, string expression) : base(message) 11 | { 12 | Data.Add("Expression", expression); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/Exceptions/RuleException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | 6 | namespace FastExpressionEngine.Exceptions 7 | { 8 | public class RuleException : Exception 9 | { 10 | public RuleException(string message) : base(message) 11 | { 12 | } 13 | 14 | public RuleException(string message, Exception innerException) : base(message, innerException) 15 | { 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/Exceptions/ScopedParamException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | 6 | namespace Autofac.Annotation.FastExpressionCompiler.Exceptions 7 | { 8 | public class ScopedParamException : Exception 9 | { 10 | public ScopedParamException(string message, Exception innerException, string scopedParamName) : base(message, 11 | innerException) 12 | { 13 | Data.Add("ScopedParamName", scopedParamName); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/ExpressionBuilders/LambdaExpressionBuilder.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq.Dynamic.Core.Exceptions; 7 | using System.Linq.Expressions; 8 | using FastExpressionEngine.Exceptions; 9 | using FastExpressionEngine.HelperFunctions; 10 | using FastExpressionEngine.Models; 11 | 12 | namespace FastExpressionEngine.ExpressionBuilders 13 | { 14 | internal sealed class LambdaExpressionBuilder : RuleExpressionBuilderBase 15 | { 16 | private readonly RuleExpressionParser _ruleExpressionParser; 17 | 18 | internal LambdaExpressionBuilder(RuleExpressionParser ruleExpressionParser) 19 | { 20 | _ruleExpressionParser = ruleExpressionParser; 21 | } 22 | 23 | internal override RuleFunc BuildDelegateForRule(Rule rule, RuleParameter[] ruleParams) 24 | { 25 | try 26 | { 27 | var ruleDelegate = _ruleExpressionParser.Compile(rule.Expression, ruleParams); 28 | return Helpers.ToResultTree(rule, null, ruleDelegate); 29 | } 30 | catch (Exception ex) 31 | { 32 | Helpers.HandleRuleException(ex, rule); 33 | 34 | var exceptionMessage = $"Exception while parsing expression `{rule?.Expression}` - {ex.Message}"; 35 | 36 | bool func(object[] param) => false; 37 | 38 | return Helpers.ToResultTree(rule, null, func, exceptionMessage); 39 | } 40 | } 41 | 42 | internal override Expression Parse(string expression, ParameterExpression[] parameters, Type returnType) 43 | { 44 | try 45 | { 46 | return _ruleExpressionParser.Parse(expression, parameters, returnType); 47 | } 48 | catch (ParseException ex) 49 | { 50 | throw new ExpressionParserException(ex.Message, expression); 51 | } 52 | } 53 | 54 | internal override Func> CompileScopedParams(RuleParameter[] ruleParameters, 55 | RuleExpressionParameter[] scopedParameters) 56 | { 57 | return _ruleExpressionParser.CompileRuleExpressionParameters(ruleParameters, scopedParameters); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /src/ExpressionBuilders/RuleExpressionBuilderBase.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq.Expressions; 7 | using FastExpressionEngine.Models; 8 | 9 | namespace FastExpressionEngine.ExpressionBuilders 10 | { 11 | /// 12 | /// Base class for expression builders 13 | /// 14 | internal abstract class RuleExpressionBuilderBase 15 | { 16 | /// 17 | /// Builds the expression for rule. 18 | /// 19 | /// The rule. 20 | /// The type parameter expressions. 21 | /// The rule input exp. 22 | /// Expression type 23 | internal abstract RuleFunc BuildDelegateForRule(Rule rule, RuleParameter[] ruleParams); 24 | 25 | internal abstract Expression Parse(string expression, ParameterExpression[] parameters, Type returnType); 26 | 27 | internal abstract Func> CompileScopedParams(RuleParameter[] ruleParameters, 28 | RuleExpressionParameter[] scopedParameters); 29 | } 30 | } -------------------------------------------------------------------------------- /src/ExpressionBuilders/RuleExpressionParser.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using FastExpressionCompiler; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Linq.Dynamic.Core; 9 | using System.Linq.Dynamic.Core.Parser; 10 | using System.Linq.Expressions; 11 | using System.Reflection; 12 | using FastExpressionEngine.Models; 13 | 14 | namespace FastExpressionEngine.ExpressionBuilders 15 | { 16 | public class RuleExpressionParser 17 | { 18 | private readonly EngineSetting _setting; 19 | private readonly IDictionary _methodInfo; 20 | 21 | public RuleExpressionParser(EngineSetting setting) 22 | { 23 | _setting = setting; 24 | _methodInfo = new Dictionary(); 25 | PopulateMethodInfo(); 26 | } 27 | 28 | private void PopulateMethodInfo() 29 | { 30 | var dict_add = typeof(Dictionary).GetMethod("Add", 31 | BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof(string), typeof(object) }, null); 32 | _methodInfo.Add("dict_add", dict_add); 33 | } 34 | 35 | public Expression Parse(string expression, ParameterExpression[] parameters, Type returnType) 36 | { 37 | var config = new ParsingConfig 38 | { 39 | CustomTypeProvider = new CustomTypeProvider(_setting.CustomTypes), 40 | IsCaseSensitive = false 41 | }; 42 | return new ExpressionParser(parameters, expression, new object[] { }, config).Parse(returnType); 43 | } 44 | 45 | public Func Compile(string expression, RuleParameter[] ruleParams) 46 | { 47 | var rtype = typeof(T); 48 | if (rtype == typeof(object)) 49 | { 50 | rtype = null; 51 | } 52 | 53 | var parameterExpressions = GetParameterExpression(ruleParams).ToArray(); 54 | 55 | var e = Parse(expression, parameterExpressions, rtype); 56 | if (rtype == null) 57 | { 58 | e = Expression.Convert(e, typeof(T)); 59 | } 60 | 61 | var expressionBody = new List() { e }; 62 | var wrappedExpression = 63 | WrapExpression(expressionBody, parameterExpressions, new ParameterExpression[] { }); 64 | return wrappedExpression.CompileFast(); 65 | } 66 | 67 | private Expression> WrapExpression(List expressionList, 68 | ParameterExpression[] parameters, ParameterExpression[] variables) 69 | { 70 | var argExp = Expression.Parameter(typeof(object[]), "args"); 71 | var paramExps = parameters.Select((c, i) => 72 | { 73 | var arg = Expression.ArrayAccess(argExp, Expression.Constant(i)); 74 | return (Expression)Expression.Assign(c, Expression.Convert(arg, c.Type)); 75 | }); 76 | var blockExpSteps = paramExps.Concat(expressionList); 77 | var blockExp = Expression.Block(parameters.Concat(variables), blockExpSteps); 78 | return Expression.Lambda>(blockExp, argExp); 79 | } 80 | 81 | internal Func> CompileRuleExpressionParameters(RuleParameter[] ruleParams, 82 | RuleExpressionParameter[] ruleExpParams = null) 83 | { 84 | ruleExpParams = ruleExpParams ?? new RuleExpressionParameter[] { }; 85 | var expression = CreateDictionaryExpression(ruleParams, ruleExpParams); 86 | return expression.CompileFast(); 87 | } 88 | 89 | public T Evaluate(string expression, RuleParameter[] ruleParams) 90 | { 91 | var func = Compile(expression, ruleParams); 92 | return func(ruleParams.Select(c => c.Value).ToArray()); 93 | } 94 | 95 | private IEnumerable CreateAssignedParameterExpression(RuleExpressionParameter[] ruleExpParams) 96 | { 97 | return ruleExpParams.Select((c, i) => 98 | { 99 | return Expression.Assign(c.ParameterExpression, c.ValueExpression); 100 | }); 101 | } 102 | 103 | // 104 | /// Gets the parameter expression. 105 | /// 106 | /// The types. 107 | /// 108 | /// 109 | /// types 110 | /// or 111 | /// type 112 | /// 113 | private IEnumerable GetParameterExpression(RuleParameter[] ruleParams) 114 | { 115 | foreach (var ruleParam in ruleParams) 116 | { 117 | if (ruleParam == null) 118 | { 119 | throw new ArgumentException($"{nameof(ruleParam)} can't be null."); 120 | } 121 | 122 | yield return ruleParam.ParameterExpression; 123 | } 124 | } 125 | 126 | private Expression>> CreateDictionaryExpression( 127 | RuleParameter[] ruleParams, RuleExpressionParameter[] ruleExpParams) 128 | { 129 | var body = new List(); 130 | var paramExp = new List(); 131 | var variableExp = new List(); 132 | 133 | 134 | var variableExpressions = CreateAssignedParameterExpression(ruleExpParams); 135 | 136 | body.AddRange(variableExpressions); 137 | 138 | var dict = Expression.Variable(typeof(Dictionary)); 139 | var add = _methodInfo["dict_add"]; 140 | 141 | body.Add(Expression.Assign(dict, Expression.New(typeof(Dictionary)))); 142 | variableExp.Add(dict); 143 | 144 | for (var i = 0; i < ruleParams.Length; i++) 145 | { 146 | paramExp.Add(ruleParams[i].ParameterExpression); 147 | } 148 | 149 | for (var i = 0; i < ruleExpParams.Length; i++) 150 | { 151 | var key = Expression.Constant(ruleExpParams[i].ParameterExpression.Name); 152 | var value = Expression.Convert(ruleExpParams[i].ParameterExpression, typeof(object)); 153 | variableExp.Add(ruleExpParams[i].ParameterExpression); 154 | body.Add(Expression.Call(dict, add, key, value)); 155 | } 156 | 157 | // Return value 158 | body.Add(dict); 159 | 160 | return WrapExpression>(body, paramExp.ToArray(), variableExp.ToArray()); 161 | } 162 | } 163 | } -------------------------------------------------------------------------------- /src/ExpressionEngine.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Concurrent; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using BitFaster.Caching.Lru; 9 | using FastExpressionEngine.ExpressionBuilders; 10 | using FastExpressionEngine.Extensions; 11 | using FastExpressionEngine.HelperFunctions; 12 | using FastExpressionEngine.Models; 13 | 14 | namespace FastExpressionEngine 15 | { 16 | /// 17 | /// 执行字符串表达式,结果为true/false 18 | /// 19 | public class ExpressionEngine 20 | { 21 | private readonly EngineSetting _reSettings; 22 | 23 | #region Variables 24 | 25 | private readonly RuleCompiler _ruleCompiler; 26 | 27 | private readonly ConcurrentLru> _cache; 28 | 29 | #endregion 30 | 31 | #region Constructor 32 | 33 | public ExpressionEngine(EngineSetting reSettings = null) 34 | { 35 | _reSettings = reSettings == null ? new EngineSetting() : new EngineSetting(reSettings); 36 | if (_reSettings.CacheSize < 1) 37 | { 38 | _reSettings.CacheSize = 1000; 39 | } 40 | 41 | _cache = new ConcurrentLru>(_reSettings.CacheSize); 42 | var ruleExpressionParser = new RuleExpressionParser(_reSettings); 43 | _ruleCompiler = new RuleCompiler(new RuleExpressionBuilderFactory(ruleExpressionParser)); 44 | } 45 | 46 | #endregion 47 | 48 | #region Public Methods 49 | 50 | /// 51 | /// This will execute all the rules of the specified workflow 52 | /// 53 | /// The name of the workflow with rules to execute against the inputs 54 | /// A variable number of inputs 55 | /// List of rule results 56 | public RuleResultTree Execute(string expression, params object[] inputs) 57 | { 58 | var ruleParams = new List(); 59 | 60 | for (var i = 0; i < inputs.Length; i++) 61 | { 62 | var input = inputs[i]; 63 | ruleParams.Add(new RuleParameter($"input{i + 1}", input)); 64 | } 65 | 66 | return Execute(expression, ruleParams.ToArray()); 67 | } 68 | 69 | public RuleResultTree Execute(Rule rule, params object[] inputs) 70 | { 71 | var ruleParams = new List(); 72 | 73 | for (var i = 0; i < inputs.Length; i++) 74 | { 75 | var input = inputs[i]; 76 | ruleParams.Add(new RuleParameter($"input{i + 1}", input)); 77 | } 78 | 79 | return Execute(rule, ruleParams.ToArray()); 80 | } 81 | 82 | /// 83 | /// This will execute all the rules of the specified workflow 84 | /// 85 | /// The name of the workflow with rules to execute against the inputs 86 | /// A variable number of rule parameters 87 | /// List of rule results 88 | public RuleResultTree Execute(string expression, 89 | params RuleParameter[] ruleParams) 90 | { 91 | var sortedRuleParams = ruleParams.ToList(); 92 | sortedRuleParams.Sort((RuleParameter a, RuleParameter b) => string.Compare(a.Name, b.Name)); 93 | var rule = new Rule 94 | { 95 | Expression = expression 96 | }; 97 | var ruleResultList = executeRule(rule, sortedRuleParams.ToArray()); 98 | return ruleResultList; 99 | } 100 | 101 | public RuleResultTree Execute(Rule rule, 102 | params RuleParameter[] ruleParams) 103 | { 104 | var sortedRuleParams = ruleParams.ToList(); 105 | sortedRuleParams.Sort((RuleParameter a, RuleParameter b) => string.Compare(a.Name, b.Name)); 106 | var ruleResultList = executeRule(rule, sortedRuleParams.ToArray()); 107 | return ruleResultList; 108 | } 109 | 110 | #endregion 111 | 112 | #region Private Methods 113 | 114 | private RuleResultTree executeRule(Rule rule, RuleParameter[] ruleParams) 115 | { 116 | var compiledRule = getOrRegisterRule(rule, ruleParams); 117 | var result = compiledRule(ruleParams); 118 | return result; 119 | } 120 | 121 | /// 122 | /// This will compile the rules and store them to dictionary 123 | /// 124 | /// workflow name 125 | /// The rule parameters. 126 | /// 127 | /// bool result 128 | /// 129 | private RuleFunc getOrRegisterRule(Rule rule, params RuleParameter[] ruleParams) 130 | { 131 | if (rule == null || string.IsNullOrEmpty(rule.Expression)) 132 | { 133 | throw new ArgumentNullException(nameof(rule.Expression)); 134 | } 135 | 136 | var compileRulesKey = GetCompiledRulesKey(rule.Expression, ruleParams); 137 | if (string.IsNullOrEmpty(rule.RuleName)) 138 | { 139 | rule.RuleName = compileRulesKey; 140 | } 141 | 142 | var ruleFunc = _cache.GetOrAdd(compileRulesKey, key => 143 | { 144 | if (_reSettings.AutoRegisterInputType) 145 | { 146 | _reSettings.CustomTypes = 147 | _reSettings.CustomTypes.Safe().Union(ruleParams.Select(c => c.Type)).ToArray(); 148 | } 149 | 150 | var globalParamExp = new Lazy( 151 | () => _ruleCompiler.GetRuleExpressionParameters(RuleExpressionType.LambdaExpression, 152 | _reSettings.GlobalParams, 153 | ruleParams) 154 | ); 155 | 156 | return CompileRule(rule, RuleExpressionType.LambdaExpression, ruleParams, globalParamExp); 157 | }); 158 | return ruleFunc; 159 | } 160 | 161 | private RuleFunc CompileRule(Rule rule, RuleExpressionType ruleExpressionType, 162 | RuleParameter[] ruleParams, Lazy scopedParams) 163 | { 164 | return _ruleCompiler.CompileRule(rule, ruleExpressionType, ruleParams, scopedParams); 165 | } 166 | 167 | 168 | private string GetCompiledRulesKey(string workflowName, RuleParameter[] ruleParams) 169 | { 170 | var ruleParamsKey = string.Join("-", ruleParams.Select(c => $"{c.Name}_{c.Type.Name}")); 171 | var key = $"{workflowName}-" + ruleParamsKey; 172 | return key.CalculateMD5Hash(); 173 | } 174 | 175 | #endregion 176 | } 177 | } -------------------------------------------------------------------------------- /src/Extensions/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace FastExpressionEngine.Extensions 11 | { 12 | internal static class EnumerableExtensions 13 | { 14 | public static IEnumerable Safe(this IEnumerable enumerable) 15 | { 16 | return enumerable ?? Enumerable.Empty(); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/FastExpressionEngine.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 包装FastExpressionCompiler,字符串条件表达式返回Bool 5 | netstandard2.1 6 | FastExpressionEngine 7 | FastExpressionEngine 8 | 包装FastExpressionCompiler,字符串条件表达式返回Bool 9 | 10 | https://github.com/yuzd/Autofac.Annotation 11 | 12 | git 13 | 14 | FastExpressionEngine 15 | FastExpressionEngine 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/HelperFunctions/ExpressionUtils.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Linq; 6 | 7 | namespace FastExpressionEngine.HelperFunctions 8 | { 9 | public static class ExpressionUtils 10 | { 11 | public static bool CheckContains(string check, string valList) 12 | { 13 | if (string.IsNullOrEmpty(check) || string.IsNullOrEmpty(valList)) 14 | return false; 15 | 16 | var list = valList.Split(',').ToList(); 17 | return list.Contains(check); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/HelperFunctions/HashUtils.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) Company. All Rights Reserved. 4 | // 5 | // nainaigu 6 | // $Date$ 7 | // 8 | //----------------------------------------------------------------------- 9 | 10 | using System.Security.Cryptography; 11 | using System.Text; 12 | 13 | namespace FastExpressionEngine.HelperFunctions 14 | { 15 | /// 16 | /// 17 | /// 18 | public static class HashUtils 19 | { 20 | /// 21 | /// 22 | /// 23 | /// 24 | /// 25 | public static string CalculateMD5Hash(this string input) 26 | { 27 | using MD5 md5 = MD5.Create(); 28 | byte[] inputBytes = Encoding.UTF8.GetBytes(input); 29 | byte[] hashBytes = md5.ComputeHash(inputBytes); 30 | 31 | StringBuilder sb = new StringBuilder(); 32 | for (int i = 0; i < hashBytes.Length; i++) 33 | { 34 | sb.Append(hashBytes[i].ToString("x2")); 35 | } 36 | 37 | return sb.ToString(); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/HelperFunctions/Helpers.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Reflection; 8 | using FastExpressionEngine.Exceptions; 9 | using FastExpressionEngine.Models; 10 | 11 | namespace FastExpressionEngine.HelperFunctions 12 | { 13 | /// 14 | /// Helpers 15 | /// 16 | internal static class Helpers 17 | { 18 | internal static RuleFunc ToResultTree(Rule rule, 19 | IEnumerable childRuleResults, Func isSuccessFunc, 20 | string exceptionMessage = "") 21 | { 22 | return (inputs) => 23 | { 24 | var isSuccess = false; 25 | var inputsDict = new Dictionary(); 26 | try 27 | { 28 | inputsDict = inputs.ToDictionary(c => c.Name, c => c.Value); 29 | isSuccess = isSuccessFunc(inputs.Select(c => c.Value).ToArray()); 30 | } 31 | catch (Exception ex) 32 | { 33 | exceptionMessage = $"Error while executing rule : {rule?.RuleName} - {ex.Message}"; 34 | HandleRuleException(new RuleException(exceptionMessage, ex), rule); 35 | isSuccess = false; 36 | } 37 | 38 | return new RuleResultTree 39 | { 40 | Rule = rule, 41 | Inputs = inputsDict, 42 | IsSuccess = isSuccess, 43 | ExceptionMessage = exceptionMessage 44 | }; 45 | }; 46 | } 47 | 48 | internal static RuleFunc ToRuleExceptionResult(Rule rule, Exception ex) 49 | { 50 | HandleRuleException(ex, rule); 51 | return ToResultTree(rule, null, (args) => false, ex.Message); 52 | } 53 | 54 | internal static void HandleRuleException(Exception ex, Rule rule) 55 | { 56 | ex.Data.Add(nameof(rule.RuleName), rule.RuleName); 57 | ex.Data.Add(nameof(rule.Expression), rule.Expression); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /src/HelperFunctions/Utils.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | using System.Dynamic; 8 | using System.Linq; 9 | using System.Linq.Dynamic.Core; 10 | 11 | namespace FastExpressionEngine.HelperFunctions 12 | { 13 | public static class Utils 14 | { 15 | public static object GetTypedObject(dynamic input) 16 | { 17 | if (input is ExpandoObject) 18 | { 19 | Type type = CreateAbstractClassType(input); 20 | return CreateObject(type, input); 21 | } 22 | else 23 | { 24 | return input; 25 | } 26 | } 27 | 28 | public static Type CreateAbstractClassType(dynamic input) 29 | { 30 | List props = new List(); 31 | 32 | if (input == null) 33 | { 34 | return typeof(object); 35 | } 36 | 37 | if (!(input is ExpandoObject)) 38 | { 39 | return input.GetType(); 40 | } 41 | 42 | else 43 | { 44 | foreach (var expando in (IDictionary)input) 45 | { 46 | Type value; 47 | if (expando.Value is IList) 48 | { 49 | if (((IList)expando.Value).Count == 0) 50 | value = typeof(List); 51 | else 52 | { 53 | var internalType = CreateAbstractClassType(((IList)expando.Value)[0]); 54 | value = new List().Cast(internalType).ToList(internalType).GetType(); 55 | } 56 | } 57 | else 58 | { 59 | value = CreateAbstractClassType(expando.Value); 60 | } 61 | 62 | props.Add(new DynamicProperty(expando.Key, value)); 63 | } 64 | } 65 | 66 | var type = DynamicClassFactory.CreateType(props); 67 | return type; 68 | } 69 | 70 | public static object CreateObject(Type type, dynamic input) 71 | { 72 | if (!(input is ExpandoObject)) 73 | { 74 | return Convert.ChangeType(input, type); 75 | } 76 | 77 | object obj = Activator.CreateInstance(type); 78 | 79 | var typeProps = type.GetProperties().ToDictionary(c => c.Name); 80 | 81 | foreach (var expando in (IDictionary)input) 82 | { 83 | if (typeProps.ContainsKey(expando.Key) && 84 | expando.Value != null && 85 | (expando.Value.GetType().Name != "DBNull" || expando.Value != DBNull.Value)) 86 | { 87 | object val; 88 | var propInfo = typeProps[expando.Key]; 89 | if (expando.Value is ExpandoObject) 90 | { 91 | var propType = propInfo.PropertyType; 92 | val = CreateObject(propType, expando.Value); 93 | } 94 | else if (expando.Value is IList) 95 | { 96 | var internalType = propInfo.PropertyType.GenericTypeArguments.FirstOrDefault() ?? 97 | typeof(object); 98 | var temp = (IList)expando.Value; 99 | var newList = new List().Cast(internalType).ToList(internalType); 100 | for (int i = 0; i < temp.Count; i++) 101 | { 102 | var child = CreateObject(internalType, temp[i]); 103 | newList.Add(child); 104 | } 105 | 106 | ; 107 | val = newList; 108 | } 109 | else 110 | { 111 | val = expando.Value; 112 | } 113 | 114 | propInfo.SetValue(obj, val, null); 115 | } 116 | } 117 | 118 | return obj; 119 | } 120 | 121 | private static IEnumerable Cast(this IEnumerable self, Type innerType) 122 | { 123 | var methodInfo = typeof(Enumerable).GetMethod("Cast"); 124 | var genericMethod = methodInfo.MakeGenericMethod(innerType); 125 | return genericMethod.Invoke(null, new[] { self }) as IEnumerable; 126 | } 127 | 128 | private static IList ToList(this IEnumerable self, Type innerType) 129 | { 130 | var methodInfo = typeof(Enumerable).GetMethod("ToList"); 131 | var genericMethod = methodInfo.MakeGenericMethod(innerType); 132 | return genericMethod.Invoke(null, new[] { self }) as IList; 133 | } 134 | } 135 | } -------------------------------------------------------------------------------- /src/Models/Rule.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System.Collections.Generic; 5 | using System.Diagnostics.CodeAnalysis; 6 | 7 | namespace FastExpressionEngine.Models 8 | { 9 | /// 10 | /// Rule class 11 | /// 12 | [ExcludeFromCodeCoverage] 13 | public class Rule 14 | { 15 | /// 16 | /// Rule name for the Rule 17 | /// 18 | public string RuleName { get; set; } 19 | 20 | internal RuleExpressionType RuleExpressionType { get; set; } = RuleExpressionType.LambdaExpression; 21 | public IEnumerable LocalParams { get; set; } 22 | public string Expression { get; set; } 23 | } 24 | } -------------------------------------------------------------------------------- /src/Models/RuleDelegate.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | namespace FastExpressionEngine.Models 5 | { 6 | public delegate T RuleFunc(params RuleParameter[] ruleParameters); 7 | } -------------------------------------------------------------------------------- /src/Models/RuleExpressionParameter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Diagnostics.CodeAnalysis; 6 | using System.Linq.Expressions; 7 | 8 | namespace FastExpressionEngine.Models 9 | { 10 | /// 11 | /// CompiledParam class. 12 | /// 13 | [ExcludeFromCodeCoverage] 14 | public class RuleExpressionParameter 15 | { 16 | public ParameterExpression ParameterExpression { get; set; } 17 | 18 | public Expression ValueExpression { get; set; } 19 | } 20 | } -------------------------------------------------------------------------------- /src/Models/RuleExpressionType.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | namespace FastExpressionEngine.Models 5 | { 6 | /// 7 | /// This is rule expression type which will use in rule config files 8 | /// 9 | public enum RuleExpressionType 10 | { 11 | LambdaExpression = 0 12 | } 13 | } -------------------------------------------------------------------------------- /src/Models/RuleParameter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Diagnostics.CodeAnalysis; 6 | using System.Linq.Expressions; 7 | using FastExpressionEngine.HelperFunctions; 8 | 9 | namespace FastExpressionEngine.Models 10 | { 11 | [ExcludeFromCodeCoverage] 12 | public class RuleParameter 13 | { 14 | public RuleParameter(string name, object value) 15 | { 16 | Value = Utils.GetTypedObject(value); 17 | Init(name, Value?.GetType()); 18 | } 19 | 20 | internal RuleParameter(string name, Type type) 21 | { 22 | Init(name, type); 23 | } 24 | 25 | public Type Type { get; private set; } 26 | public string Name { get; private set; } 27 | public object Value { get; private set; } 28 | public ParameterExpression ParameterExpression { get; private set; } 29 | 30 | private void Init(string name, Type type) 31 | { 32 | Name = name; 33 | Type = type ?? typeof(object); 34 | ParameterExpression = Expression.Parameter(Type, Name); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/Models/RuleResultTree.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | namespace FastExpressionEngine.Models 9 | { 10 | /// 11 | /// Rule result class with child result heirarchy 12 | /// 13 | [ExcludeFromCodeCoverage] 14 | public class RuleResultTree 15 | { 16 | /// 17 | /// Gets or sets the rule. 18 | /// 19 | /// 20 | /// The rule. 21 | /// 22 | internal Rule Rule { get; set; } 23 | 24 | /// 25 | /// Gets or sets a value indicating whether this instance is success. 26 | /// 27 | /// 28 | /// true if this instance is success; otherwise, false. 29 | /// 30 | public bool IsSuccess { get; set; } 31 | 32 | 33 | /// 34 | /// Gets or sets the input object 35 | /// 36 | internal Dictionary Inputs { get; set; } 37 | 38 | 39 | /// 40 | /// Gets the exception message in case an error is thrown during rules calculation. 41 | /// 42 | public string ExceptionMessage { get; set; } 43 | } 44 | } -------------------------------------------------------------------------------- /src/Models/ScopedParam.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System.Diagnostics.CodeAnalysis; 5 | 6 | namespace FastExpressionEngine.Models 7 | { 8 | /// Class LocalParam. 9 | /// 10 | [ExcludeFromCodeCoverage] 11 | public class ScopedParam 12 | { 13 | /// 14 | /// Gets or sets the name of the param. 15 | /// 16 | /// 17 | /// The name of the rule. 18 | /// ] 19 | public string Name { get; set; } 20 | 21 | /// 22 | /// Gets or Sets the lambda expression which can be reference in Rule. 23 | /// 24 | public string Expression { get; set; } 25 | } 26 | 27 | [ExcludeFromCodeCoverage] 28 | public class LocalParam : ScopedParam 29 | { 30 | } 31 | } -------------------------------------------------------------------------------- /src/RuleCompiler.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Linq.Expressions; 8 | using FastExpressionEngine.Exceptions; 9 | using FastExpressionEngine.ExpressionBuilders; 10 | using FastExpressionEngine.HelperFunctions; 11 | using FastExpressionEngine.Models; 12 | 13 | namespace FastExpressionEngine 14 | { 15 | /// 16 | /// Rule compilers 17 | /// 18 | internal class RuleCompiler 19 | { 20 | /// 21 | /// The expression builder factory 22 | /// 23 | private readonly RuleExpressionBuilderFactory _expressionBuilderFactory; 24 | 25 | 26 | /// 27 | /// Initializes a new instance of the class. 28 | /// 29 | /// The expression builder factory. 30 | /// expressionBuilderFactory 31 | internal RuleCompiler(RuleExpressionBuilderFactory expressionBuilderFactory) 32 | { 33 | _expressionBuilderFactory = expressionBuilderFactory ?? 34 | throw new ArgumentNullException( 35 | $"{nameof(expressionBuilderFactory)} can't be null."); 36 | } 37 | 38 | /// 39 | /// Compiles the rule 40 | /// 41 | /// 42 | /// 43 | /// 44 | /// 45 | /// Compiled func delegate 46 | internal RuleFunc CompileRule(Rule rule, RuleExpressionType ruleExpressionType, 47 | RuleParameter[] ruleParams, Lazy globalParams) 48 | { 49 | if (rule == null) 50 | { 51 | var ex = new ArgumentNullException(nameof(rule)); 52 | throw ex; 53 | } 54 | 55 | try 56 | { 57 | var globalParamExp = globalParams.Value; 58 | var extendedRuleParams = ruleParams.Concat(globalParamExp.Select(c => 59 | new RuleParameter(c.ParameterExpression.Name, c.ParameterExpression.Type))) 60 | .ToArray(); 61 | var ruleExpression = GetDelegateForRule(rule, extendedRuleParams); 62 | 63 | 64 | return GetWrappedRuleFunc(rule, ruleExpression, ruleParams, globalParamExp); 65 | } 66 | catch (Exception ex) 67 | { 68 | var message = $"Error while compiling rule `{rule.RuleName}`: {ex.Message}"; 69 | return Helpers.ToRuleExceptionResult(rule, new RuleException(message, ex)); 70 | } 71 | } 72 | 73 | 74 | /// 75 | /// Gets the expression for rule. 76 | /// 77 | /// The rule. 78 | /// The type parameter expressions. 79 | /// The rule input exp. 80 | /// 81 | private RuleFunc GetDelegateForRule(Rule rule, RuleParameter[] ruleParams) 82 | { 83 | var scopedParamList = GetRuleExpressionParameters(rule.RuleExpressionType, rule?.LocalParams, ruleParams); 84 | 85 | var extendedRuleParams = ruleParams.Concat(scopedParamList.Select(c => 86 | new RuleParameter(c.ParameterExpression.Name, c.ParameterExpression.Type))) 87 | .ToArray(); 88 | 89 | RuleFunc ruleFn = BuildRuleFunc(rule, extendedRuleParams); 90 | ; 91 | 92 | return GetWrappedRuleFunc(rule, ruleFn, ruleParams, scopedParamList); 93 | } 94 | 95 | internal RuleExpressionParameter[] GetRuleExpressionParameters(RuleExpressionType ruleExpressionType, 96 | IEnumerable localParams, RuleParameter[] ruleParams) 97 | { 98 | var ruleExpParams = new List(); 99 | 100 | if (localParams?.Any() == true) 101 | { 102 | var parameters = ruleParams.Select(c => c.ParameterExpression) 103 | .ToList(); 104 | 105 | var expressionBuilder = GetExpressionBuilder(ruleExpressionType); 106 | 107 | foreach (var lp in localParams) 108 | { 109 | try 110 | { 111 | var lpExpression = expressionBuilder.Parse(lp.Expression, parameters.ToArray(), null); 112 | var ruleExpParam = new RuleExpressionParameter() 113 | { 114 | ParameterExpression = Expression.Parameter(lpExpression.Type, lp.Name), 115 | ValueExpression = lpExpression 116 | }; 117 | parameters.Add(ruleExpParam.ParameterExpression); 118 | ruleExpParams.Add(ruleExpParam); 119 | } 120 | catch (Exception ex) 121 | { 122 | var message = $"{ex.Message}, in ScopedParam: {lp.Name}"; 123 | throw new RuleException(message); 124 | } 125 | } 126 | } 127 | 128 | return ruleExpParams.ToArray(); 129 | } 130 | 131 | /// 132 | /// Builds the expression. 133 | /// 134 | /// The rule. 135 | /// The type parameter expressions. 136 | /// The rule input exp. 137 | /// 138 | /// 139 | private RuleFunc BuildRuleFunc(Rule rule, RuleParameter[] ruleParams) 140 | { 141 | var ruleExpressionBuilder = GetExpressionBuilder(rule.RuleExpressionType); 142 | 143 | var ruleFunc = ruleExpressionBuilder.BuildDelegateForRule(rule, ruleParams); 144 | 145 | return ruleFunc; 146 | } 147 | 148 | 149 | internal Func> CompileScopedParams(RuleExpressionType ruleExpressionType, 150 | RuleParameter[] ruleParameters, RuleExpressionParameter[] ruleExpParams) 151 | { 152 | return GetExpressionBuilder(ruleExpressionType).CompileScopedParams(ruleParameters, ruleExpParams); 153 | } 154 | 155 | private RuleFunc GetWrappedRuleFunc(Rule rule, RuleFunc ruleFunc, 156 | RuleParameter[] ruleParameters, RuleExpressionParameter[] ruleExpParams) 157 | { 158 | if (ruleExpParams.Length == 0) 159 | { 160 | return ruleFunc; 161 | } 162 | 163 | var paramDelegate = CompileScopedParams(rule.RuleExpressionType, ruleParameters, ruleExpParams); 164 | 165 | return (ruleParams) => 166 | { 167 | var inputs = ruleParams.Select(c => c.Value).ToArray(); 168 | IEnumerable scopedParams; 169 | try 170 | { 171 | var scopedParamsDict = paramDelegate(inputs); 172 | scopedParams = scopedParamsDict.Select(c => new RuleParameter(c.Key, c.Value)); 173 | } 174 | catch (Exception ex) 175 | { 176 | var message = $"Error while executing scoped params for rule `{rule.RuleName}` - {ex}"; 177 | var resultFn = Helpers.ToRuleExceptionResult(rule, new RuleException(message, ex)); 178 | return resultFn(ruleParams); 179 | } 180 | 181 | var extendedInputs = ruleParams.Concat(scopedParams); 182 | var result = ruleFunc(extendedInputs.ToArray()); 183 | return result; 184 | }; 185 | } 186 | 187 | private RuleExpressionBuilderBase GetExpressionBuilder(RuleExpressionType expressionType) 188 | { 189 | return _expressionBuilderFactory.RuleGetExpressionBuilder(expressionType); 190 | } 191 | } 192 | } -------------------------------------------------------------------------------- /src/RuleExpressionBuilderFactory.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using FastExpressionEngine.ExpressionBuilders; 6 | using FastExpressionEngine.Models; 7 | 8 | namespace FastExpressionEngine 9 | { 10 | internal class RuleExpressionBuilderFactory 11 | { 12 | private readonly LambdaExpressionBuilder _lambdaExpressionBuilder; 13 | 14 | public RuleExpressionBuilderFactory(RuleExpressionParser expressionParser) 15 | { 16 | _lambdaExpressionBuilder = new LambdaExpressionBuilder(expressionParser); 17 | } 18 | 19 | public RuleExpressionBuilderBase RuleGetExpressionBuilder(RuleExpressionType ruleExpressionType) 20 | { 21 | switch (ruleExpressionType) 22 | { 23 | case RuleExpressionType.LambdaExpression: 24 | return _lambdaExpressionBuilder; 25 | default: 26 | throw new InvalidOperationException($"{nameof(ruleExpressionType)} has not been supported yet."); 27 | } 28 | } 29 | } 30 | } --------------------------------------------------------------------------------