-- This file is  free  software, which  comes  along  with  SmartEiffel. This
-- software  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. You can modify it as you want, provided
-- this header is kept unaltered, and a notification of the changes is added.
-- You  are  allowed  to  redistribute  it and sell it, alone or as a part of
-- another product.
--       Copyright (C) 1994-2002 LORIA - INRIA - U.H.P. Nancy 1 - FRANCE
--          Dominique COLNET and Suzanne COLLIN - SmartEiffel@loria.fr
--                       http://SmartEiffel.loria.fr
--
expanded class NUMBER_TOOLS
--
-- This class provides abstract creation functions for NUMBERs as well as 
-- some other useful tools for NUMBERs.
--
-- Because this class is expanded, one may simply declare some entity of 
-- type NUMBER_TOOLS to use those NUMBER tools. One may also inherit this 
-- class in order to use those tools as well.
--

feature

   from_integer(n: INTEGER): NUMBER is
	 -- Uses value `n' to create a new NUMBER.
      do
	 if (n <= -Base)  then
	    !LARGE_NEGATIVE_INTEGER!Result.make_smaller(n)
         elseif (n >= Base) then
	    !LARGE_POSITIVE_INTEGER!Result.make_smaller(n)
	 else
	    !SMALL_INTEGER!Result.make(n)
	 end
      ensure
	 Result.to_integer = n
      end

   from_string(formula: STRING): NUMBER is
	 -- Parse the contents of `formula' to create a new NUMBER. If some 
	 -- error occurs (like for example a division by zero), the `Result' 
	 -- is Void and the error report is left in the `parser_buffer'. 
      require
	 is_number(formula)
      do
	 parser_buffer.initialize_with(formula)
	 parser_buffer.skip_separators
	 Result := parse_create_e0
	 if parser_buffer.last_error /= Void then
	    Result := Void
	 end
      ensure
	 Result /= Void xor parser_buffer.last_error /= Void
      end

   from_input_stream(input: INPUT_STREAM ): NUMBER is
	 -- Create a number from a file or standard input
      require
	 input.is_connected
      local
	 string: STRING
      do
	 if not( input.end_of_input ) then
	    create string.make(0)
	    input.read_line_in( string )
	    Result := from_string( string )
	 end
      ensure
	 Result /= Void xor parser_buffer.last_error /= Void
      end

   is_number(formula: STRING): BOOLEAN is
	 -- Is the `formula' a correct notation to create a NUMBER ?
	 -- Actually, any correct `formula' using a combination of litteral 
	 -- integer constants with + - * / () and ! is a correct notation to 
	 -- create a NUMBER. Traditional priority rules are used for 
	 -- operators and the ! character denote the factorial computation.
	 -- Here is the BNF grammar used:
	 --
	 --  E0 = E1 R1
	 --  E1 = E2 R2
	 --  E2 = E3 R3
	 --  E3 = "+" E3 | "-" E3 | "(" E0 ")" | "constant"
	 --  R1 = "+" E1 R1 | "-" E1 R1 | ^
	 --  R2 = "*" E2 R2 | "/" E2 R2 | ^
	 --  R3 = "!" | ^
      require
	 not formula.is_empty
      do
	 parser_buffer.initialize_with(formula)
	 parser_buffer.skip_separators
	 Result := parse_e0
	 if Result then
	    if parser_buffer.current_index /= formula.count + 1 then
	       Result := False
	       parser_buffer.set_last_error(once "End of text expected.")
	    end
	 end
      ensure
	 Result xor parser_buffer.last_error /= Void
      end

   parser_buffer: MINI_PARSER_BUFFER is
	 -- This once function gives access to the unique `parser_buffer' to 
	 -- allow the memorization of the `Current' position and the 
	 -- memorization of the last error message.
      once
	 create Result
      end
   
feature {NUMBER_TOOLS}

   Base : INTEGER is
	 -- The Base is the grater number which is like 10^x and
	 -- which is inferior to the Maximu_integer value.
	 --
	 -- So if Maximum_number is 2147483647 :
	 -- The Base is :           1000000000.
	 -- A number has a value between 0-9 so a number in a
	 -- item of a FIXED_ARRAY[INTEGER] must be a succession
	 -- of 9 so the value of the greater item is 999999999.
         -- And the Base is the greater value of a item + 1.
      local
	 exponent: INTEGER
      once
	 exponent := (Maximum_integer.log10).truncated_to_integer
	 Result := (10 ^ exponent).to_integer_32
      end; -- Base

feature {NONE}

   parse_e0: BOOLEAN is
      do
	 Result := parse_e1 and then parse_r1
      end
   
   parse_e1: BOOLEAN is
      do
	 Result := parse_e2 and then parse_r2
      end
   
   parse_e2: BOOLEAN is
      do
	 Result := parse_e3
	 parse_r3
      end
   
   parse_e3: BOOLEAN is
      do
	 if parser_buffer.end_reached then
	    parser_buffer.set_last_error(Integer_expected)
	 else
	    inspect
	       parser_buffer.current_character
	    when '+', '-' then
	       parser_buffer.next
	       parser_buffer.skip_separators
	       Result := parse_e3
	    when '(' then
	       parser_buffer.next
	       parser_buffer.skip_separators
	       Result := parse_e0
	       if Result then
		  if parser_buffer.end_reached or else
		     parser_buffer.current_character /= ')'
		   then
		     Result := False
		     parser_buffer.set_last_error(Integer_expected)
		  else
		     parser_buffer.next
		     parser_buffer.skip_separators
		  end
	       end
	    else
	       Result := parse_constant
	    end
	 end
      end

   parse_r1: BOOLEAN is
      do
	 if parser_buffer.end_reached then
	    Result := True
	 else
	    inspect
	       parser_buffer.current_character
	    when '+', '-' then
	       parser_buffer.next
	       parser_buffer.skip_separators
	       Result := parse_e1 and then parse_r1
	    else
	       Result := True
	    end
	 end
      end

   parse_r2: BOOLEAN is
      do
	 if parser_buffer.end_reached then
	    Result := True
	 else
	    inspect
	       parser_buffer.current_character
	    when '*', '/' then
	       parser_buffer.next
	       parser_buffer.skip_separators
	       Result := parse_e2 and then parse_r2
	    else
	       Result := True
	    end
	 end
      end

   parse_r3 is
      do
	 if not parser_buffer.end_reached then
	    if parser_buffer.current_character = '!' then
	       parser_buffer.next
	       parser_buffer.skip_separators
	    end
	 end
      end

   parse_constant: BOOLEAN is
      local
	 stop: BOOLEAN
      do
	 if parser_buffer.end_reached or else
	    not parser_buffer.current_character.is_digit
	  then
	    parser_buffer.set_last_error(Integer_expected)
	 else
	    Result := True
	    from
	       parser_buffer.next
	    until
	       stop
	    loop
	       if parser_buffer.end_reached then
		  stop := true
	       elseif parser_buffer.current_character.is_digit then
		  parser_buffer.next
	       else
		  stop := true
	       end
	    end
	    parser_buffer.skip_separators
	 end
      end

   parse_create_e0: NUMBER is
      do
	 Result := parse_create_e1
	 Result := parse_create_r1(Result)
      end
   
   parse_create_e1: NUMBER is
      do
	 Result := parse_create_e2
	 Result := parse_create_r2(Result)
      end
   
   parse_create_e2: NUMBER is
      do
	 Result := parse_create_e3
	 Result := parse_create_r3(Result)
      end
   
   parse_create_e3: NUMBER is
      do
	 inspect
	    parser_buffer.current_character
	 when '+' then
	    parser_buffer.next
	    parser_buffer.skip_separators
	    Result := parse_create_e3
	 when '-' then
	    parser_buffer.next
	    parser_buffer.skip_separators
	    Result := - parse_create_e3
	 when '(' then
	    parser_buffer.next
	    parser_buffer.skip_separators
	    Result := parse_create_e0
	    parser_buffer.next
	    parser_buffer.skip_separators
	 else
	    Result := parse_create_constant
	 end
      end
   
   parse_create_r1(left: NUMBER): NUMBER is
      do
	 if parser_buffer.end_reached then
	    Result := left
	 else
	    inspect
	       parser_buffer.current_character
	    when '+' then
	       parser_buffer.next
	       parser_buffer.skip_separators
	       Result := left + parse_create_e1
	       Result := parse_create_r1(Result)
	    when '-' then
	       parser_buffer.next
	       parser_buffer.skip_separators
	       Result := left - parse_create_e1
	       Result := parse_create_r1(Result)
	    else
	       Result := left
	    end
	 end
      end

   parse_create_r2(left: NUMBER): NUMBER is
      do
	 if parser_buffer.end_reached then
	    Result := left
	 else
	    inspect
	       parser_buffer.current_character
	    when '*' then
	       parser_buffer.next
	       parser_buffer.skip_separators
	       Result := left * parse_create_e2
	       Result := parse_create_r2(Result)
	    when '/' then
	       parser_buffer.next
	       parser_buffer.skip_separators
	       Result := parse_create_e2
	       if Result.is_zero then
		  parser_buffer.set_last_error(once "Attempt to divide "
					       + left.to_string
					       + once " by zero.")
	       else
		  Result := left / Result
	       end
	       Result := parse_create_r2(Result)
	    else
	       Result := left
	    end
	 end
      end

   parse_create_r3(left: NUMBER): NUMBER is
      do
	 Result := left
	 if not parser_buffer.end_reached then
	    if parser_buffer.current_character = '!' then
	       parser_buffer.next
	       parser_buffer.skip_separators
	       if Result.is_abstract_integer then
		  if Result.is_positive then
		     Result := Result.factorial
		  else
		     parser_buffer.set_last_error(once "Attempt to compute %
		                           %factorial of a negative value ("
				              + Result.to_string + once ").")
		  end
	       else
		  parser_buffer.set_last_error(once "Attempt to compute %
		     %factorial with a non integral value (" 
		     + Result.to_string + once ").")
	       end
	    end
	 end
      end

   parse_create_constant: NUMBER is
      local
	 stop: BOOLEAN; c: CHARACTER; n, n_save: INTEGER
      do
	 from until stop
	 loop
	    if parser_buffer.end_reached then
	       stop := True
	    else
	       c := parser_buffer.current_character
	       if c.is_digit then
		  if Result /= Void then
		     Result := (Result @* 10) @+ c.decimal_value
		  else
		     -- *** BUG ***
		     n_save := n
		     n := n * 10 + c.decimal_value
		     if n < 0 then
			Result := from_integer(n_save)
			Result := (Result @* 10) @+ c.decimal_value
		     end
		     -- *** WORK AROUND ***
		     if n >= 214748364 then
			Result := from_integer(n)
		     end
		     -- *** D.Colnet 17 nov. 2002 ***
		  end
		  parser_buffer.next
	       else
		  stop := True
	       end
	    end
	 end
	 parser_buffer.skip_separators
	 if Result = Void then Result := from_integer(n) end
      ensure
	 Result /= Void
      end

   Integer_expected: STRING is "Integer constant expected."
   
end -- NUMBER_TOOLS