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

302 lines
9.5 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using Unity.VisualScripting.Antlr3.Runtime;
using UnityEngine;
namespace Unity.VisualScripting.Dependencies.NCalc
{
public class Expression
{
private Expression()
{
// Fix: the original grammar doesn't include a null identifier.
Parameters["null"] = Parameters["NULL"] = null;
}
public Expression(string expression, EvaluateOptions options = EvaluateOptions.None) : this()
{
if (string.IsNullOrEmpty(expression))
{
throw new ArgumentException("Expression can't be empty", nameof(expression));
}
// Fix: The original grammar doesn't allow double quotes for strings.
expression = expression.Replace('\"', '\'');
OriginalExpression = expression;
Options = options;
}
public Expression(LogicalExpression expression, EvaluateOptions options = EvaluateOptions.None) : this()
{
if (expression == null)
{
throw new ArgumentException("Expression can't be null", nameof(expression));
}
ParsedExpression = expression;
Options = options;
}
public event EvaluateFunctionHandler EvaluateFunction;
public event EvaluateParameterHandler EvaluateParameter;
/// <summary>
/// Textual representation of the expression to evaluate.
/// </summary>
protected readonly string OriginalExpression;
protected Dictionary<string, IEnumerator> ParameterEnumerators;
private Dictionary<string, object> _parameters;
public EvaluateOptions Options { get; set; }
public string Error { get; private set; }
public LogicalExpression ParsedExpression { get; private set; }
public Dictionary<string, object> Parameters
{
get
{
return _parameters ?? (_parameters = new Dictionary<string, object>());
}
set
{
_parameters = value;
}
}
public void UpdateUnityTimeParameters()
{
Parameters["dt"] = Parameters["DT"] = Time.deltaTime;
Parameters["second"] = Parameters["Second"] = 1 / Time.deltaTime;
}
/// <summary>
/// Pre-compiles the expression in order to check syntax errors.
/// If errors are detected, the Error property contains the message.
/// </summary>
/// <returns>True if the expression syntax is correct, otherwise false</returns>
public bool HasErrors()
{
try
{
if (ParsedExpression == null)
{
ParsedExpression = Compile(OriginalExpression, (Options & EvaluateOptions.NoCache) == EvaluateOptions.NoCache);
}
// In case HasErrors() is called multiple times for the same expression
return ParsedExpression != null && Error != null;
}
catch (Exception e)
{
Error = e.Message;
return true;
}
}
public object Evaluate(Flow flow)
{
if (HasErrors())
{
throw new EvaluationException(Error);
}
if (ParsedExpression == null)
{
ParsedExpression = Compile(OriginalExpression, (Options & EvaluateOptions.NoCache) == EvaluateOptions.NoCache);
}
var visitor = new EvaluationVisitor(flow, Options);
visitor.EvaluateFunction += EvaluateFunction;
visitor.EvaluateParameter += EvaluateParameter;
visitor.Parameters = Parameters;
// If array evaluation, execute the same expression multiple times
if ((Options & EvaluateOptions.IterateParameters) == EvaluateOptions.IterateParameters)
{
var size = -1;
ParameterEnumerators = new Dictionary<string, IEnumerator>();
foreach (var parameter in Parameters.Values)
{
if (parameter is IEnumerable enumerable)
{
var localsize = 0;
foreach (var o in enumerable)
{
localsize++;
}
if (size == -1)
{
size = localsize;
}
else if (localsize != size)
{
throw new EvaluationException("When IterateParameters option is used, IEnumerable parameters must have the same number of items.");
}
}
}
foreach (var key in Parameters.Keys)
{
var parameter = Parameters[key] as IEnumerable;
if (parameter != null)
{
ParameterEnumerators.Add(key, parameter.GetEnumerator());
}
}
var results = new List<object>();
for (var i = 0; i < size; i++)
{
foreach (var key in ParameterEnumerators.Keys)
{
var enumerator = ParameterEnumerators[key];
enumerator.MoveNext();
Parameters[key] = enumerator.Current;
}
ParsedExpression.Accept(visitor);
results.Add(visitor.Result);
}
return results;
}
else
{
ParsedExpression.Accept(visitor);
return visitor.Result;
}
}
public static LogicalExpression Compile(string expression, bool noCache)
{
LogicalExpression logicalExpression = null;
if (_cacheEnabled && !noCache)
{
try
{
Rwl.AcquireReaderLock(Timeout.Infinite);
if (_compiledExpressions.ContainsKey(expression))
{
Trace.TraceInformation("Expression retrieved from cache: " + expression);
var wr = _compiledExpressions[expression];
logicalExpression = wr.Target as LogicalExpression;
if (wr.IsAlive && logicalExpression != null)
{
return logicalExpression;
}
}
}
finally
{
Rwl.ReleaseReaderLock();
}
}
if (logicalExpression == null)
{
var lexer = new NCalcLexer(new ANTLRStringStream(expression));
var parser = new NCalcParser(new CommonTokenStream(lexer));
logicalExpression = parser.ncalcExpression().value;
if (parser.Errors != null && parser.Errors.Count > 0)
{
throw new EvaluationException(String.Join(Environment.NewLine, parser.Errors.ToArray()));
}
if (_cacheEnabled && !noCache)
{
try
{
Rwl.AcquireWriterLock(Timeout.Infinite);
_compiledExpressions[expression] = new WeakReference(logicalExpression);
}
finally
{
Rwl.ReleaseWriterLock();
}
CleanCache();
Trace.TraceInformation("Expression added to cache: " + expression);
}
}
return logicalExpression;
}
#region Cache management
private static bool _cacheEnabled = true;
private static Dictionary<string, WeakReference> _compiledExpressions = new Dictionary<string, WeakReference>();
private static readonly ReaderWriterLock Rwl = new ReaderWriterLock();
public static bool CacheEnabled
{
get
{
return _cacheEnabled;
}
set
{
_cacheEnabled = value;
if (!CacheEnabled)
{
// Clears cache
_compiledExpressions = new Dictionary<string, WeakReference>();
}
}
}
/// <summary>
/// Removes unused entries from cached compiled expression.
/// </summary>
private static void CleanCache()
{
var keysToRemove = new List<string>();
try
{
Rwl.AcquireWriterLock(Timeout.Infinite);
foreach (var de in _compiledExpressions)
{
if (!de.Value.IsAlive)
{
keysToRemove.Add(de.Key);
}
}
foreach (var key in keysToRemove)
{
_compiledExpressions.Remove(key);
Trace.TraceInformation("Cache entry released: " + key);
}
}
finally
{
Rwl.ReleaseReaderLock();
}
}
#endregion
}
}