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}