UnityGame/Library/PackageCache/com.unity.visualscripting/Runtime/VisualScripting.Flow/Dependencies/NCalc/EvaluationVisitor.cs
2024-10-27 10:53:47 +03:00

445 lines
16 KiB
C#

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