protected function evaluate($exp)
    {
        list(, $op, $left, $right, $whiteBefore, $whiteAfter) = $exp;

        $left = $this->reduce($left);
        $right = $this->reduce($right);

        if ($leftColor = $this->coerceColor($left)) {
            $left = $leftColor;
        }

        if ($rightColor = $this->coerceColor($right)) {
            $right = $rightColor;
        }

        $ltype = $left[0];
        $rtype = $right[0];

        // operators that work on all types
        if ($op == 'and') {
            return $this->toBool($left == self::$TRUE && $right == self::$TRUE);
        }

        if ($op == '=') {
            return $this->toBool($this->eq($left, $right));
        }

        if ($op == '+' && !is_null($str = $this->stringConcatenate($left, $right))) {
            return $str;
        }

        // type based operators
        $fname = "op_${ltype}_${rtype}";
        if (is_callable(array($this, $fname))) {
            $out = $this->$fname($op, $left, $right);
            if (!is_null($out)) {
                return $out;
            }
        }

        // make the expression look it did before being parsed
        $paddedOp = $op;
        if ($whiteBefore) {
            $paddedOp = ' '.$paddedOp;
        }
        if ($whiteAfter) {
            $paddedOp .= ' ';
        }

        return array('string', '', array($left, $paddedOp, $right));
    }