using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Text; using LoreSoft.MathExpressions.Properties; using System.Globalization; namespace LoreSoft.MathExpressions { /// /// Evaluate math expressions /// /// Using the MathEvaluator to calculate a math expression. /// /// MathEvaluator eval = new MathEvaluator(); /// //basic math /// double result = eval.Evaluate("(2 + 1) * (1 + 2)"); /// //calling a function /// result = eval.Evaluate("sqrt(4)"); /// //evaluate trigonometric /// result = eval.Evaluate("cos(pi * 45 / 180.0)"); /// //convert inches to feet /// result = eval.Evaluate("12 [in->ft]"); /// //use variable /// result = eval.Evaluate("answer * 10"); /// /// public class MathEvaluator : IDisposable { /// The name of the answer variable. /// public const string AnswerVariable = "answer"; //instance scope to optimize reuse private Stack _symbolStack; private Queue _expressionQueue; private Dictionary _expressionCache; private StringBuilder _buffer; private Stack _calculationStack; private Stack _parameters; private List _innerFunctions; private StringReader _expressionReader; /// /// Initializes a new instance of the class. /// public MathEvaluator() { _variables = new VariableDictionary(this); _innerFunctions = new List(FunctionExpression.GetFunctionNames()); _functions = new ReadOnlyCollection(_innerFunctions); _expressionCache = new Dictionary(StringComparer.OrdinalIgnoreCase); _symbolStack = new Stack(); _expressionQueue = new Queue(); _buffer = new StringBuilder(); _calculationStack = new Stack(); _parameters = new Stack(2); } private VariableDictionary _variables; /// /// Gets the variables collections. /// /// The variables for . public VariableDictionary Variables { get { return _variables; } } private ReadOnlyCollection _functions; /// Gets the functions available to . /// The functions for . /// public ReadOnlyCollection Functions { get { return _functions; } } /// Gets the answer from the last evaluation. /// The answer variable value. /// public double Answer { get { return _variables[AnswerVariable]; } } /// Evaluates the specified expression. /// The expression to evaluate. /// The result of the evaluated expression. /// When expression is null or empty. /// When there is an error parsing the expression. public double Evaluate(string expression) { if (string.IsNullOrEmpty(expression)) throw new ArgumentNullException("expression"); _expressionReader = new StringReader(expression); _symbolStack.Clear(); _expressionQueue.Clear(); ParseExpressionToQueue(); double result = CalculateFromQueue(); _variables[AnswerVariable] = result; return result; } /// Registers a function for the . /// Name of the function. /// An instance of for the function. /// When functionName or expression are null. /// When IExpression.Evaluate property is null or the functionName is already registered. /// /// public void RegisterFunction(string functionName, IExpression expression) { if (string.IsNullOrEmpty(functionName)) throw new ArgumentNullException("functionName"); if (expression == null) throw new ArgumentNullException("expression"); if (expression.Evaluate == null) throw new ArgumentException(Resources.EvaluatePropertyCanNotBeNull, "expression"); if (_innerFunctions.BinarySearch(functionName) >= 0) throw new ArgumentException( string.Format(CultureInfo.CurrentCulture, Resources.FunctionNameRegistered, functionName), "functionName"); _innerFunctions.Add(functionName); _innerFunctions.Sort(); _expressionCache.Add(functionName, expression); } /// Determines whether the specified name is a function. /// The name of the function. /// true if the specified name is function; otherwise, false. internal bool IsFunction(string name) { return (_innerFunctions.BinarySearch(name, StringComparer.OrdinalIgnoreCase) >= 0); } private void ParseExpressionToQueue() { char l = '\0'; char c = '\0'; do { // last non white space char if (!char.IsWhiteSpace(c)) l = c; c = (char)_expressionReader.Read(); if (char.IsWhiteSpace(c)) continue; if (TryNumber(c, l)) continue; if (TryString(c)) continue; if (TryStartGroup(c)) continue; if (TryOperator(c)) continue; if (TryEndGroup(c)) continue; if (TryConvert(c)) continue; throw new ParseException(Resources.InvalidCharacterEncountered + c); } while (_expressionReader.Peek() != -1); ProcessSymbolStack(); } private bool TryConvert(char c) { if (c != '[') return false; _buffer.Length = 0; _buffer.Append(c); char p = (char)_expressionReader.Peek(); while (char.IsLetter(p) || char.IsWhiteSpace(p) || p == '-' || p == '>' || p == ']') { if (!char.IsWhiteSpace(p)) _buffer.Append((char)_expressionReader.Read()); else _expressionReader.Read(); if (p == ']') break; p = (char)_expressionReader.Peek(); } if (ConvertExpression.IsConvertExpression(_buffer.ToString())) { IExpression e = GetExpressionFromSymbol(_buffer.ToString()); _expressionQueue.Enqueue(e); return true; } throw new ParseException(Resources.InvalidConvertionExpression + _buffer); } private bool TryString(char c) { if (!char.IsLetter(c)) return false; _buffer.Length = 0; _buffer.Append(c); char p = (char)_expressionReader.Peek(); while (char.IsLetter(p)) { _buffer.Append((char)_expressionReader.Read()); p = (char)_expressionReader.Peek(); } if (_variables.ContainsKey(_buffer.ToString())) { double value = _variables[_buffer.ToString()]; NumberExpression expression = new NumberExpression(value); _expressionQueue.Enqueue(expression); return true; } if (IsFunction(_buffer.ToString())) { _symbolStack.Push(_buffer.ToString()); return true; } throw new ParseException(Resources.InvalidVariableEncountered + _buffer); } private bool TryStartGroup(char c) { if (c != '(') return false; _symbolStack.Push(c.ToString()); return true; } private bool TryEndGroup(char c) { if (c != ')') return false; bool hasStart = false; while (_symbolStack.Count > 0) { string p = _symbolStack.Pop(); if (p == "(") { hasStart = true; if (_symbolStack.Count == 0) break; string n = _symbolStack.Peek(); if (FunctionExpression.IsFunction(n)) { p = _symbolStack.Pop(); IExpression f = GetExpressionFromSymbol(p); _expressionQueue.Enqueue(f); } break; } IExpression e = GetExpressionFromSymbol(p); _expressionQueue.Enqueue(e); } if (!hasStart) throw new ParseException(Resources.UnbalancedParentheses); return true; } private bool TryOperator(char c) { if (!OperatorExpression.IsSymbol(c)) return false; bool repeat; string s = c.ToString(); do { string p = _symbolStack.Count == 0 ? string.Empty : _symbolStack.Peek(); repeat = false; if (_symbolStack.Count == 0) _symbolStack.Push(s); else if (p == "(") _symbolStack.Push(s); else if (Precedence(s) > Precedence(p)) _symbolStack.Push(s); else { IExpression e = GetExpressionFromSymbol(_symbolStack.Pop()); _expressionQueue.Enqueue(e); repeat = true; } } while (repeat); return true; } private bool TryNumber(char c, char l) { bool isNumber = NumberExpression.IsNumber(c); // only negative when last char is group start or symbol bool isNegative = NumberExpression.IsNegativeSign(c) && (l == '\0' || l == '(' || OperatorExpression.IsSymbol(l)); if (!isNumber && !isNegative) return false; _buffer.Length = 0; _buffer.Append(c); char p = (char)_expressionReader.Peek(); while (NumberExpression.IsNumber(p)) { _buffer.Append((char)_expressionReader.Read()); p = (char)_expressionReader.Peek(); } double value; if (!(double.TryParse(_buffer.ToString(), out value))) throw new ParseException(Resources.InvalidNumberFormat + _buffer); NumberExpression expression = new NumberExpression(value); _expressionQueue.Enqueue(expression); return true; } private void ProcessSymbolStack() { while (_symbolStack.Count > 0) { string p = _symbolStack.Pop(); if (p.Length == 1 && p == "(") throw new ParseException(Resources.UnbalancedParentheses); IExpression e = GetExpressionFromSymbol(p); _expressionQueue.Enqueue(e); } } private IExpression GetExpressionFromSymbol(string p) { IExpression e; if (_expressionCache.ContainsKey(p)) e = _expressionCache[p]; else if (OperatorExpression.IsSymbol(p)) { e = new OperatorExpression(p); _expressionCache.Add(p, e); } else if (FunctionExpression.IsFunction(p)) { e = new FunctionExpression(p, false); _expressionCache.Add(p, e); } else if (ConvertExpression.IsConvertExpression(p)) { e = new ConvertExpression(p); _expressionCache.Add(p, e); } else throw new ParseException(Resources.InvalidSymbolOnStack + p); return e; } private static int Precedence(string c) { if (c.Length == 1 && (c[0] == '*' || c[0] == '/' || c[0] == '%')) return 2; return 1; } private double CalculateFromQueue() { double result; _calculationStack.Clear(); foreach (IExpression expression in _expressionQueue) { if (_calculationStack.Count < expression.ArgumentCount) throw new ParseException(Resources.NotEnoughNumbers + expression); _parameters.Clear(); for (int i = 0; i < expression.ArgumentCount; i++) _parameters.Push(_calculationStack.Pop()); _calculationStack.Push(expression.Evaluate.Invoke(_parameters.ToArray())); } result = _calculationStack.Pop(); return result; } #region IDisposable Members /// /// Releases unmanaged and - optionally - managed resources /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Releases unmanaged and managed resources /// /// /// true to release both managed and unmanaged resources; /// false to release only unmanaged resources. /// protected virtual void Dispose(bool disposing) { if (disposing) { if (_expressionReader != null) { _expressionReader.Dispose(); _expressionReader = null; } } } #endregion } }