using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Text.RegularExpressions; namespace FMSAdmin.Helpers.Formula { public class MathCalculator { public enum Parameter { A = 0, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z } public enum OperatorType { Plus, Minus, Multiplication, Division, OpenBrace } public delegate decimal CalculateFunctionDelegate(decimal inputValue); public Dictionary Parameters = new Dictionary(); public Dictionary Functions = new Dictionary(); private List OperationOrder = new List(); private static Regex doubleRegex = new Regex(@"^-?\d+(?:\.\d+)?", RegexOptions.Compiled); private static Regex functionRegex = new Regex(@"^\$(.+?)\((.)\)\$", RegexOptions.Compiled); private String Fomula { get; set; } Stack stack = new Stack(); List CompliedFormula = new List(); public void TracePostFormula() { foreach (var item in this.CompliedFormula) { } } public void SetParameter(MathParameterValue[] parameters) { foreach (var p in parameters) { this.Parameters.Add(p.Variable, p); } MapParameters(); } public void SetFunction(string functionName, CalculateFunctionDelegate callback) { if (this.Functions.ContainsKey(functionName)) { this.Functions[functionName] = callback; return; } this.Functions.Add(functionName, callback); MapFunctions(); } public List GetFunctions() { var list = new List(); foreach (var item in this.CompliedFormula) { switch (item.Type) { case FormulaItem.ItemType.Function: list.Add((FunctionItem)item); break; } } return list; } private void MapFunctions() { foreach (var item in this.CompliedFormula) { switch (item.Type) { case FormulaItem.ItemType.Function: var f = item as FunctionItem; f.Function.FunctionDelegate = this.Functions[f.Function.FunctionName]; break; } } } private void MapParameters() { foreach (var item in this.CompliedFormula) { switch (item.Type) { case FormulaItem.ItemType.OperandField: var o = item as OperandFieldItem; o.ParameterValue = this.Parameters[o.ParameterValue.Variable]; break; case FormulaItem.ItemType.Function: var f = item as FunctionItem; switch (f.Value.Type) { case FormulaItem.ItemType.OperandField: var of = (OperandFieldItem)f.Value; of.ParameterValue = this.Parameters[of.ParameterValue.Variable]; break; } break; } } } public decimal? Calculate() { Stack stack = new Stack(); foreach (var item in this.CompliedFormula) { switch (item.Type) { case FormulaItem.ItemType.Constant: case FormulaItem.ItemType.OperandField: case FormulaItem.ItemType.Function: stack.Push(item); break; case FormulaItem.ItemType.Operator: var ci = this.CalculateOperator(stack.Pop(), stack.Pop(), ((OperatorItem)item).Operator); if (ci == null) { return null; } stack.Push(ci); break; } } return stack.Pop().GetValue(); } private ConstantItem CalculateOperator(FormulaItem item1, FormulaItem item2, OperatorType op) { decimal result = 0; switch (op) { case OperatorType.Plus: result = item2.GetValue() + item1.GetValue(); break; case OperatorType.Minus: result = item2.GetValue() - item1.GetValue(); break; case OperatorType.Multiplication: result = item2.GetValue() * item1.GetValue(); break; case OperatorType.Division: if (item1.GetValue() == 0) return null; result = item2.GetValue() / item1.GetValue(); break; } return new ConstantItem { Value = result }; } public bool Compile(string formula) { this.CompliedFormula.Clear(); formula = formula.Replace(" ", ""); Stack stack = new Stack(); char[] braces = new char[] { '(', ')' }; string operators = "+-*/"; var array = formula.ToCharArray(); int runcount = 0; for (var i = 0; i < array.Length;) { if (++runcount > 1000) break; var item = array[i]; switch (item) { case '(': stack.Push(new OperatorItem { Operator = OperatorType.OpenBrace }); i++; break; case ')': while (stack.Any()) { var stackItem = stack.Pop(); if (stackItem.Operator == OperatorType.OpenBrace) { break; } CompliedFormula.Add(stackItem); } i++; break; case '$': Match funcMatch = functionRegex.Match(formula.Substring(i)); if (funcMatch.Success == false || funcMatch.Groups.Count != 3) { CompliedFormula.Clear(); return false; } var f = new FunctionItem { Function = new MathFunction { FunctionName = funcMatch.Groups[1].Value } }; string value = funcMatch.Groups[2].Value; if ('A' <= value[0] && value[0] <= 'Z') { if (value.Length > 1) { return false; } f.Value = new OperandFieldItem { ParameterValue = new MathParameterValue { Variable = (Parameter)(value[0] - 'A') } }; } else { decimal outputValue; if (decimal.TryParse(value, out outputValue) == false) { return false; } f.Value = new ConstantItem { Value = outputValue }; } CompliedFormula.Add(f); i += funcMatch.Length; break; default: int operatorIndex = operators.IndexOf(item); bool isProcessed = false; if (operatorIndex == (int)OperatorType.Minus) { if (i == 0 || array[i - 1] == '(' || operators.IndexOf(array[i - 1]) >= 0) { isProcessed = true; } } if (isProcessed == false) { if (operatorIndex >= 0) { //if( operatorIndex < (int)OperatorType.Multiplication ) { while (stack.Any()) { var stackItem = stack.Peek(); if (stackItem.Operator == OperatorType.OpenBrace) break; if ((int)stackItem.Operator < operatorIndex) break; stack.Pop(); CompliedFormula.Add(stackItem); } } stack.Push(new OperatorItem { Operator = (OperatorType)operatorIndex }); i++; break; } else if ('A' <= item && item <= 'Z') { //Trace.WriteLine( item ); CompliedFormula.Add(new OperandFieldItem { ParameterValue = new MathParameterValue { Variable = (Parameter)(item - 'A') } }); i++; break; } } Match match = doubleRegex.Match(formula.Substring(i)); if (match.Success == false) { throw new Exception("Error: Check Syntax : " + item); } decimal parseValue = decimal.Parse(match.Value); //Trace.WriteLine( value ); CompliedFormula.Add(new ConstantItem { Value = parseValue }); i += match.Value.Length; break; } } while (stack.Any()) { var operatorItem = stack.Pop(); //Trace.WriteLine( operatorItem.Operator ); CompliedFormula.Add(operatorItem); } return true; } public bool ExtractDouble(string inputValue, out decimal output) { return decimal.TryParse(inputValue, out output); } public abstract class FormulaItem { public enum ItemType { Operator, Constant, OperandField, Function } public ItemType Type { get; set; } public abstract decimal GetValue(); } public class OperatorItem : FormulaItem { public OperatorType Operator { get; set; } public OperatorItem() { this.Type = FormulaItem.ItemType.Operator; } public override string ToString() { switch (this.Operator) { case OperatorType.Plus: return "+"; case OperatorType.Minus: return "-"; case OperatorType.Multiplication: return "*"; case OperatorType.Division: return "/"; } return "Unknown"; } public override decimal GetValue() { throw new Exception("OperatorItem.GetValue() Called!"); //return 0; } } public class ConstantItem : FormulaItem { public decimal Value { get; set; } public ConstantItem() { this.Type = FormulaItem.ItemType.Constant; } public override string ToString() { return this.Value.ToString(); } public override decimal GetValue() { return this.Value; } } public class OperandFieldItem : FormulaItem { public MathParameterValue ParameterValue { get; set; } public OperandFieldItem() { this.Type = FormulaItem.ItemType.OperandField; } public override string ToString() { return this.ParameterValue.Variable.ToString(); } public override decimal GetValue() { return this.ParameterValue.Value; } } public class FunctionItem : FormulaItem { public MathFunction Function { get; set; } public FormulaItem Value { get; set; } public FunctionItem() { this.Type = FormulaItem.ItemType.Function; } public override string ToString() { return string.Format("Function({0})", this.Value.GetValue()); } public override decimal GetValue() { return this.Function.FunctionDelegate(this.Value.GetValue()); } } } public class MathFunction { public String FunctionName { get; set; } public MathCalculator.CalculateFunctionDelegate FunctionDelegate { get; set; } } public class MathParameterValue { public MathCalculator.Parameter Variable { get; set; } public decimal Value { get; set; } } }