RulesEngine/src/RulesEngine/ExpressionBuilders/RuleExpressionParser.cs

93 lines
3.6 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using FastExpressionCompiler;
using Microsoft.Extensions.Caching.Memory;
using RulesEngine.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Linq.Expressions;
namespace RulesEngine.ExpressionBuilders
{
public class RuleExpressionParser
{
private readonly ReSettings _reSettings;
private static IMemoryCache _memoryCache;
public RuleExpressionParser(ReSettings reSettings)
{
_reSettings = reSettings;
_memoryCache = new MemoryCache(new MemoryCacheOptions {
SizeLimit = 1000
});
}
public Func<object[], T> Compile<T>(string expression, RuleParameter[] ruleParams)
{
var cacheKey = GetCacheKey(expression, ruleParams, typeof(T));
return _memoryCache.GetOrCreate(cacheKey, (entry) => {
entry.SetSize(1);
var config = new ParsingConfig { CustomTypeProvider = new CustomTypeProvider(_reSettings.CustomTypes) };
var typeParamExpressions = GetParameterExpression(ruleParams).ToArray();
var e = DynamicExpressionParser.ParseLambda(config, true, typeParamExpressions.ToArray(), typeof(T), expression);
var wrappedExpression = WrapExpression<T>(e, typeParamExpressions);
return wrappedExpression.CompileFast<Func<object[], T>>();
});
}
private Expression<Func<object[], T>> WrapExpression<T>(LambdaExpression expression, ParameterExpression[] parameters)
{
var argExp = Expression.Parameter(typeof(object[]), "args");
var paramExps = parameters.Select((c, i) => {
var arg = Expression.ArrayAccess(argExp, Expression.Constant(i));
return (Expression)Expression.Assign(c, Expression.Convert(arg, c.Type));
});
var blockExpSteps = paramExps.Concat(new List<Expression> { expression.Body });
var blockExp = Expression.Block(parameters, blockExpSteps);
return Expression.Lambda<Func<object[], T>>(blockExp, argExp);
}
public T Evaluate<T>(string expression, RuleParameter[] ruleParams)
{
var func = Compile<T>(expression, ruleParams);
return func(ruleParams.Select(c => c.Value).ToArray());
}
// <summary>
/// Gets the parameter expression.
/// </summary>
/// <param name="ruleParams">The types.</param>
/// <returns></returns>
/// <exception cref="ArgumentException">
/// types
/// or
/// type
/// </exception>
private IEnumerable<ParameterExpression> GetParameterExpression(params RuleParameter[] ruleParams)
{
foreach (var ruleParam in ruleParams)
{
if (ruleParam == null)
{
throw new ArgumentException($"{nameof(ruleParam)} can't be null.");
}
yield return Expression.Parameter(ruleParam.Type, ruleParam.Name);
}
}
private string GetCacheKey(string expression, RuleParameter[] ruleParameters, Type returnType)
{
var paramKey = string.Join("|", ruleParameters.Select(c => c.Type.ToString()));
var returnTypeKey = returnType?.ToString() ?? "null";
var combined = $"Expression:{expression}-Params:{paramKey}-ReturnType:{returnTypeKey}";
return combined;
}
}
}