A game about forced loneliness, made by TACStudios
1using System; 2using System.Collections; 3using System.Collections.Generic; 4using System.Diagnostics; 5using System.Threading; 6using Unity.VisualScripting.Antlr3.Runtime; 7using UnityEngine; 8 9namespace Unity.VisualScripting.Dependencies.NCalc 10{ 11 public class Expression 12 { 13 private Expression() 14 { 15 // Fix: the original grammar doesn't include a null identifier. 16 Parameters["null"] = Parameters["NULL"] = null; 17 } 18 19 public Expression(string expression, EvaluateOptions options = EvaluateOptions.None) : this() 20 { 21 if (string.IsNullOrEmpty(expression)) 22 { 23 throw new ArgumentException("Expression can't be empty", nameof(expression)); 24 } 25 26 // Fix: The original grammar doesn't allow double quotes for strings. 27 expression = expression.Replace('\"', '\''); 28 29 OriginalExpression = expression; 30 Options = options; 31 } 32 33 public Expression(LogicalExpression expression, EvaluateOptions options = EvaluateOptions.None) : this() 34 { 35 if (expression == null) 36 { 37 throw new ArgumentException("Expression can't be null", nameof(expression)); 38 } 39 40 ParsedExpression = expression; 41 Options = options; 42 } 43 44 public event EvaluateFunctionHandler EvaluateFunction; 45 public event EvaluateParameterHandler EvaluateParameter; 46 47 /// <summary> 48 /// Textual representation of the expression to evaluate. 49 /// </summary> 50 protected readonly string OriginalExpression; 51 52 protected Dictionary<string, IEnumerator> ParameterEnumerators; 53 54 private Dictionary<string, object> _parameters; 55 56 public EvaluateOptions Options { get; set; } 57 58 public string Error { get; private set; } 59 60 public LogicalExpression ParsedExpression { get; private set; } 61 62 public Dictionary<string, object> Parameters 63 { 64 get 65 { 66 return _parameters ?? (_parameters = new Dictionary<string, object>()); 67 } 68 set 69 { 70 _parameters = value; 71 } 72 } 73 74 public void UpdateUnityTimeParameters() 75 { 76 Parameters["dt"] = Parameters["DT"] = Time.deltaTime; 77 Parameters["second"] = Parameters["Second"] = 1 / Time.deltaTime; 78 } 79 80 /// <summary> 81 /// Pre-compiles the expression in order to check syntax errors. 82 /// If errors are detected, the Error property contains the message. 83 /// </summary> 84 /// <returns>True if the expression syntax is correct, otherwise false</returns> 85 public bool HasErrors() 86 { 87 try 88 { 89 if (ParsedExpression == null) 90 { 91 ParsedExpression = Compile(OriginalExpression, (Options & EvaluateOptions.NoCache) == EvaluateOptions.NoCache); 92 } 93 94 // In case HasErrors() is called multiple times for the same expression 95 return ParsedExpression != null && Error != null; 96 } 97 catch (Exception e) 98 { 99 Error = e.Message; 100 return true; 101 } 102 } 103 104 public object Evaluate(Flow flow) 105 { 106 if (HasErrors()) 107 { 108 throw new EvaluationException(Error); 109 } 110 111 if (ParsedExpression == null) 112 { 113 ParsedExpression = Compile(OriginalExpression, (Options & EvaluateOptions.NoCache) == EvaluateOptions.NoCache); 114 } 115 116 var visitor = new EvaluationVisitor(flow, Options); 117 visitor.EvaluateFunction += EvaluateFunction; 118 visitor.EvaluateParameter += EvaluateParameter; 119 visitor.Parameters = Parameters; 120 121 // If array evaluation, execute the same expression multiple times 122 if ((Options & EvaluateOptions.IterateParameters) == EvaluateOptions.IterateParameters) 123 { 124 var size = -1; 125 126 ParameterEnumerators = new Dictionary<string, IEnumerator>(); 127 128 foreach (var parameter in Parameters.Values) 129 { 130 if (parameter is IEnumerable enumerable) 131 { 132 var localsize = 0; 133 134 foreach (var o in enumerable) 135 { 136 localsize++; 137 } 138 139 if (size == -1) 140 { 141 size = localsize; 142 } 143 else if (localsize != size) 144 { 145 throw new EvaluationException("When IterateParameters option is used, IEnumerable parameters must have the same number of items."); 146 } 147 } 148 } 149 150 foreach (var key in Parameters.Keys) 151 { 152 var parameter = Parameters[key] as IEnumerable; 153 if (parameter != null) 154 { 155 ParameterEnumerators.Add(key, parameter.GetEnumerator()); 156 } 157 } 158 159 var results = new List<object>(); 160 161 for (var i = 0; i < size; i++) 162 { 163 foreach (var key in ParameterEnumerators.Keys) 164 { 165 var enumerator = ParameterEnumerators[key]; 166 enumerator.MoveNext(); 167 Parameters[key] = enumerator.Current; 168 } 169 170 ParsedExpression.Accept(visitor); 171 results.Add(visitor.Result); 172 } 173 174 return results; 175 } 176 else 177 { 178 ParsedExpression.Accept(visitor); 179 return visitor.Result; 180 } 181 } 182 183 public static LogicalExpression Compile(string expression, bool noCache) 184 { 185 LogicalExpression logicalExpression = null; 186 187 if (_cacheEnabled && !noCache) 188 { 189 try 190 { 191 Rwl.AcquireReaderLock(Timeout.Infinite); 192 193 if (_compiledExpressions.ContainsKey(expression)) 194 { 195 Trace.TraceInformation("Expression retrieved from cache: " + expression); 196 var wr = _compiledExpressions[expression]; 197 logicalExpression = wr.Target as LogicalExpression; 198 199 if (wr.IsAlive && logicalExpression != null) 200 { 201 return logicalExpression; 202 } 203 } 204 } 205 finally 206 { 207 Rwl.ReleaseReaderLock(); 208 } 209 } 210 211 if (logicalExpression == null) 212 { 213 var lexer = new NCalcLexer(new ANTLRStringStream(expression)); 214 var parser = new NCalcParser(new CommonTokenStream(lexer)); 215 216 logicalExpression = parser.ncalcExpression().value; 217 218 if (parser.Errors != null && parser.Errors.Count > 0) 219 { 220 throw new EvaluationException(String.Join(Environment.NewLine, parser.Errors.ToArray())); 221 } 222 223 if (_cacheEnabled && !noCache) 224 { 225 try 226 { 227 Rwl.AcquireWriterLock(Timeout.Infinite); 228 _compiledExpressions[expression] = new WeakReference(logicalExpression); 229 } 230 finally 231 { 232 Rwl.ReleaseWriterLock(); 233 } 234 235 CleanCache(); 236 237 Trace.TraceInformation("Expression added to cache: " + expression); 238 } 239 } 240 241 return logicalExpression; 242 } 243 244 #region Cache management 245 246 private static bool _cacheEnabled = true; 247 private static Dictionary<string, WeakReference> _compiledExpressions = new Dictionary<string, WeakReference>(); 248 private static readonly ReaderWriterLock Rwl = new ReaderWriterLock(); 249 250 public static bool CacheEnabled 251 { 252 get 253 { 254 return _cacheEnabled; 255 } 256 set 257 { 258 _cacheEnabled = value; 259 260 if (!CacheEnabled) 261 { 262 // Clears cache 263 _compiledExpressions = new Dictionary<string, WeakReference>(); 264 } 265 } 266 } 267 268 /// <summary> 269 /// Removes unused entries from cached compiled expression. 270 /// </summary> 271 private static void CleanCache() 272 { 273 var keysToRemove = new List<string>(); 274 275 try 276 { 277 Rwl.AcquireWriterLock(Timeout.Infinite); 278 279 foreach (var de in _compiledExpressions) 280 { 281 if (!de.Value.IsAlive) 282 { 283 keysToRemove.Add(de.Key); 284 } 285 } 286 287 foreach (var key in keysToRemove) 288 { 289 _compiledExpressions.Remove(key); 290 Trace.TraceInformation("Cache entry released: " + key); 291 } 292 } 293 finally 294 { 295 Rwl.ReleaseReaderLock(); 296 } 297 } 298 299 #endregion 300 } 301}