445 lines
16 KiB
C#
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>();
|
|
}
|
|
}
|