
(********************************************************************)
(*                                                                  *)
(*  intrange.s7i  Handle ranges of possible values for expressions. *)
(*  Copyright (C) 2020  Thomas Mertes                               *)
(*                                                                  *)
(*  This file is part of the Seed7 compiler.                        *)
(*                                                                  *)
(*  This program is free software; you can redistribute it and/or   *)
(*  modify it under the terms of the GNU General Public License as  *)
(*  published by the Free Software Foundation; either version 2 of  *)
(*  the License, or (at your option) any later version.             *)
(*                                                                  *)
(*  This program is distributed in the hope that it will be useful, *)
(*  but WITHOUT ANY WARRANTY; without even the implied warranty of  *)
(*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the   *)
(*  GNU General Public License for more details.                    *)
(*                                                                  *)
(*  You should have received a copy of the GNU General Public       *)
(*  License along with this program; if not, write to the           *)
(*  Free Software Foundation, Inc., 51 Franklin Street,             *)
(*  Fifth Floor, Boston, MA  02110-1301, USA.                       *)
(*                                                                  *)
(********************************************************************)


const type: intRange is new struct
    var integer: minValue is integer.first;
    var integer: maxValue is integer.last;
  end struct;


const func boolean: (ref intRange: range1) = (ref intRange: range2) is
  return range1.minValue = range2.minValue and range1.maxValue = range2.maxValue;


const func boolean: (ref intRange: range1) <> (ref intRange: range2) is
  return range1.minValue <> range2.minValue or range1.maxValue <> range2.maxValue;


const func intRange: getIntRange (in reference: intExpression) is func
  result
    var intRange: valueRange is intRange.value;
  local
    var reference: function is NIL;
    var ref_list: params is ref_list.EMPTY;
    var string: actionName is "";
    var reference: evaluatedParam is NIL;
    var integer: number is 0;
    var intRange: numberRange is intRange.value;
  begin
    if reduceOverflowChecks and category(intExpression) = CALLOBJECT then
      params := getValue(intExpression, ref_list);
      function := params[1];
      params := params[2 ..];
      if category(function) = ACTOBJECT then
        actionName := str(getValue(function, ACTION));
        case actionName of
          when {"ARR_LNG", "BST_LNG", "FIL_LNG", "HSH_LNG", "RFL_LNG",
                "SET_CARD",
                "INT_BYTES_BE_2_UINT", "INT_BYTES_LE_2_UINT",
                "SQL_STMT_COLUMN_COUNT"}:
            valueRange.minValue := 0;
            valueRange.maxValue := integer.last;
          when {"BIG_CMP", "BIN_CMP", "BST_CMP", "CHR_CMP", "DRW_CMP",
                "FLT_CMP", "INT_CMP", "ITF_CMP", "PCS_CMP", "REF_CMP",
                "SET_CMP", "SQL_CMP_DB", "SQL_CMP_STMT", "STR_CMP",
                "TYP_CMP"}:
            valueRange.minValue := -1;
            valueRange.maxValue := 1;
          when {"BIN_CARD"}:
            valueRange.minValue := 0;
            valueRange.maxValue := ccConf.INTTYPE_SIZE;
          when {"BLN_ORD"}:
            valueRange.minValue := 0;
            valueRange.maxValue := 1;
          when {"CHR_ORD"}:
            if ccConf.TWOS_COMPLEMENT_INTTYPE then
              valueRange.minValue := -2147483648;
            else
              valueRange.minValue := -2147483647;
            end if;
            valueRange.maxValue := 2147483647;
          when {"FIL_TELL"}:
            valueRange.minValue := 1;
            valueRange.maxValue := integer.last;
          when {"INT_ABS"}:
            numberRange := getIntRange(params[1]);
            if numberRange <> intRange.value then
              valueRange.minValue := 0;
              if numberRange.minValue > integer.first and
                  numberRange.maxValue > integer.first then
                if abs(numberRange.minValue) > abs(numberRange.maxValue) then
                  valueRange.maxValue := abs(numberRange.minValue);
                else
                  valueRange.maxValue := abs(numberRange.maxValue);
                end if;
              else
                valueRange.maxValue := integer.last;
              end if;
            else
              valueRange.minValue := 0;
              valueRange.maxValue := integer.last;
            end if;
          when {"INT_BIT_LENGTH"}:
            valueRange.minValue := 0;
            valueRange.maxValue := pred(ccConf.INTTYPE_SIZE);
          when {"INT_DIV"}:
            if getConstant(params[3], INTOBJECT, evaluatedParam) then
              number := getValue(evaluatedParam, integer);
              if number > 0 then
                valueRange.minValue := integer.first div number;
                valueRange.maxValue := integer.last div number;
              elsif number = -1 then
                valueRange.minValue := -integer.last;
              elsif number < -1 then
                valueRange.minValue := integer.last div number;
                valueRange.maxValue := integer.first div number;
              end if;
            elsif getConstant(params[1], INTOBJECT, evaluatedParam) then
              number := getValue(evaluatedParam, integer);
              if number >= 0 then
                valueRange.minValue := -number;
                valueRange.maxValue := number;
              elsif number <> integer.first then
                valueRange.minValue := number;
                valueRange.maxValue := -number;
              end if;
            end if;
          when {"INT_LOG10"}:
            valueRange.minValue := -1;
            valueRange.maxValue := log10(integer.last);
          when {"INT_LOG2"}:
            valueRange.minValue := -1;
            valueRange.maxValue := log2(integer.last);
          when {"INT_LOWEST_SET_BIT"}:
            valueRange.minValue := -1;
            valueRange.maxValue := pred(ccConf.INTTYPE_SIZE);
          when {"INT_NEGATE"}:
            numberRange := getIntRange(params[2]);
            if numberRange <> intRange.value then
              valueRange.minValue := -numberRange.maxValue;
              if numberRange.minValue < -integer.last then
                valueRange.maxValue := integer.last;
              else
                valueRange.maxValue := -numberRange.minValue;
              end if;
            else
              valueRange.minValue := -integer.last;
              valueRange.maxValue := integer.last;
            end if;
          when {"INT_MDIV"}:
            if getConstant(params[3], INTOBJECT, evaluatedParam) then
              number := getValue(evaluatedParam, integer);
              if number > 0 then
                valueRange.minValue := integer.first mdiv number;
                valueRange.maxValue := integer.last mdiv number;
              elsif number = -1 then
                valueRange.minValue := -integer.last;
              elsif number < -1 then
                valueRange.minValue := integer.last mdiv number;
                valueRange.maxValue := integer.first mdiv number;
              end if;
            elsif getConstant(params[1], INTOBJECT, evaluatedParam) then
              number := getValue(evaluatedParam, integer);
              if number >= 0 then
                valueRange.minValue := -number;
                valueRange.maxValue := number;
              elsif number <> integer.first then
                valueRange.minValue := number;
                valueRange.maxValue := -number;
              end if;
            end if;
          when {"INT_MOD"}:
            if getConstant(params[3], INTOBJECT, evaluatedParam) then
              number := getValue(evaluatedParam, integer);
              if number > 0 then
                valueRange.minValue := 0;
                valueRange.maxValue := pred(number);
              elsif number < 0 then
                valueRange.minValue := succ(number);
                valueRange.maxValue := 0;
              end if;
            elsif getConstant(params[1], INTOBJECT, evaluatedParam) then
              number := getValue(evaluatedParam, integer);
              if number = integer.first then
                valueRange.minValue := succ(integer.first) div 2;
                valueRange.maxValue := -2 - integer.first;
              elsif number = integer.last then
                valueRange.minValue := 2 - integer.last;
                valueRange.maxValue := pred(integer.last) div 2;
              elsif number > 0 then
                if integer.first + number <= 2 - number then
                  valueRange.minValue := integer.first + number;
                else
                  valueRange.minValue := 2 - number;
                end if;
                valueRange.maxValue := number;
              elsif number = 0 then
                valueRange.minValue := 0;
                valueRange.maxValue := 0;
              else # number < 0 then
                valueRange.minValue := number;
                if integer.last + number >= -(number + 2) then
                  valueRange.maxValue := integer.last + number;
                else
                  valueRange.maxValue := -(number + 2);
                end if;
              end if;
            end if;
          when {"INT_RAND"}:
            if getConstant(params[1], INTOBJECT, evaluatedParam) then
              valueRange.minValue := getValue(evaluatedParam, integer);
            end if;
            if getConstant(params[2], INTOBJECT, evaluatedParam) then
              valueRange.maxValue := getValue(evaluatedParam, integer);
            end if;
          when {"INT_REM"}:
            if getConstant(params[3], INTOBJECT, evaluatedParam) then
              number := getValue(evaluatedParam, integer);
              if number > 0 then
                valueRange.minValue := -pred(number);
                valueRange.maxValue := pred(number);
              elsif number < 0 then
                valueRange.minValue := succ(number);
                valueRange.maxValue := -succ(number);
              end if;
            elsif getConstant(params[1], INTOBJECT, evaluatedParam) then
              number := getValue(evaluatedParam, integer);
              if number >= 0 then
                valueRange.minValue := 0;
                valueRange.maxValue := number;
              elsif number = 0 then
                valueRange.minValue := 0;
                valueRange.maxValue := 0;
              else # number < 0 then
                valueRange.minValue := number;
                valueRange.maxValue := 0;
              end if;
            end if;
          when {"INT_RSHIFT"}:
            if getConstant(params[3], INTOBJECT, evaluatedParam) then
              number := getValue(evaluatedParam, integer);
              if number > 0 and number < ccConf.INTTYPE_SIZE then
                valueRange.minValue := integer.first >> number;
                valueRange.maxValue := integer.last >> number;
              end if;
            elsif getConstant(params[1], INTOBJECT, evaluatedParam) then
              number := getValue(evaluatedParam, integer);
              if number < 0 then
                valueRange.minValue := number;
                valueRange.maxValue := -1;
              else
                valueRange.minValue := 0;
                valueRange.maxValue := number;
              end if;
            end if;
          when {"INT_SQRT"}:
            valueRange.minValue := 0;
            valueRange.maxValue := sqrt(integer.last);
          when {"STR_CHIPOS", "STR_CHPOS", "STR_IPOS", "STR_POS",
                "STR_RCHIPOS", "STR_RCHPOS", "STR_RIPOS", "STR_RPOS"}:
            if getConstant(params[1], STRIOBJECT, evaluatedParam) then
              number := length(getValue(evaluatedParam, string));
              valueRange.minValue := 0;
              valueRange.maxValue := number;
            elsif ccConf.POINTER_SIZE > ccConf.INTTYPE_SIZE then
              valueRange.minValue := 0;
              valueRange.maxValue := integer.last;
            else
              valueRange.minValue := 0;
              # Because of UTF-32 the available bytes are divided by four.
              valueRange.maxValue := 2 ** (ccConf.POINTER_SIZE - 2);
            end if;
          when {"STR_LNG"}:
            if ccConf.POINTER_SIZE > ccConf.INTTYPE_SIZE then
              valueRange.minValue := 0;
              valueRange.maxValue := integer.last;
            else
              valueRange.minValue := 0;
              # Because of UTF-32 the available bytes are divided by four.
              valueRange.maxValue := 2 ** (ccConf.POINTER_SIZE - 2);
            end if;
        end case;
      end if;
    end if;
  end func;
