• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

microsoft / RulesEngine / 12843565311

18 Jan 2025 11:22AM UTC coverage: 94.26% (-1.8%) from 96.012%
12843565311

push

github

web-flow
[Short-Circuiting The Evaluation While Parsing] (#638)

* short-circuiting dynamic evaluation

304 of 368 branches covered (82.61%)

19 of 20 new or added lines in 1 file covered. (95.0%)

11 existing lines in 4 files now uncovered.

624 of 662 relevant lines covered (94.26%)

103.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.33
/src/RulesEngine/ExpressionBuilders/RuleExpressionParser.cs
1
// Copyright (c) Microsoft Corporation.
2
//  Licensed under the MIT License.
3

4
using FastExpressionCompiler;
5
using RulesEngine.HelperFunctions;
6
using RulesEngine.Models;
7
using System;
8
using System.Collections.Generic;
9
using System.Linq;
10
using System.Linq.Dynamic.Core;
11
using System.Linq.Dynamic.Core.Exceptions;
12
using System.Linq.Dynamic.Core.Parser;
13
using System.Linq.Expressions;
14
using System.Reflection;
15
using System.Text.RegularExpressions;
16

17
namespace RulesEngine.ExpressionBuilders
18
{
19
    public class RuleExpressionParser
20
    {
21
        private readonly ReSettings _reSettings;
22
        private readonly IDictionary<string, MethodInfo> _methodInfo;
23

24
        public RuleExpressionParser(ReSettings reSettings = null)
97✔
25
        {
26
            _reSettings = reSettings ?? new ReSettings();
97!
27
            _methodInfo = new Dictionary<string, MethodInfo>();
97✔
28
            PopulateMethodInfo();
97✔
29
        }
97✔
30

31
        private void PopulateMethodInfo()
32
        {
33
            var dict_add = typeof(Dictionary<string, object>).GetMethod("Add", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof(string), typeof(object) }, null);
97✔
34
            _methodInfo.Add("dict_add", dict_add);
97✔
35
        }
97✔
36
        public Expression Parse(string expression, ParameterExpression[] parameters, Type returnType)
37
        {
38
            var config = new ParsingConfig {
215✔
39
                CustomTypeProvider = new CustomTypeProvider(_reSettings.CustomTypes),
215✔
40
                IsCaseSensitive = _reSettings.IsExpressionCaseSensitive
215✔
41
            };
215✔
42

43
            // Instead of immediately returning default values, allow for expression parsing to handle dynamic evaluation.
44
            try
45
            {
46
                return new ExpressionParser(parameters, expression, Array.Empty<object>(), config).Parse(returnType);
215✔
47
            }
48
            catch (ParseException)
18✔
49
            {
50
                return Expression.Constant(GetDefaultValueForType(returnType));
18✔
51
            }
52
            catch (Exception ex)
7✔
53
            {
54
                throw new Exception($"Expression parsing error: {ex.Message}", ex);
7✔
55
            }
56
        }
208✔
57

58
        private object GetDefaultValueForType(Type type)
59
        {
60
            if (type == typeof(bool))
18✔
61
                return false;
15✔
62
            if (type == typeof(int) || type == typeof(float) || type == typeof(double))
3!
NEW
63
                return int.MinValue;
×
64
            return null;
3✔
65
        }
66

67
        public Func<object[], T> Compile<T>(string expression, RuleParameter[] ruleParams)
68
        {
69
            var rtype = typeof(T);
168✔
70
            if (rtype == typeof(object))
168✔
71
            {
72
                rtype = null;
14✔
73
            }
74
            var parameterExpressions = GetParameterExpression(ruleParams).ToArray();
168✔
75

76
            var e = Parse(expression, parameterExpressions, rtype);
168✔
77
            if (rtype == null)
161✔
78
            {
79
                e = Expression.Convert(e, typeof(T));
14✔
80
            }
81
            var expressionBody = new List<Expression>() { e };
161✔
82
            var wrappedExpression = WrapExpression<T>(expressionBody, parameterExpressions, new ParameterExpression[] { });
161✔
83
            return CompileExpression(wrappedExpression);
161✔
84

85
        }
86

87
        private Func<object[], T> CompileExpression<T>(Expression<Func<object[], T>> expression)
88
        {
89
            if (_reSettings.UseFastExpressionCompiler)
202✔
90
            {
91
                return expression.CompileFast();
193✔
92
            }
93
            return expression.Compile();
9✔
94
        }
95

96
        private Expression<Func<object[], T>> WrapExpression<T>(List<Expression> expressionList, ParameterExpression[] parameters, ParameterExpression[] variables)
97
        {
98
            var argExp = Expression.Parameter(typeof(object[]), "args");
202✔
99
            var paramExps = parameters.Select((c, i) => {
202✔
100
                var arg = Expression.ArrayAccess(argExp, Expression.Constant(i));
423✔
101
                return (Expression)Expression.Assign(c, Expression.Convert(arg, c.Type));
423✔
102
            });
202✔
103
            var blockExpSteps = paramExps.Concat(expressionList);
202✔
104
            var blockExp = Expression.Block(parameters.Concat(variables), blockExpSteps);
202✔
105
            return Expression.Lambda<Func<object[], T>>(blockExp, argExp);
202✔
106
        }
107

108
        internal Func<object[], Dictionary<string, object>> CompileRuleExpressionParameters(RuleParameter[] ruleParams, RuleExpressionParameter[] ruleExpParams = null)
109
        {
110
            ruleExpParams = ruleExpParams ?? new RuleExpressionParameter[] { };
41!
111
            var expression = CreateDictionaryExpression(ruleParams, ruleExpParams);
41✔
112
            return CompileExpression(expression);
41✔
113
        }
114

115
        public T Evaluate<T>(string expression, RuleParameter[] ruleParams)
116
        {
117
            var func = Compile<T>(expression, ruleParams);
15✔
118
            return func(ruleParams.Select(c => c.Value).ToArray());
28✔
119
        }
120

121
        private IEnumerable<Expression> CreateAssignedParameterExpression(RuleExpressionParameter[] ruleExpParams)
122
        {
123
            return ruleExpParams.Select((c, i) => {
41✔
124
                return Expression.Assign(c.ParameterExpression, c.ValueExpression);
50✔
125
            });
41✔
126
        }
127

128
        // <summary>
129
        /// Gets the parameter expression.
130
        /// </summary>
131
        /// <param name="ruleParams">The types.</param>
132
        /// <returns></returns>
133
        /// <exception cref="ArgumentException">
134
        /// types
135
        /// or
136
        /// type
137
        /// </exception>
138
        private IEnumerable<ParameterExpression> GetParameterExpression(RuleParameter[] ruleParams)
139
        {
140
            foreach (var ruleParam in ruleParams)
1,060✔
141
            {
142
                if (ruleParam == null)
362!
143
                {
144
                    throw new ArgumentException($"{nameof(ruleParam)} can't be null.");
×
145
                }
146

147
                yield return ruleParam.ParameterExpression;
362✔
148
            }
149
        }
168✔
150

151
        private Expression<Func<object[], Dictionary<string, object>>> CreateDictionaryExpression(RuleParameter[] ruleParams, RuleExpressionParameter[] ruleExpParams)
152
        {
153
            var body = new List<Expression>();
41✔
154
            var paramExp = new List<ParameterExpression>();
41✔
155
            var variableExp = new List<ParameterExpression>();
41✔
156

157

158
            var variableExpressions = CreateAssignedParameterExpression(ruleExpParams);
41✔
159

160
            body.AddRange(variableExpressions);
41✔
161

162
            var dict = Expression.Variable(typeof(Dictionary<string, object>));
41✔
163
            var add = _methodInfo["dict_add"];
41✔
164

165
            body.Add(Expression.Assign(dict, Expression.New(typeof(Dictionary<string, object>))));
41✔
166
            variableExp.Add(dict);
41✔
167

168
            for (var i = 0; i < ruleParams.Length; i++)
226✔
169
            {
170
                paramExp.Add(ruleParams[i].ParameterExpression);
72✔
171
            }
172
            for (var i = 0; i < ruleExpParams.Length; i++)
182✔
173
            {
174
                var key = Expression.Constant(ruleExpParams[i].ParameterExpression.Name);
50✔
175
                var value = Expression.Convert(ruleExpParams[i].ParameterExpression, typeof(object));
50✔
176
                variableExp.Add(ruleExpParams[i].ParameterExpression);
50✔
177
                body.Add(Expression.Call(dict, add, key, value));
50✔
178

179
            }
180
            // Return value
181
            body.Add(dict);
41✔
182

183
            return WrapExpression<Dictionary<string, object>>(body, paramExp.ToArray(), variableExp.ToArray());
41✔
184
        }
185
    }
186
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc