A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.Globalization;
4using UnityEngine;
5
6namespace Unity.VisualScripting.Dependencies.NCalc
7{
8 public class EvaluationVisitor : LogicalExpressionVisitor
9 {
10 public EvaluationVisitor(Flow flow, EvaluateOptions options)
11 {
12 this.flow = flow;
13 this.options = options;
14 }
15
16 public event EvaluateFunctionHandler EvaluateFunction;
17
18 public event EvaluateParameterHandler EvaluateParameter;
19
20 private readonly Flow flow;
21
22 private readonly EvaluateOptions options;
23
24 private bool IgnoreCase => options.HasFlag(EvaluateOptions.IgnoreCase);
25
26 public object Result { get; private set; }
27
28 public Dictionary<string, object> Parameters { get; set; }
29
30 private object Evaluate(LogicalExpression expression)
31 {
32 expression.Accept(this);
33 return Result;
34 }
35
36 public override void Visit(TernaryExpression ternary)
37 {
38 // Evaluates the left expression and saves the value
39 ternary.LeftExpression.Accept(this);
40
41 var left = ConversionUtility.Convert<bool>(Result);
42
43 if (left)
44 {
45 ternary.MiddleExpression.Accept(this);
46 }
47 else
48 {
49 ternary.RightExpression.Accept(this);
50 }
51 }
52
53 public override void Visit(BinaryExpression binary)
54 {
55 // Simulate Lazy<Func<>> behavior for late evaluation
56 object leftValue = null;
57 Func<object> left = () =>
58 {
59 if (leftValue == null)
60 {
61 binary.LeftExpression.Accept(this);
62 leftValue = Result;
63 }
64 return leftValue;
65 };
66
67 // Simulate Lazy<Func<>> behavior for late evaluation
68 object rightValue = null;
69 Func<object> right = () =>
70 {
71 if (rightValue == null)
72 {
73 binary.RightExpression.Accept(this);
74 rightValue = Result;
75 }
76 return rightValue;
77 };
78
79 switch (binary.Type)
80 {
81 case BinaryExpressionType.And:
82 Result = ConversionUtility.Convert<bool>(left()) && ConversionUtility.Convert<bool>(right());
83 break;
84
85 case BinaryExpressionType.Or:
86 Result = ConversionUtility.Convert<bool>(left()) || ConversionUtility.Convert<bool>(right());
87 break;
88
89 case BinaryExpressionType.Div:
90 Result = OperatorUtility.Divide(left(), right());
91 break;
92
93 case BinaryExpressionType.Equal:
94 Result = OperatorUtility.Equal(left(), right());
95 break;
96
97 case BinaryExpressionType.Greater:
98 Result = OperatorUtility.GreaterThan(left(), right());
99 break;
100
101 case BinaryExpressionType.GreaterOrEqual:
102 Result = OperatorUtility.GreaterThanOrEqual(left(), right());
103 break;
104
105 case BinaryExpressionType.Lesser:
106 Result = OperatorUtility.LessThan(left(), right());
107 break;
108
109 case BinaryExpressionType.LesserOrEqual:
110 Result = OperatorUtility.LessThanOrEqual(left(), right());
111 break;
112
113 case BinaryExpressionType.Minus:
114 Result = OperatorUtility.Subtract(left(), right());
115 break;
116
117 case BinaryExpressionType.Modulo:
118 Result = OperatorUtility.Modulo(left(), right());
119 break;
120
121 case BinaryExpressionType.NotEqual:
122 Result = OperatorUtility.NotEqual(left(), right());
123 break;
124
125 case BinaryExpressionType.Plus:
126 Result = OperatorUtility.Add(left(), right());
127 break;
128
129 case BinaryExpressionType.Times:
130 Result = OperatorUtility.Multiply(left(), right());
131 break;
132
133 case BinaryExpressionType.BitwiseAnd:
134 Result = OperatorUtility.And(left(), right());
135 break;
136
137 case BinaryExpressionType.BitwiseOr:
138 Result = OperatorUtility.Or(left(), right());
139 break;
140
141 case BinaryExpressionType.BitwiseXOr:
142 Result = OperatorUtility.ExclusiveOr(left(), right());
143 break;
144
145 case BinaryExpressionType.LeftShift:
146 Result = OperatorUtility.LeftShift(left(), right());
147 break;
148
149 case BinaryExpressionType.RightShift:
150 Result = OperatorUtility.RightShift(left(), right());
151 break;
152 }
153 }
154
155 public override void Visit(UnaryExpression unary)
156 {
157 // Recursively evaluates the underlying expression
158 unary.Expression.Accept(this);
159
160 switch (unary.Type)
161 {
162 case UnaryExpressionType.Not:
163 Result = !ConversionUtility.Convert<bool>(Result);
164 break;
165
166 case UnaryExpressionType.Negate:
167 Result = OperatorUtility.Negate(Result);
168 break;
169
170 case UnaryExpressionType.BitwiseNot:
171 Result = OperatorUtility.Not(Result);
172 break;
173 }
174 }
175
176 public override void Visit(ValueExpression value)
177 {
178 Result = value.Value;
179 }
180
181 public override void Visit(FunctionExpression function)
182 {
183 var args = new FunctionArgs
184 {
185 Parameters = new Expression[function.Expressions.Length]
186 };
187
188 // Don't call parameters right now, instead let the function do it as needed.
189 // Some parameters shouldn't be called, for instance, in a if(), the "not" value might be a division by zero
190 // Evaluating every value could produce unexpected behaviour
191 for (var i = 0; i < function.Expressions.Length; i++)
192 {
193 args.Parameters[i] = new Expression(function.Expressions[i], options);
194 args.Parameters[i].EvaluateFunction += EvaluateFunction;
195 args.Parameters[i].EvaluateParameter += EvaluateParameter;
196
197 // Assign the parameters of the Expression to the arguments so that custom Functions and Parameters can use them
198 args.Parameters[i].Parameters = Parameters;
199 }
200
201 // Calls external implementation
202 OnEvaluateFunction(IgnoreCase ? function.Identifier.Name.ToLower() : function.Identifier.Name, args);
203
204 // If an external implementation was found get the result back
205 if (args.HasResult)
206 {
207 Result = args.Result;
208 return;
209 }
210
211 switch (function.Identifier.Name.ToLower(CultureInfo.InvariantCulture))
212 {
213 case "abs":
214 CheckCase(function, "Abs");
215 CheckExactArgumentCount(function, 1);
216 Result = Mathf.Abs(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0])));
217 break;
218
219 case "acos":
220 CheckCase(function, "Acos");
221 CheckExactArgumentCount(function, 1);
222 Result = Mathf.Acos(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0])));
223 break;
224
225 case "asin":
226 CheckCase(function, "Asin");
227 CheckExactArgumentCount(function, 1);
228 Result = Mathf.Asin(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0])));
229 break;
230
231 case "atan":
232 CheckCase(function, "Atan");
233 CheckExactArgumentCount(function, 1);
234 Result = Mathf.Atan(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0])));
235 break;
236
237 case "ceil":
238 CheckCase(function, "Ceil");
239 CheckExactArgumentCount(function, 1);
240 Result = Mathf.Ceil(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0])));
241 break;
242
243 case "cos":
244 CheckCase(function, "Cos");
245 CheckExactArgumentCount(function, 1);
246 Result = Mathf.Cos(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0])));
247 break;
248
249 case "exp":
250 CheckCase(function, "Exp");
251 CheckExactArgumentCount(function, 1);
252 Result = Mathf.Exp(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0])));
253 break;
254
255 case "floor":
256 CheckCase(function, "Floor");
257 CheckExactArgumentCount(function, 1);
258 Result = Mathf.Floor(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0])));
259 break;
260
261 case "log":
262 CheckCase(function, "Log");
263 CheckExactArgumentCount(function, 2);
264 Result = Mathf.Log(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0])), ConversionUtility.Convert<float>(Evaluate(function.Expressions[1])));
265 break;
266
267 case "log10":
268 CheckCase(function, "Log10");
269 CheckExactArgumentCount(function, 1);
270 Result = Mathf.Log10(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0])));
271 break;
272
273 case "pow":
274 CheckCase(function, "Pow");
275 CheckExactArgumentCount(function, 2);
276 Result = Mathf.Pow(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0])), ConversionUtility.Convert<float>(Evaluate(function.Expressions[1])));
277 break;
278
279 case "round":
280 CheckCase(function, "Round");
281 CheckExactArgumentCount(function, 1);
282 //var rounding = (options & EvaluateOptions.RoundAwayFromZero) == EvaluateOptions.RoundAwayFromZero ? MidpointRounding.AwayFromZero : MidpointRounding.ToEven;
283 Result = Mathf.Round(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0])));
284 break;
285
286 case "sign":
287 CheckCase(function, "Sign");
288 CheckExactArgumentCount(function, 1);
289 Result = Mathf.Sign(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0])));
290
291 break;
292
293 case "sin":
294 CheckCase(function, "Sin");
295 CheckExactArgumentCount(function, 1);
296 Result = Mathf.Sin(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0])));
297 break;
298
299 case "sqrt":
300 CheckCase(function, "Sqrt");
301 CheckExactArgumentCount(function, 1);
302 Result = Mathf.Sqrt(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0])));
303
304 break;
305
306 case "tan":
307 CheckCase(function, "Tan");
308 CheckExactArgumentCount(function, 1);
309 Result = Mathf.Tan(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0])));
310
311 break;
312
313 case "max":
314 CheckCase(function, "Max");
315 CheckExactArgumentCount(function, 2);
316 Result = Mathf.Max(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0])), ConversionUtility.Convert<float>(Evaluate(function.Expressions[1])));
317 break;
318
319 case "min":
320 CheckCase(function, "Min");
321 CheckExactArgumentCount(function, 2);
322 Result = Mathf.Min(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0])), ConversionUtility.Convert<float>(Evaluate(function.Expressions[1])));
323 break;
324
325 case "in":
326 CheckCase(function, "In");
327 CheckExactArgumentCount(function, 2);
328
329 var parameter = Evaluate(function.Expressions[0]);
330
331 var evaluation = false;
332
333 // Goes through any values, and stop whe one is found
334 for (var i = 1; i < function.Expressions.Length; i++)
335 {
336 var argument = Evaluate(function.Expressions[i]);
337
338 if (Equals(parameter, argument))
339 {
340 evaluation = true;
341 break;
342 }
343 }
344
345 Result = evaluation;
346 break;
347
348 default:
349 throw new ArgumentException("Function not found", function.Identifier.Name);
350 }
351 }
352
353 private void CheckCase(FunctionExpression function, string reference)
354 {
355 var called = function.Identifier.Name;
356
357 if (IgnoreCase)
358 {
359 if (string.Equals(called, reference, StringComparison.InvariantCultureIgnoreCase))
360 {
361 return;
362 }
363
364 throw new ArgumentException("Function not found.", called);
365 }
366
367 if (called != reference)
368 {
369 throw new ArgumentException($"Function not found: '{called}'. Try '{reference}' instead.");
370 }
371 }
372
373 private void OnEvaluateFunction(string name, FunctionArgs args)
374 {
375 EvaluateFunction?.Invoke(flow, name, args);
376 }
377
378 public override void Visit(IdentifierExpression identifier)
379 {
380 if (Parameters.ContainsKey(identifier.Name))
381 {
382 // The parameter is defined in the dictionary
383 if (Parameters[identifier.Name] is Expression)
384 {
385 // The parameter is itself another Expression
386 var expression = (Expression)Parameters[identifier.Name];
387
388 // Overloads parameters
389 foreach (var p in Parameters)
390 {
391 expression.Parameters[p.Key] = p.Value;
392 }
393
394 expression.EvaluateFunction += EvaluateFunction;
395 expression.EvaluateParameter += EvaluateParameter;
396
397 Result = ((Expression)Parameters[identifier.Name]).Evaluate(flow);
398 }
399 else
400 {
401 Result = Parameters[identifier.Name];
402 }
403 }
404 else
405 {
406 // The parameter should be defined in a callback method
407 var args = new ParameterArgs();
408
409 // Calls external implementation
410 OnEvaluateParameter(identifier.Name, args);
411
412 if (!args.HasResult)
413 {
414 throw new ArgumentException("Parameter was not defined", identifier.Name);
415 }
416
417 Result = args.Result;
418 }
419 }
420
421 private void OnEvaluateParameter(string name, ParameterArgs args)
422 {
423 EvaluateParameter?.Invoke(flow, name, args);
424 }
425
426 public static void CheckExactArgumentCount(FunctionExpression function, int count)
427 {
428 if (function.Expressions.Length != count)
429 {
430 throw new ArgumentException($"{function.Identifier.Name}() takes at exactly {count} arguments. {function.Expressions.Length} provided.");
431 }
432 }
433
434 public static void CheckMinArgumentCount(FunctionExpression function, int count)
435 {
436 if (function.Expressions.Length < count)
437 {
438 throw new ArgumentException($"{function.Identifier.Name}() takes at at least {count} arguments. {function.Expressions.Length} provided.");
439 }
440 }
441
442 private delegate T Func<T>();
443 }
444}