// ** object model
        override public object Evaluate(Dictionary <string, double> operands)
        {
            switch (_token.ID)
            {
            case TKID.ADD:
                return(+_expr.toDouble(operands));

            case TKID.SUB:
                return(-_expr.toDouble(operands));

            case TKID.ABS:
                return(Math.Abs(_expr.toDouble(operands)));

            case TKID.NOT:
                return(!_expr.toBool(operands));
            }
            throw new ArgumentException("Bad expression.");
        }
        // ** object model
        override public object Evaluate(Dictionary <string, double> operands)
        {
            // handle comparisons
            if (_token.Type == TKTYPE.COMPARE)
            {
                var cmp = _lft.CompareTo(_rgt, operands);
                if (cmp == int.MinValue)
                {
                    return(false);                      // one of the operands was NaN
                }
                switch (_token.ID)
                {
                case TKID.GT: return(cmp > 0);

                case TKID.LT: return(cmp < 0);

                case TKID.GE: return(cmp >= 0);

                case TKID.LE: return(cmp <= 0);

                case TKID.EQ: return(cmp == 0);

                case TKID.NE: return(cmp != 0);
                }
            }

            // handle everything else
            switch (_token.ID)
            {
            case TKID.ADD:
                return(_lft.toDouble(operands) + _rgt.toDouble(operands));

            case TKID.SUB:
                return(_lft.toDouble(operands) - _rgt.toDouble(operands));

            case TKID.MUL:
                return(_lft.toDouble(operands) * _rgt.toDouble(operands));

            case TKID.DIV:
                double r = _rgt.toDouble(operands);
                if (r == 0)
                {
                    return(double.NaN);             // division by 0
                }
                else
                {
                    return(_lft.toDouble(operands) / r);
                }

            case TKID.DIVINT:
                return((double)(int)(_lft.toDouble(operands) / _rgt.toDouble(operands)));

            case TKID.MOD:
                return((double)(int)(_lft.toDouble(operands) % _rgt.toDouble(operands)));

            case TKID.POWER:
                var a = _lft.toDouble(operands);
                var b = _rgt.toDouble(operands);
                if (b == 0.0)
                {
                    return(1.0);
                }
                if (b == 0.5)
                {
                    return(Math.Sqrt(a));
                }
                if (b == 1.0)
                {
                    return(a);
                }
                if (b == 2.0)
                {
                    return(a * a);
                }
                if (b == 3.0)
                {
                    return(a * a * a);
                }
                if (b == 4.0)
                {
                    return(a * a * a * a);
                }
                return(Math.Pow(_lft.toDouble(operands), _rgt.toDouble(operands)));

            case TKID.MAX:
                return(Math.Max(_lft.toDouble(operands), _rgt.toDouble(operands)));

            case TKID.MIN:
                return(Math.Min(_lft.toDouble(operands), _rgt.toDouble(operands)));

            case TKID.AND:
                return(_lft.toBool(operands) && _rgt.toBool(operands));

            case TKID.OR:
                return(_lft.toBool(operands) || _rgt.toBool(operands));
            }
            throw new ArgumentException("Bad expression.");
        }