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