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}