123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444 |
- 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>();
- }
- }
|