PageRenderTime 36ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/Evaluant.Calculator/Expression.cs

http://ncalc.codeplex.com
C# | 287 lines | 223 code | 50 blank | 14 comment | 39 complexity | 29dbec67767deefbfeaab37ebd025a0c MD5 | raw file
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using NCalc.Domain;
  5. using Antlr.Runtime;
  6. using System.Diagnostics;
  7. using System.Threading;
  8. namespace NCalc
  9. {
  10. public class Expression
  11. {
  12. public EvaluateOptions Options { get; set; }
  13. /// <summary>
  14. /// Textual representation of the expression to evaluate.
  15. /// </summary>
  16. protected string OriginalExpression;
  17. public Expression(string expression) : this(expression, EvaluateOptions.None)
  18. {
  19. }
  20. public Expression(string expression, EvaluateOptions options)
  21. {
  22. if (String.IsNullOrEmpty(expression))
  23. throw new
  24. ArgumentException("Expression can't be empty", "expression");
  25. OriginalExpression = expression;
  26. Options = options;
  27. }
  28. public Expression(LogicalExpression expression) : this(expression, EvaluateOptions.None)
  29. {
  30. }
  31. public Expression(LogicalExpression expression, EvaluateOptions options)
  32. {
  33. if (expression == null)
  34. throw new
  35. ArgumentException("Expression can't be null", "expression");
  36. ParsedExpression = expression;
  37. Options = options;
  38. }
  39. #region Cache management
  40. private static bool _cacheEnabled = true;
  41. private static Dictionary<string, WeakReference> _compiledExpressions = new Dictionary<string, WeakReference>();
  42. private static readonly ReaderWriterLock Rwl = new ReaderWriterLock();
  43. public static bool CacheEnabled
  44. {
  45. get { return _cacheEnabled; }
  46. set
  47. {
  48. _cacheEnabled = value;
  49. if (!CacheEnabled)
  50. {
  51. // Clears cache
  52. _compiledExpressions = new Dictionary<string, WeakReference>();
  53. }
  54. }
  55. }
  56. /// <summary>
  57. /// Removed unused entries from cached compiled expression
  58. /// </summary>
  59. private static void CleanCache()
  60. {
  61. var keysToRemove = new List<string>();
  62. try
  63. {
  64. Rwl.AcquireWriterLock(Timeout.Infinite);
  65. foreach (var de in _compiledExpressions)
  66. {
  67. if (!de.Value.IsAlive)
  68. {
  69. keysToRemove.Add(de.Key);
  70. }
  71. }
  72. foreach (string key in keysToRemove)
  73. {
  74. _compiledExpressions.Remove(key);
  75. Trace.TraceInformation("Cache entry released: " + key);
  76. }
  77. }
  78. finally
  79. {
  80. Rwl.ReleaseReaderLock();
  81. }
  82. }
  83. #endregion
  84. public static LogicalExpression Compile(string expression, bool nocache)
  85. {
  86. LogicalExpression logicalExpression = null;
  87. if (_cacheEnabled && !nocache)
  88. {
  89. try
  90. {
  91. Rwl.AcquireReaderLock(Timeout.Infinite);
  92. if (_compiledExpressions.ContainsKey(expression))
  93. {
  94. Trace.TraceInformation("Expression retrieved from cache: " + expression);
  95. var wr = _compiledExpressions[expression];
  96. logicalExpression = wr.Target as LogicalExpression;
  97. if (wr.IsAlive && logicalExpression != null)
  98. {
  99. return logicalExpression;
  100. }
  101. }
  102. }
  103. finally
  104. {
  105. Rwl.ReleaseReaderLock();
  106. }
  107. }
  108. if (logicalExpression == null)
  109. {
  110. var lexer = new NCalcLexer(new ANTLRStringStream(expression));
  111. var parser = new NCalcParser(new CommonTokenStream(lexer));
  112. logicalExpression = parser.ncalcExpression().value;
  113. if (parser.Errors != null && parser.Errors.Count > 0)
  114. {
  115. throw new EvaluationException(String.Join(Environment.NewLine, parser.Errors.ToArray()));
  116. }
  117. if (_cacheEnabled && !nocache)
  118. {
  119. try
  120. {
  121. Rwl.AcquireWriterLock(Timeout.Infinite);
  122. _compiledExpressions[expression] = new WeakReference(logicalExpression);
  123. }
  124. finally
  125. {
  126. Rwl.ReleaseWriterLock();
  127. }
  128. CleanCache();
  129. Trace.TraceInformation("Expression added to cache: " + expression);
  130. }
  131. }
  132. return logicalExpression;
  133. }
  134. /// <summary>
  135. /// Pre-compiles the expression in order to check syntax errors.
  136. /// If errors are detected, the Error property contains the message.
  137. /// </summary>
  138. /// <returns>True if the expression syntax is correct, otherwiser False</returns>
  139. public bool HasErrors()
  140. {
  141. try
  142. {
  143. if (ParsedExpression == null)
  144. {
  145. ParsedExpression = Compile(OriginalExpression, (Options & EvaluateOptions.NoCache) == EvaluateOptions.NoCache);
  146. }
  147. // In case HasErrors() is called multiple times for the same expression
  148. return ParsedExpression != null && Error != null;
  149. }
  150. catch(Exception e)
  151. {
  152. Error = e.Message;
  153. return true;
  154. }
  155. }
  156. public string Error { get; private set; }
  157. public LogicalExpression ParsedExpression { get; private set; }
  158. protected Dictionary<string, IEnumerator> ParameterEnumerators;
  159. protected Dictionary<string, object> ParametersBackup;
  160. public object Evaluate()
  161. {
  162. if (HasErrors())
  163. {
  164. throw new EvaluationException(Error);
  165. }
  166. if (ParsedExpression == null)
  167. {
  168. ParsedExpression = Compile(OriginalExpression, (Options & EvaluateOptions.NoCache) == EvaluateOptions.NoCache);
  169. }
  170. var visitor = new EvaluationVisitor(Options);
  171. visitor.EvaluateFunction += EvaluateFunction;
  172. visitor.EvaluateParameter += EvaluateParameter;
  173. visitor.Parameters = Parameters;
  174. // if array evaluation, execute the same expression multiple times
  175. if ((Options & EvaluateOptions.IterateParameters) == EvaluateOptions.IterateParameters)
  176. {
  177. int size = -1;
  178. ParametersBackup = new Dictionary<string, object>();
  179. foreach (string key in Parameters.Keys)
  180. {
  181. ParametersBackup.Add(key, Parameters[key]);
  182. }
  183. ParameterEnumerators = new Dictionary<string, IEnumerator>();
  184. foreach (object parameter in Parameters.Values)
  185. {
  186. if (parameter is IEnumerable)
  187. {
  188. int localsize = 0;
  189. foreach (object o in (IEnumerable)parameter)
  190. {
  191. localsize++;
  192. }
  193. if (size == -1)
  194. {
  195. size = localsize;
  196. }
  197. else if (localsize != size)
  198. {
  199. throw new EvaluationException("When IterateParameters option is used, IEnumerable parameters must have the same number of items");
  200. }
  201. }
  202. }
  203. foreach (string key in Parameters.Keys)
  204. {
  205. var parameter = Parameters[key] as IEnumerable;
  206. if (parameter != null)
  207. {
  208. ParameterEnumerators.Add(key, parameter.GetEnumerator());
  209. }
  210. }
  211. var results = new List<object>();
  212. for (int i = 0; i < size; i++)
  213. {
  214. foreach (string key in ParameterEnumerators.Keys)
  215. {
  216. IEnumerator enumerator = ParameterEnumerators[key];
  217. enumerator.MoveNext();
  218. Parameters[key] = enumerator.Current;
  219. }
  220. ParsedExpression.Accept(visitor);
  221. results.Add(visitor.Result);
  222. }
  223. return results;
  224. }
  225. ParsedExpression.Accept(visitor);
  226. return visitor.Result;
  227. }
  228. public event EvaluateFunctionHandler EvaluateFunction;
  229. public event EvaluateParameterHandler EvaluateParameter;
  230. private Dictionary<string, object> _parameters;
  231. public Dictionary<string, object> Parameters
  232. {
  233. get { return _parameters ?? (_parameters = new Dictionary<string, object>()); }
  234. set { _parameters = value; }
  235. }
  236. }
  237. }