123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301 |
- 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
- }
- }
|