1374 lines
		
	
	
		
			33 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			1374 lines
		
	
	
		
			33 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
using System;
 | 
						|
using System.Collections;
 | 
						|
using System.Collections.Generic;
 | 
						|
using System.Text.RegularExpressions;
 | 
						|
 | 
						|
namespace DevComponents.DotNetBar.SuperGrid
 | 
						|
{
 | 
						|
    /// <summary>
 | 
						|
    /// Expression evaluator
 | 
						|
    /// </summary>
 | 
						|
    public class EEval
 | 
						|
    {
 | 
						|
        #region Private data
 | 
						|
 | 
						|
        private int _TCount;
 | 
						|
        private int _PCount;
 | 
						|
        private ETokens[] _PTokens = new ETokens[10];
 | 
						|
        private List<ETokens> _PfTokens = new List<ETokens>();
 | 
						|
 | 
						|
        private GridCell _Cell;
 | 
						|
        private GridPanel _GridPanel;
 | 
						|
        private List<GridCell> _UsedCells;
 | 
						|
 | 
						|
        private string _Source;
 | 
						|
        private List<string> _StringPool = new List<string>();
 | 
						|
 | 
						|
        private object _Tag;
 | 
						|
 | 
						|
        #endregion
 | 
						|
 | 
						|
        ///<summary>
 | 
						|
        /// Expression evaluator constructor
 | 
						|
        ///</summary>
 | 
						|
        ///<param name="gridPanel">Associated GridPanel</param>
 | 
						|
        ///<param name="source">'Excel-like' expression (eg. =d4+d5)</param>
 | 
						|
        public EEval(GridPanel gridPanel, string source)
 | 
						|
            : this(null, source, new List<GridCell>())
 | 
						|
        {
 | 
						|
            _GridPanel = gridPanel;
 | 
						|
        }
 | 
						|
 | 
						|
        internal EEval(GridCell cell, string source)
 | 
						|
            : this(cell, source, new List<GridCell>())
 | 
						|
        {
 | 
						|
        }
 | 
						|
 | 
						|
        private EEval(GridCell cell, string source, List<GridCell> usedCells)
 | 
						|
        {
 | 
						|
            _Cell = cell;
 | 
						|
            _UsedCells = usedCells;
 | 
						|
 | 
						|
            if (_Cell != null)
 | 
						|
                _GridPanel = cell.GridPanel;
 | 
						|
 | 
						|
            Source = source;
 | 
						|
        }
 | 
						|
 | 
						|
        #region Public properties
 | 
						|
 | 
						|
        #region Cell
 | 
						|
 | 
						|
        ///<summary>
 | 
						|
        /// Gets the associated Grid Cell, if any
 | 
						|
        ///</summary>
 | 
						|
        public GridCell Cell
 | 
						|
        {
 | 
						|
            get { return (_Cell); }
 | 
						|
        }
 | 
						|
 | 
						|
        #endregion
 | 
						|
 | 
						|
        #region GridPanel
 | 
						|
 | 
						|
        ///<summary>
 | 
						|
        /// Gets the associated Grid Panel
 | 
						|
        ///</summary>
 | 
						|
        public GridPanel GridPanel
 | 
						|
        {
 | 
						|
            get { return (_GridPanel); }
 | 
						|
        }
 | 
						|
 | 
						|
        #endregion
 | 
						|
 | 
						|
        #region Source
 | 
						|
 | 
						|
        ///<summary>
 | 
						|
        /// Gets or sets the expression source
 | 
						|
        ///</summary>
 | 
						|
        public string Source
 | 
						|
        {
 | 
						|
            get { return (_Source); }
 | 
						|
 | 
						|
            set
 | 
						|
            {
 | 
						|
                _Source = value;
 | 
						|
 | 
						|
                Tokenize(value);
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        #endregion
 | 
						|
 | 
						|
        #region Tag
 | 
						|
 | 
						|
        ///<summary>
 | 
						|
        /// Gets or sets user-defined data associated with the object
 | 
						|
        ///</summary>
 | 
						|
        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
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Main expression entry routine
 | 
						|
        /// </summary>
 | 
						|
        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
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Handles the ^ operator
 | 
						|
        /// </summary>
 | 
						|
        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
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Handles the Ampersand operator
 | 
						|
        /// </summary>
 | 
						|
        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
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Handles the shift left and right operators
 | 
						|
        /// </summary>
 | 
						|
        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
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Handles %, *, and / operators
 | 
						|
        /// </summary>
 | 
						|
        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
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Handles %, *, and / operators
 | 
						|
        /// </summary>
 | 
						|
        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
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Handles factor processing
 | 
						|
        /// </summary>
 | 
						|
        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
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Handles function parsing
 | 
						|
        /// </summary>
 | 
						|
        /// <param name="mc"></param>
 | 
						|
        /// <param name="e"></param>
 | 
						|
        /// <returns></returns>
 | 
						|
        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
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Determines whether the parsed string
 | 
						|
        /// is a one of our function keywords
 | 
						|
        /// </summary>
 | 
						|
        /// <param name="mc"></param>
 | 
						|
        /// <param name="s"></param>
 | 
						|
        /// <returns></returns>
 | 
						|
        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
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Handles function parameters
 | 
						|
        /// </summary>
 | 
						|
        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
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Gets the next parsed token
 | 
						|
        /// </summary>
 | 
						|
        /// <returns></returns>
 | 
						|
        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
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Saves the given token for future use
 | 
						|
        /// </summary>
 | 
						|
        /// <param name="t"></param>
 | 
						|
        private void PutToken(ETokens t)
 | 
						|
        {
 | 
						|
            _PTokens[_PCount++] = t;
 | 
						|
        }
 | 
						|
 | 
						|
        #endregion
 | 
						|
 | 
						|
        #endregion
 | 
						|
 | 
						|
        #region Evaluate
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Evaluates the previously tokenized code
 | 
						|
        /// </summary>
 | 
						|
        /// <returns></returns>
 | 
						|
        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
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Evaluates the current function
 | 
						|
        /// </summary>
 | 
						|
        /// <param name="myStack"></param>
 | 
						|
        /// <param name="count"></param>
 | 
						|
        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
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Calculates the average of the given set of values
 | 
						|
        /// </summary>
 | 
						|
        /// <param name="args"></param>
 | 
						|
        /// <returns></returns>
 | 
						|
        private double Avg(IEnumerable<object> 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
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Returns the smallest whole value
 | 
						|
        /// greater than or equal to the given value.
 | 
						|
        /// </summary>
 | 
						|
        /// <param name="args"></param>
 | 
						|
        /// <returns></returns>
 | 
						|
        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
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Returns the largest whole value
 | 
						|
        /// less than or equal to the given value.
 | 
						|
        /// </summary>
 | 
						|
        /// <param name="args"></param>
 | 
						|
        /// <returns></returns>
 | 
						|
        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
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Calculates the minimum from the given set of values
 | 
						|
        /// </summary>
 | 
						|
        /// <param name="args"></param>
 | 
						|
        /// <returns></returns>
 | 
						|
        private double Min(ICollection<object> 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
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Calculates the maximum from the given set of values
 | 
						|
        /// </summary>
 | 
						|
        /// <param name="args"></param>
 | 
						|
        /// <returns></returns>
 | 
						|
        private double Max(ICollection<object> 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
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Returns the largest whole value
 | 
						|
        /// less than or equal to the given value.
 | 
						|
        /// </summary>
 | 
						|
        /// <param name="args"></param>
 | 
						|
        /// <returns></returns>
 | 
						|
        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
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Calculates the sum of the given set of values
 | 
						|
        /// </summary>
 | 
						|
        /// <param name="args"></param>
 | 
						|
        /// <returns></returns>
 | 
						|
        private double Sum(IEnumerable<object> 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
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Calculates the sum of the given set of values
 | 
						|
        /// </summary>
 | 
						|
        /// <param name="args"></param>
 | 
						|
        /// <returns></returns>
 | 
						|
        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
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Process the given object value
 | 
						|
        /// </summary>
 | 
						|
        /// <param name="o"></param>
 | 
						|
        /// <returns></returns>
 | 
						|
        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<GridCell> 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
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Processes the given MinMax object value
 | 
						|
        /// </summary>
 | 
						|
        /// <param name="o"></param>
 | 
						|
        /// <param name="min"></param>
 | 
						|
        /// <returns></returns>
 | 
						|
        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<GridCell> 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
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Processes the given cell reference value
 | 
						|
        /// </summary>
 | 
						|
        /// <param name="cell"></param>
 | 
						|
        /// <param name="value"></param>
 | 
						|
        /// <returns></returns>
 | 
						|
        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
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Gets the double value from the given string
 | 
						|
        /// </summary>
 | 
						|
        /// <param name="s"></param>
 | 
						|
        /// <returns></returns>
 | 
						|
        private object GetValue(string s)
 | 
						|
        {
 | 
						|
            double d;
 | 
						|
 | 
						|
            if (double.TryParse(s, out d) == true)
 | 
						|
                return (d);
 | 
						|
 | 
						|
            return (s);
 | 
						|
        }
 | 
						|
 | 
						|
        #endregion
 | 
						|
 | 
						|
        #endregion
 | 
						|
 | 
						|
        #region GetCellReferences
 | 
						|
 | 
						|
        internal List<GridCell> GetCellReferences()
 | 
						|
        {
 | 
						|
            return (GetCellReferences(_Source));
 | 
						|
        }
 | 
						|
 | 
						|
        private List<GridCell> GetCellReferences(string item)
 | 
						|
        {
 | 
						|
            const string sref1 = @"(?<col1>[\.]|[a-zA-Z_]+|""[^""]+"")\s*(?<row1>[\.]|\d+)";
 | 
						|
            const string sref2 = @"(?<col2>[\.]|[a-zA-Z_]+|""[^""]+"")\s*(?<row2>[\.]|\d+)";
 | 
						|
 | 
						|
            Regex reg = new Regex(
 | 
						|
                "(" + sref1 + ":" + sref2 + ")|" +
 | 
						|
                "(" + sref1 + ")");
 | 
						|
 | 
						|
            MatchCollection mc = reg.Matches(item);
 | 
						|
 | 
						|
            if (mc.Count > 0)
 | 
						|
            {
 | 
						|
                List<GridCell> cells = new List<GridCell>();
 | 
						|
 | 
						|
                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<GridCell> 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<GridCell> 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
 | 
						|
 | 
						|
    ///<summary>
 | 
						|
    /// ETokens
 | 
						|
    ///</summary>
 | 
						|
    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
 | 
						|
} |