Maze / Library /PackageCache /com.unity.visualscripting@1.8.0 /Runtime /VisualScripting.Flow /Dependencies /NCalc /EvaluationVisitor.cs
| using System; | |
| using System.Collections.Generic; | |
| using System.Globalization; | |
| using UnityEngine; | |
| namespace Unity.VisualScripting.Dependencies.NCalc | |
| { | |
| public class EvaluationVisitor : LogicalExpressionVisitor | |
| { | |
| public EvaluationVisitor(Flow flow, EvaluateOptions options) | |
| { | |
| this.flow = flow; | |
| this.options = options; | |
| } | |
| public event EvaluateFunctionHandler EvaluateFunction; | |
| public event EvaluateParameterHandler EvaluateParameter; | |
| private readonly Flow flow; | |
| private readonly EvaluateOptions options; | |
| private bool IgnoreCase => options.HasFlag(EvaluateOptions.IgnoreCase); | |
| public object Result { get; private set; } | |
| public Dictionary<string, object> Parameters { get; set; } | |
| private object Evaluate(LogicalExpression expression) | |
| { | |
| expression.Accept(this); | |
| return Result; | |
| } | |
| public override void Visit(TernaryExpression ternary) | |
| { | |
| // Evaluates the left expression and saves the value | |
| ternary.LeftExpression.Accept(this); | |
| var left = ConversionUtility.Convert<bool>(Result); | |
| if (left) | |
| { | |
| ternary.MiddleExpression.Accept(this); | |
| } | |
| else | |
| { | |
| ternary.RightExpression.Accept(this); | |
| } | |
| } | |
| public override void Visit(BinaryExpression binary) | |
| { | |
| // Simulate Lazy<Func<>> behavior for late evaluation | |
| object leftValue = null; | |
| Func<object> left = () => | |
| { | |
| if (leftValue == null) | |
| { | |
| binary.LeftExpression.Accept(this); | |
| leftValue = Result; | |
| } | |
| return leftValue; | |
| }; | |
| // Simulate Lazy<Func<>> behavior for late evaluation | |
| object rightValue = null; | |
| Func<object> right = () => | |
| { | |
| if (rightValue == null) | |
| { | |
| binary.RightExpression.Accept(this); | |
| rightValue = Result; | |
| } | |
| return rightValue; | |
| }; | |
| switch (binary.Type) | |
| { | |
| case BinaryExpressionType.And: | |
| Result = ConversionUtility.Convert<bool>(left()) && ConversionUtility.Convert<bool>(right()); | |
| break; | |
| case BinaryExpressionType.Or: | |
| Result = ConversionUtility.Convert<bool>(left()) || ConversionUtility.Convert<bool>(right()); | |
| break; | |
| case BinaryExpressionType.Div: | |
| Result = OperatorUtility.Divide(left(), right()); | |
| break; | |
| case BinaryExpressionType.Equal: | |
| Result = OperatorUtility.Equal(left(), right()); | |
| break; | |
| case BinaryExpressionType.Greater: | |
| Result = OperatorUtility.GreaterThan(left(), right()); | |
| break; | |
| case BinaryExpressionType.GreaterOrEqual: | |
| Result = OperatorUtility.GreaterThanOrEqual(left(), right()); | |
| break; | |
| case BinaryExpressionType.Lesser: | |
| Result = OperatorUtility.LessThan(left(), right()); | |
| break; | |
| case BinaryExpressionType.LesserOrEqual: | |
| Result = OperatorUtility.LessThanOrEqual(left(), right()); | |
| break; | |
| case BinaryExpressionType.Minus: | |
| Result = OperatorUtility.Subtract(left(), right()); | |
| break; | |
| case BinaryExpressionType.Modulo: | |
| Result = OperatorUtility.Modulo(left(), right()); | |
| break; | |
| case BinaryExpressionType.NotEqual: | |
| Result = OperatorUtility.NotEqual(left(), right()); | |
| break; | |
| case BinaryExpressionType.Plus: | |
| Result = OperatorUtility.Add(left(), right()); | |
| break; | |
| case BinaryExpressionType.Times: | |
| Result = OperatorUtility.Multiply(left(), right()); | |
| break; | |
| case BinaryExpressionType.BitwiseAnd: | |
| Result = OperatorUtility.And(left(), right()); | |
| break; | |
| case BinaryExpressionType.BitwiseOr: | |
| Result = OperatorUtility.Or(left(), right()); | |
| break; | |
| case BinaryExpressionType.BitwiseXOr: | |
| Result = OperatorUtility.ExclusiveOr(left(), right()); | |
| break; | |
| case BinaryExpressionType.LeftShift: | |
| Result = OperatorUtility.LeftShift(left(), right()); | |
| break; | |
| case BinaryExpressionType.RightShift: | |
| Result = OperatorUtility.RightShift(left(), right()); | |
| break; | |
| } | |
| } | |
| public override void Visit(UnaryExpression unary) | |
| { | |
| // Recursively evaluates the underlying expression | |
| unary.Expression.Accept(this); | |
| switch (unary.Type) | |
| { | |
| case UnaryExpressionType.Not: | |
| Result = !ConversionUtility.Convert<bool>(Result); | |
| break; | |
| case UnaryExpressionType.Negate: | |
| Result = OperatorUtility.Negate(Result); | |
| break; | |
| case UnaryExpressionType.BitwiseNot: | |
| Result = OperatorUtility.Not(Result); | |
| break; | |
| } | |
| } | |
| public override void Visit(ValueExpression value) | |
| { | |
| Result = value.Value; | |
| } | |
| public override void Visit(FunctionExpression function) | |
| { | |
| var args = new FunctionArgs | |
| { | |
| Parameters = new Expression[function.Expressions.Length] | |
| }; | |
| // Don't call parameters right now, instead let the function do it as needed. | |
| // Some parameters shouldn't be called, for instance, in a if(), the "not" value might be a division by zero | |
| // Evaluating every value could produce unexpected behaviour | |
| for (var i = 0; i < function.Expressions.Length; i++) | |
| { | |
| args.Parameters[i] = new Expression(function.Expressions[i], options); | |
| args.Parameters[i].EvaluateFunction += EvaluateFunction; | |
| args.Parameters[i].EvaluateParameter += EvaluateParameter; | |
| // Assign the parameters of the Expression to the arguments so that custom Functions and Parameters can use them | |
| args.Parameters[i].Parameters = Parameters; | |
| } | |
| // Calls external implementation | |
| OnEvaluateFunction(IgnoreCase ? function.Identifier.Name.ToLower() : function.Identifier.Name, args); | |
| // If an external implementation was found get the result back | |
| if (args.HasResult) | |
| { | |
| Result = args.Result; | |
| return; | |
| } | |
| switch (function.Identifier.Name.ToLower(CultureInfo.InvariantCulture)) | |
| { | |
| case "abs": | |
| CheckCase(function, "Abs"); | |
| CheckExactArgumentCount(function, 1); | |
| Result = Mathf.Abs(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0]))); | |
| break; | |
| case "acos": | |
| CheckCase(function, "Acos"); | |
| CheckExactArgumentCount(function, 1); | |
| Result = Mathf.Acos(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0]))); | |
| break; | |
| case "asin": | |
| CheckCase(function, "Asin"); | |
| CheckExactArgumentCount(function, 1); | |
| Result = Mathf.Asin(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0]))); | |
| break; | |
| case "atan": | |
| CheckCase(function, "Atan"); | |
| CheckExactArgumentCount(function, 1); | |
| Result = Mathf.Atan(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0]))); | |
| break; | |
| case "ceil": | |
| CheckCase(function, "Ceil"); | |
| CheckExactArgumentCount(function, 1); | |
| Result = Mathf.Ceil(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0]))); | |
| break; | |
| case "cos": | |
| CheckCase(function, "Cos"); | |
| CheckExactArgumentCount(function, 1); | |
| Result = Mathf.Cos(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0]))); | |
| break; | |
| case "exp": | |
| CheckCase(function, "Exp"); | |
| CheckExactArgumentCount(function, 1); | |
| Result = Mathf.Exp(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0]))); | |
| break; | |
| case "floor": | |
| CheckCase(function, "Floor"); | |
| CheckExactArgumentCount(function, 1); | |
| Result = Mathf.Floor(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0]))); | |
| break; | |
| case "log": | |
| CheckCase(function, "Log"); | |
| CheckExactArgumentCount(function, 2); | |
| Result = Mathf.Log(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0])), ConversionUtility.Convert<float>(Evaluate(function.Expressions[1]))); | |
| break; | |
| case "log10": | |
| CheckCase(function, "Log10"); | |
| CheckExactArgumentCount(function, 1); | |
| Result = Mathf.Log10(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0]))); | |
| break; | |
| case "pow": | |
| CheckCase(function, "Pow"); | |
| CheckExactArgumentCount(function, 2); | |
| Result = Mathf.Pow(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0])), ConversionUtility.Convert<float>(Evaluate(function.Expressions[1]))); | |
| break; | |
| case "round": | |
| CheckCase(function, "Round"); | |
| CheckExactArgumentCount(function, 1); | |
| //var rounding = (options & EvaluateOptions.RoundAwayFromZero) == EvaluateOptions.RoundAwayFromZero ? MidpointRounding.AwayFromZero : MidpointRounding.ToEven; | |
| Result = Mathf.Round(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0]))); | |
| break; | |
| case "sign": | |
| CheckCase(function, "Sign"); | |
| CheckExactArgumentCount(function, 1); | |
| Result = Mathf.Sign(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0]))); | |
| break; | |
| case "sin": | |
| CheckCase(function, "Sin"); | |
| CheckExactArgumentCount(function, 1); | |
| Result = Mathf.Sin(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0]))); | |
| break; | |
| case "sqrt": | |
| CheckCase(function, "Sqrt"); | |
| CheckExactArgumentCount(function, 1); | |
| Result = Mathf.Sqrt(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0]))); | |
| break; | |
| case "tan": | |
| CheckCase(function, "Tan"); | |
| CheckExactArgumentCount(function, 1); | |
| Result = Mathf.Tan(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0]))); | |
| break; | |
| case "max": | |
| CheckCase(function, "Max"); | |
| CheckExactArgumentCount(function, 2); | |
| Result = Mathf.Max(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0])), ConversionUtility.Convert<float>(Evaluate(function.Expressions[1]))); | |
| break; | |
| case "min": | |
| CheckCase(function, "Min"); | |
| CheckExactArgumentCount(function, 2); | |
| Result = Mathf.Min(ConversionUtility.Convert<float>(Evaluate(function.Expressions[0])), ConversionUtility.Convert<float>(Evaluate(function.Expressions[1]))); | |
| break; | |
| case "in": | |
| CheckCase(function, "In"); | |
| CheckExactArgumentCount(function, 2); | |
| var parameter = Evaluate(function.Expressions[0]); | |
| var evaluation = false; | |
| // Goes through any values, and stop whe one is found | |
| for (var i = 1; i < function.Expressions.Length; i++) | |
| { | |
| var argument = Evaluate(function.Expressions[i]); | |
| if (Equals(parameter, argument)) | |
| { | |
| evaluation = true; | |
| break; | |
| } | |
| } | |
| Result = evaluation; | |
| break; | |
| default: | |
| throw new ArgumentException("Function not found", function.Identifier.Name); | |
| } | |
| } | |
| private void CheckCase(FunctionExpression function, string reference) | |
| { | |
| var called = function.Identifier.Name; | |
| if (IgnoreCase) | |
| { | |
| if (string.Equals(called, reference, StringComparison.InvariantCultureIgnoreCase)) | |
| { | |
| return; | |
| } | |
| throw new ArgumentException("Function not found.", called); | |
| } | |
| if (called != reference) | |
| { | |
| throw new ArgumentException($"Function not found: '{called}'. Try '{reference}' instead."); | |
| } | |
| } | |
| private void OnEvaluateFunction(string name, FunctionArgs args) | |
| { | |
| EvaluateFunction?.Invoke(flow, name, args); | |
| } | |
| public override void Visit(IdentifierExpression identifier) | |
| { | |
| if (Parameters.ContainsKey(identifier.Name)) | |
| { | |
| // The parameter is defined in the dictionary | |
| if (Parameters[identifier.Name] is Expression) | |
| { | |
| // The parameter is itself another Expression | |
| var expression = (Expression)Parameters[identifier.Name]; | |
| // Overloads parameters | |
| foreach (var p in Parameters) | |
| { | |
| expression.Parameters[p.Key] = p.Value; | |
| } | |
| expression.EvaluateFunction += EvaluateFunction; | |
| expression.EvaluateParameter += EvaluateParameter; | |
| Result = ((Expression)Parameters[identifier.Name]).Evaluate(flow); | |
| } | |
| else | |
| { | |
| Result = Parameters[identifier.Name]; | |
| } | |
| } | |
| else | |
| { | |
| // The parameter should be defined in a callback method | |
| var args = new ParameterArgs(); | |
| // Calls external implementation | |
| OnEvaluateParameter(identifier.Name, args); | |
| if (!args.HasResult) | |
| { | |
| throw new ArgumentException("Parameter was not defined", identifier.Name); | |
| } | |
| Result = args.Result; | |
| } | |
| } | |
| private void OnEvaluateParameter(string name, ParameterArgs args) | |
| { | |
| EvaluateParameter?.Invoke(flow, name, args); | |
| } | |
| public static void CheckExactArgumentCount(FunctionExpression function, int count) | |
| { | |
| if (function.Expressions.Length != count) | |
| { | |
| throw new ArgumentException($"{function.Identifier.Name}() takes at exactly {count} arguments. {function.Expressions.Length} provided."); | |
| } | |
| } | |
| public static void CheckMinArgumentCount(FunctionExpression function, int count) | |
| { | |
| if (function.Expressions.Length < count) | |
| { | |
| throw new ArgumentException($"{function.Identifier.Name}() takes at at least {count} arguments. {function.Expressions.Length} provided."); | |
| } | |
| } | |
| private delegate T Func<T>(); | |
| } | |
| } | |