using System; using System.Collections; using System.Collections.Generic; using System.Text.RegularExpressions; namespace DevComponents.DotNetBar.SuperGrid { /// /// Expression evaluator /// public class EEval { #region Private data private int _TCount; private int _PCount; private ETokens[] _PTokens = new ETokens[10]; private List _PfTokens = new List(); private GridCell _Cell; private GridPanel _GridPanel; private List _UsedCells; private string _Source; private List _StringPool = new List(); private object _Tag; #endregion /// /// Expression evaluator constructor /// ///Associated GridPanel ///'Excel-like' expression (eg. =d4+d5) public EEval(GridPanel gridPanel, string source) : this(null, source, new List()) { _GridPanel = gridPanel; } internal EEval(GridCell cell, string source) : this(cell, source, new List()) { } private EEval(GridCell cell, string source, List usedCells) { _Cell = cell; _UsedCells = usedCells; if (_Cell != null) _GridPanel = cell.GridPanel; Source = source; } #region Public properties #region Cell /// /// Gets the associated Grid Cell, if any /// public GridCell Cell { get { return (_Cell); } } #endregion #region GridPanel /// /// Gets the associated Grid Panel /// public GridPanel GridPanel { get { return (_GridPanel); } } #endregion #region Source /// /// Gets or sets the expression source /// public string Source { get { return (_Source); } set { _Source = value; Tokenize(value); } } #endregion #region Tag /// /// Gets or sets user-defined data associated with the object /// public object Tag { get { return (_Tag); } set { _Tag = value; } } #endregion #endregion #region Tokenize code private void Tokenize(string source) { const string sref = @"([\.]|[a-zA-Z_]+|""[^""]+"")\s*([\.]|\d+)"; Regex p = new Regex( "(" + sref + "\\s*:\\s*" + sref + ")|(" + sref + ")|" + "([a-zA-Z0-9.]+)|" + "\"([^\"]+)\"|" + "([()+\\-*/%,|&\\^])|(<<)|(>>)"); MatchCollection mc = p.Matches(source); Expr(mc); ETokens t = GetToken(mc); if (t != ETokens.BadToken) throw new Exception("Expression error."); } #region Expr /// /// Main expression entry routine /// private void Expr(MatchCollection mc) { Term1(mc); ETokens t = GetToken(mc); while (t == ETokens.BitwiseOr) { Term1(mc); _PfTokens.Add(t); t = GetToken(mc); } PutToken(t); } #endregion #region Term1 /// /// Handles the ^ operator /// private void Term1(MatchCollection mc) { Term2(mc); ETokens t = GetToken(mc); while (t == ETokens.BitwiseXor) { Term2(mc); _PfTokens.Add(t); t = GetToken(mc); } PutToken(t); } #endregion #region Term2 /// /// Handles the Ampersand operator /// private void Term2(MatchCollection mc) { Term3(mc); ETokens t = GetToken(mc); while (t == ETokens.BitwiseAnd) { Term3(mc); _PfTokens.Add(t); t = GetToken(mc); } PutToken(t); } #endregion #region Term3 /// /// Handles the shift left and right operators /// private void Term3(MatchCollection mc) { Term4(mc); ETokens t = GetToken(mc); while (t == ETokens.ShiftLeft || t == ETokens.ShiftRight) { Term4(mc); _PfTokens.Add(t); t = GetToken(mc); } PutToken(t); } #endregion #region Term4 /// /// Handles %, *, and / operators /// private void Term4(MatchCollection mc) { Term5(mc); ETokens t = GetToken(mc); while (t == ETokens.Add || t == ETokens.Subtract) { Term5(mc); _PfTokens.Add(t); t = GetToken(mc); } PutToken(t); } #endregion #region Term5 /// /// Handles %, *, and / operators /// private void Term5(MatchCollection mc) { Factor(mc); ETokens t = GetToken(mc); while (t == ETokens.Mod || t == ETokens.Multiply || t == ETokens.Divide) { Factor(mc); _PfTokens.Add(t); t = GetToken(mc); } PutToken(t); } #endregion #region Factor /// /// Handles factor processing /// private void Factor(MatchCollection mc) { ETokens t = GetToken(mc); int n = 0; // Unary operators while (t == ETokens.Add || t == ETokens.Subtract) { if (t == ETokens.Subtract) n++; t = GetToken(mc); } // Operand processing if (t < ETokens.Operator) { if (Function(mc, t) == false) _PfTokens.Add(t); } else if (t == ETokens.LParen) { Expr(mc); t = GetToken(mc); if (t != ETokens.RParen) throw new Exception("Expecting right parenthesis."); } else { PutToken(t); } if (n % 2 == 1) { if (_PfTokens.Count == 0) throw new Exception("Invalid expression."); _PfTokens.Add(ETokens.Negate); } } #endregion #region Function /// /// Handles function parsing /// /// /// /// private bool Function(MatchCollection mc, ETokens e) { string s = _StringPool[(int)e].ToUpper(); ETokens t = GetFunction(mc, s); if (t != ETokens.BadToken) { _PfTokens.Add(t); t = GetToken(mc); if (t != ETokens.LParen) throw new Exception("Expecting left parenthesis."); ParameterList(mc, e); t = GetToken(mc); if (t != ETokens.RParen) throw new Exception("Expecting right parenthesis."); return (true); } return (false); } #region GetFunction /// /// Determines whether the parsed string /// is a one of our function keywords /// /// /// /// private ETokens GetFunction(MatchCollection mc, string s) { ETokens t = GetToken(mc); if (t == ETokens.LParen) { PutToken(t); switch (s) { case "AVG": return (ETokens.Avg); case "CEILING": return (ETokens.Ceiling); case "FLOOR": return (ETokens.Floor); case "MIN": return (ETokens.Min); case "MAX": return (ETokens.Max); case "ROUND": return (ETokens.Round); case "SUM": return (ETokens.Sum); default: return (ETokens.User); } } PutToken(t); return (ETokens.BadToken); } #endregion #endregion #region ParameterList /// /// Handles function parameters /// private void ParameterList(MatchCollection mc, ETokens e) { int n = 0; if (_PfTokens[_PfTokens.Count - 1] == ETokens.User) { _PfTokens.Add(e); n++; } ETokens t = GetToken(mc); while (t != ETokens.RParen && t != ETokens.BadToken) { PutToken(t); int count = _PfTokens.Count; Expr(mc); if (_PfTokens.Count > count) n++; t = GetToken(mc); if (t != ETokens.Comma) break; t = GetToken(mc); } PutToken(t); _PfTokens.Add(ETokens.Function); _PfTokens.Add((ETokens) n); } #endregion #region GetToken /// /// Gets the next parsed token /// /// private ETokens GetToken(MatchCollection mc) { ETokens t = ETokens.BadToken; if (_PCount > 0) { t = _PTokens[--_PCount]; } else { if (_TCount < mc.Count) { string s = mc[_TCount].Value; switch (s) { case "|": t = ETokens.BitwiseOr; break; case "^": t = ETokens.BitwiseXor; break; case "&": t = ETokens.BitwiseAnd; break; case "<<": t = ETokens.ShiftLeft; break; case ">>": t = ETokens.ShiftRight; break; case "+": t = ETokens.Add; break; case "-": t = ETokens.Subtract; break; case "*": t = ETokens.Multiply; break; case "/": t = ETokens.Divide; break; case "%": t = ETokens.Mod; break; case "(": t = ETokens.LParen; break; case ")": t = ETokens.RParen; break; case ",": t = ETokens.Comma; break; default: int index = _StringPool.IndexOf(s); if (index < 0) { _StringPool.Add(s); index = _StringPool.Count - 1; } t = (ETokens)(index); break; } _TCount++; } } return (t); } #endregion #region PutToken /// /// Saves the given token for future use /// /// private void PutToken(ETokens t) { _PTokens[_PCount++] = t; } #endregion #endregion #region Evaluate /// /// Evaluates the previously tokenized code /// /// public object Evaluate() { _UsedCells.Clear(); if (_Cell != null) _UsedCells.Add(_Cell); return (EvaluateEx()); } internal object EvaluateEx() { Stack myStack = new Stack(); for (int i = 0; i < _PfTokens.Count; i++) { if (_PfTokens[i] < ETokens.Operator) { myStack.Push(_StringPool[(int)_PfTokens[i]]); } else if (_PfTokens[i] > ETokens.Functions) { myStack.Push(_PfTokens[i]); } else { if (_PfTokens[i] == ETokens.Negate) { object value = ProcessValue(myStack.Pop()); if (value is double == false) throw new Exception("Invalid negation value"); myStack.Push(-(double)value); } else if (_PfTokens[i] == ETokens.Function) { if (++i == _PfTokens.Count) throw new Exception("Expecting function parameter count"); EvalFunction(myStack, (int)_PfTokens[i]); } else { object value1 = ProcessValue(myStack.Pop()); object value2 = ProcessValue(myStack.Pop()); if (value1 == null || value2 == null) { if (value1 == null && value2 == null) value1 = value2 = 0d; else if (value1 == null) { if (value2 is string) value1 = ""; else value1 = 0d; } else { if (value1 is string) value2 = ""; else value2 = 0d; } } if (value1 is string || value2 is string) OpStringValue(myStack, _PfTokens[i], value1.ToString(), value2.ToString()); else OpDoubleValue(myStack, _PfTokens[i], (double)value1, (double)value2); } } } if (myStack.Count > 0) return (ProcessValue(myStack.Pop())); return (0); } #region OpStringValue private void OpStringValue(Stack myStack, ETokens op, string s1, string s2) { switch (op) { case ETokens.Add: myStack.Push(s2 + s1); break; default: throw new Exception("Invalid string operation."); } } #endregion #region OpDoubleValue private void OpDoubleValue( Stack myStack, ETokens op, double d1, double d2) { switch (op) { case ETokens.BitwiseAnd: myStack.Push((double)((int)d2 & (int)d1)); break; case ETokens.BitwiseOr: myStack.Push((double)((int)d2 | (int)d1)); break; case ETokens.BitwiseXor: myStack.Push((double)((int)d2 ^ (int)d1)); break; case ETokens.Add: myStack.Push(d2 + d1); break; case ETokens.Subtract: myStack.Push(d2 - d1); break; case ETokens.Multiply: myStack.Push(d2 * d1); break; case ETokens.Divide: myStack.Push(d2 / d1); break; case ETokens.Mod: myStack.Push(d2 % d1); break; case ETokens.ShiftLeft: myStack.Push((double)((int)d2 << (int)d1)); break; case ETokens.ShiftRight: myStack.Push((double)((int)d2 >> (int)d1)); break; } } #endregion #region EvalFunction /// /// Evaluates the current function /// /// /// private void EvalFunction(Stack myStack, int count) { object[] args = new object[count]; for (int i = count - 1; i >= 0; i--) args[i] = myStack.Pop(); switch ((ETokens)myStack.Pop()) { case ETokens.Avg: myStack.Push(Avg(args)); break; case ETokens.Ceiling: myStack.Push(Ceiling(args)); break; case ETokens.Floor: myStack.Push(Floor(args)); break; case ETokens.Min: myStack.Push(Min(args)); break; case ETokens.Max: myStack.Push(Max(args)); break; case ETokens.Round: myStack.Push(Round(args)); break; case ETokens.Sum: myStack.Push(Sum(args)); break; case ETokens.User: myStack.Push(User(args)); break; } } #region Average /// /// Calculates the average of the given set of values /// /// /// private double Avg(IEnumerable args) { double d = 0; int count = 1; foreach (object o in args) { object value = ProcessValue(o, ref count); if (value is double) d += (double) value; else throw new Exception("Can't AVERAGE non-numeric data."); } return (d / count); } #endregion #region Ceiling /// /// Returns the smallest whole value /// greater than or equal to the given value. /// /// /// private double Ceiling(object[] args) { if (args.Length != 1) throw new Exception("Ceiling(): Invalid number of arguments."); object value = ProcessValue(args[0]); if (value is double == false) throw new Exception("Ceiling(): Invalid data type."); return (Math.Ceiling((double)value)); } #endregion #region Floor /// /// Returns the largest whole value /// less than or equal to the given value. /// /// /// private double Floor(object[] args) { if (args.Length != 1) throw new Exception("Floor(): Invalid number of arguments."); object value = ProcessValue(args[0]); if (value is double == false) throw new Exception("Floor(): Invalid data type."); return (Math.Floor((double)value)); } #endregion #region Minimum /// /// Calculates the minimum from the given set of values /// /// /// private double Min(ICollection args) { if (args.Count == 0) return (0); double d = double.PositiveInfinity; foreach (object o in args) { double n = ProcessMinMaxValue(o, true); if (n < d) d = n; } return (d); } #endregion #region Maximum /// /// Calculates the maximum from the given set of values /// /// /// private double Max(ICollection args) { if (args.Count == 0) return (0); double d = double.NegativeInfinity; foreach (object o in args) { double n = ProcessMinMaxValue(o, false); if (n > d) d = n; } return (d); } #endregion #region Round /// /// Returns the largest whole value /// less than or equal to the given value. /// /// /// private double Round(object[] args) { if (args.Length != 1 && args.Length != 2) throw new Exception("Round(): Invalid number of arguments."); object value = ProcessValue(args[0]); object decimals = args.Length == 2 ? ProcessValue(args[1]) : 0d; if (value is double == false) throw new Exception("Round(): Invalid value type."); if (decimals is double == false) throw new Exception("Round(): Invalid decimals type."); return (Math.Round((double)value, Convert.ToInt32(decimals))); } #endregion #region Sum /// /// Calculates the sum of the given set of values /// /// /// private double Sum(IEnumerable args) { double d = 0; int count = 0; foreach (object o in args) { object value = ProcessValue(o, ref count); if (value is double) d += (double)value; else throw new Exception("Can't SUM non-numeric data."); } return (d); } #endregion #region User /// /// Calculates the sum of the given set of values /// /// /// private object User(object[] args) { object d = null; int count = 0; object[] uargs = new object[args.Length]; for (int i = 0; i < args.Length; i++) uargs[i] = ProcessValue(args[i], ref count); _GridPanel.SuperGrid.DoCellUserFunctionEvent(_Cell, uargs, ref d); return (d); } #endregion #region ProcessValue /// /// Process the given object value /// /// /// private object ProcessValue(object o) { int count = 0; return (ProcessValue(o, ref count)); } private object ProcessValue(object o, ref int count) { count = 1; if (o is double) return (double)o; if (o is string) { string s = (string)o; if (s.Length > 0) { if (s[0] == '.' || s[0] == '"' || char.IsLetter(s[0]) == true) { object d = null; List cells = GetCellReferences(s); if (cells != null && cells.Count > 0) { foreach (GridCell cell in cells) { object n; if (ProcessCellValue(cell, out n) == true) { if (d == null) { d = n; } else { if (n is string || d is string) d = d + n.ToString(); else d = (double)d + (double)n; } count++; } } } else { return (s); } return (d); } return (GetValue(s)); } } return (0d); } #endregion #region ProcessMinMaxValue /// /// Processes the given MinMax object value /// /// /// /// private double ProcessMinMaxValue(object o, bool min) { if (o is double) return (double) o; if (o is string) { string s = (string)o; if (s.Length > 0) { if (s[0] == '.' || s[0] == '"' || char.IsLetter(s[0]) == true) { double d = 0; IEnumerable cells = GetCellReferences(s); bool valueSet = false; foreach (GridCell cell in cells) { object value; if (ProcessCellValue(cell, out value) == true) { if (value is double == false) throw new Exception("Cannot Min/Max non-numeric values."); double n = (double)value; if (valueSet == false || (min ? n < d : n > d)) { d = n; valueSet = true; } } } return (d); } return ((double)GetValue(s)); } } return (0); } #endregion #region ProcessCellValue /// /// Processes the given cell reference value /// /// /// /// private bool ProcessCellValue(GridCell cell, out object value) { value = cell.Value ?? ""; string s = value.ToString(); if (string.IsNullOrEmpty(s) == false) { if (s[0] == '=') { if (_UsedCells.Contains(cell) == true) throw new Exception("Recursive cell reference"); _UsedCells.Add(cell); EEval eval = new EEval(cell, s, _UsedCells); value = eval.EvaluateEx(); _UsedCells.Remove(cell); return (true); } value = GetValue(s); return (true); } return (false); } #endregion #endregion #region GetValue /// /// Gets the double value from the given string /// /// /// private object GetValue(string s) { double d; if (double.TryParse(s, out d) == true) return (d); return (s); } #endregion #endregion #region GetCellReferences internal List GetCellReferences() { return (GetCellReferences(_Source)); } private List GetCellReferences(string item) { const string sref1 = @"(?[\.]|[a-zA-Z_]+|""[^""]+"")\s*(?[\.]|\d+)"; const string sref2 = @"(?[\.]|[a-zA-Z_]+|""[^""]+"")\s*(?[\.]|\d+)"; Regex reg = new Regex( "(" + sref1 + ":" + sref2 + ")|" + "(" + sref1 + ")"); MatchCollection mc = reg.Matches(item); if (mc.Count > 0) { List cells = new List(); for (int i = 0; i < mc.Count; i++) AddCellRange(_GridPanel, cells, mc[i].Groups); return (cells); } return (null); } #region AddCellRange private void AddCellRange( GridPanel panel, List cells, GroupCollection groups) { int row1; int col1 = GetRowCol(panel, groups["col1"].Value, groups["row1"].Value, out row1); int row2 = row1; int col2 = col1; if (groups["col2"].Success == true) { col2 = GetRowCol(panel, groups["col2"].Value, groups["row2"].Value, out row2); } if (col1 >= 0 && col2 >= 0) ProcessRange(panel, cells, col1, row1, col2, row2); } #region GetRowCol private int GetRowCol(GridPanel panel, string scol, string srow, out int row) { scol = scol.Replace("\"", ""); if (_Cell != null) { if (srow.Equals(".") == true) row = _Cell.GridRow.RowIndex; else int.TryParse(srow, out row); if (scol.Equals(".") == true) return (_Cell.ColumnIndex); } else { int.TryParse(srow, out row); } int col; if (int.TryParse(scol, out col) == true) return (col); GridColumn column = panel.Columns[scol]; if (column != null) return (column.ColumnIndex); return (-1); } #endregion #region ProcessRange private void ProcessRange(GridPanel panel, ICollection cells, int col1, int row1, int col2, int row2) { GridContainer cont = _Cell.GridRow.Parent as GridContainer; if (cont != null) { for (int i = row1; i <= row2; i++) { for (int j = col1; j <= col2; j++) { GridCell cell = cont.GetCell(i, j); if (cell != null) cells.Add(cell); } } } } #endregion #endregion #endregion } #region enums #region ETokens /// /// ETokens /// internal enum ETokens { // Anything before this, is an operand Operator = 512, // Normal operators Negate, LParen, RParen, Multiply, Divide, Mod, Add, Subtract, ShiftLeft, ShiftRight, BitwiseAnd, BitwiseOr, BitwiseXor, Function, Comma, BadToken, // Anything after this is a function Functions, Avg, Ceiling, Floor, Max, Min, Round, Sum, User, }; #endregion #endregion }