-- This file is  free  software, which  comes  along  with  SmallEiffel. 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-98 LORIA - UHP - CRIN - INRIA - FRANCE
--            Dominique COLNET and Suzanne COLLIN - colnet@loria.fr 
--                       http://SmallEiffel.loria.fr
--
deferred class INPUT_STREAM
   --
   -- This abstract class is the superclass of all classes 
   -- representing an input stream of bytes. 
   --

feature -- State of the stream :

   is_connected: BOOLEAN is
         -- True when the corresponding stream is connected
         -- to some physical input device.
      deferred
      end;

   end_of_input: BOOLEAN is
         -- Has end-of-input been reached ?
         -- True when the last character has been read.
      require
         is_connected
      deferred
      end;

feature -- To read one character at a time :

   read_character is
         -- Read a character and assign it to `last_character'.
      require
         not end_of_input
      deferred
      ensure
         not push_back_flag
      end;

   last_character: CHARACTER is
         -- Last character read with `read_character'.
      require
         is_connected
      deferred
      end;
   
   push_back_flag: BOOLEAN;
         -- True in one char is already pushed back.

   unread_character is
         -- Un-read the last character read.
         --
      require
         not push_back_flag
      deferred
      ensure
         push_back_flag
      end;

feature -- Skipping separators :

   skip_separators is
         -- Stop doing `read_character' as soon as `end_of_file' is reached
         -- or as soon as `last_character' is not `is_separator'.
         -- When first character is already not `is_separator' nothing 
         -- is done. 
      do
         from  
         until
            end_of_input or else 
            not last_character.is_separator
         loop
            read_character;
         end;
      end;
   
   skip_separators_using(separators:STRING) is
         -- Same job as `skip_separators' using `separators'.
      require 
         separators /= void
      do
         -- Implemented by : Lars Brueckner 
         -- (larsbruk@rbg.informatik.th-darmstadt.de)
         from 
         until
            end_of_input or else 
            not separators.has(last_character)
         loop
            read_character;
         end;
      end;

feature -- To read one number at a time :
   
   read_integer is
         -- Read an integer according to the Eiffel syntax.
         -- Make result available in `last_integer'.
         -- Heading separators are automatically skipped using 
         -- `is_separator' of class CHARACTER.
         -- Trailing separators are not skipped.
      require
         not end_of_input
      local
         state: INTEGER;
         sign: BOOLEAN;
         -- state = 0 : waiting sign or first digit.
         -- state = 1 : sign read, waiting first digit.
         -- state = 2 : in the number.
         -- state = 3 : end state.
         -- state = 4 : error state.
      do
         from
         until
            state > 2
         loop
            read_character;
            inspect 
               state
            when 0 then
               if last_character.is_separator then
               elseif last_character.is_digit then
                  last_integer := last_character.value;
                  state := 2;
               elseif last_character = '-' then
                  sign := true;
                  state := 1;
               elseif last_character = '+' then
                  state := 1;
               else
                  state := 4;
               end;
            when 1 then
               if last_character.is_separator then
               elseif last_character.is_digit then
                  last_integer := last_character.value;
                  state := 2;
               else
                  state := 4;
               end;
            else -- 2
               if last_character.is_digit then
                  last_integer := (last_integer * 10) + last_character.value;
               else
                  state := 3;
               end;
            end;
            if end_of_input then
               inspect 
                  state
               when 0 .. 1 then
                  state := 4;
               when 2 .. 3 then
                  state := 3;
               else -- 4
               end;
            end;
         end;
         if not end_of_input then
            unread_character;
         end;
         debug
            if state = 4 then
               std_error.put_string("Error in INPUT_STREAM.read_integer.%N");
               crash;
            end;
         end;
         if sign then
            last_integer := - last_integer;
         end;
      end;
   
   last_integer: INTEGER; 
         -- Last integer read using `read_integer'.
   
   read_real is
         -- Read a REAL and make the result available in `last_real'
         -- and in `last_double'.
         -- The integral part is available in `last_integer'.
      require
         not end_of_input
      do
         read_double;
         last_real := last_double.to_real;
      end;
   
   last_real: REAL; 
         -- Last real read with `read_real'.
   
   read_double is
         -- Read a DOUBLE and make the result available in 
         -- `last_double'. 
      require
         not end_of_input
      local
         state: INTEGER;
         sign: BOOLEAN;
         ip, i: INTEGER;
         -- state = 0 : waiting sign or first digit.
         -- state = 1 : sign read, waiting first digit.
         -- state = 2 : in the integral part.
         -- state = 3 : in the fractional part.
         -- state = 4 : end state.
         -- state = 5 : error state.
      do
         from
            tmp_read_double.clear;
         until
            state >= 4
         loop
            read_character;
            inspect 
               state
            when 0 then
               if last_character.is_separator then
               elseif last_character.is_digit then
                  ip := last_character.value;
                  state := 2;
               elseif last_character = '-' then
                  sign := true;
                  state := 1;
               elseif last_character = '+' then
                  state := 1;
               elseif last_character = '.' then
                  state := 3;
               else
                  state := 5;
               end;
            when 1 then
               if last_character.is_separator then
               elseif last_character.is_digit then
                  ip := last_character.value;
                  state := 2;
               else
                  state := 5;
               end;
            when 2 then
               if last_character.is_digit then
                  ip := (ip * 10) + last_character.value;
               elseif last_character = '.' then
                  state := 3;
               else
                  state := 4;
               end;
            else -- 3 
               if last_character.is_digit then
                  tmp_read_double.extend(last_character);
               else
                  state := 4;
               end;
            end;
            if end_of_input then
               inspect
                  state
               when 2 .. 4 then
                  state := 4;
               else
                  state := 5;
               end;
            end;
         end;
         if not end_of_input then
            unread_character;
         end;
         debug
            if state = 5 then
               std_error.put_string("Error in STD_FILE.read_double.%N");
               crash;
            end;
         end;
         from  
            last_double := 0;
            i := tmp_read_double.count;
         until
            i = 0
         loop
            last_double := (last_double + tmp_read_double.item(i).value) / 10;
            i := i - 1;
         end;
         last_double := last_double + ip;
         if sign then
            last_double := - last_double;
         end;
      end;
   
   last_double: DOUBLE;
         -- Last double read with `read_double'.
   
feature -- To read one line or one word at a time :

   last_string: STRING is
         -- Access to a unique STRING to get the result computed
         -- with `read_line', `read_word' or `newline'.
         -- No new STRING is allocated.
      once
         !!Result.make(256);
      end;
   
   read_line is
         -- Read a complete line ended by '%N' or `end_of_input'. 
         -- Make the result available in `last_string'.
         -- Character '%N' is not added in `last_string'. 
         -- 
         -- NOTE: the result is available in `last_string' without any 
         --       memory allocation.
      require
         not end_of_input
      do
         last_string.clear;
         read_line_in(last_string);
      end;
         
   read_word is
         -- Read a word using `is_separator' of class CHARACTER. 
         -- Result is available in `last_string' (no allocation 
         -- of memory).
         -- Heading separators are automatically skipped.
         -- Trailing separators are not skipped (`last_character' is
         -- left on the first one).  
         -- If `end_of_input' is encountered, Result can be the 
         -- empty string.
      require
         not end_of_input
      do
         skip_separators;
         from  
            last_string.clear;
         until
            end_of_input or else
            last_character.is_separator
         loop
            last_string.extend(last_character);
            read_character;
         end;
      end;

   newline is
         -- Consume input until newline ('%N') is found.
         -- Corresponding STRING is stored in `last_string'.
      local
         stop: BOOLEAN;
      do
         from  
            last_string.clear;
            stop := end_of_input;
         until
            stop
         loop
            read_character;
            if end_of_input or else last_character = '%N' then
               stop := true;
            else
               last_string.extend(last_character);
            end;
         end;
      end;

feature -- Other features :
      
   read_line_in(str: STRING) is
         -- Same jobs as `read_line' but storage is directly done in `str'.
         --
      require
         not end_of_input
      deferred
      end;

   read_word_using(separators: STRING) is
         -- Same jobs as `read_word' using `separators'.
      require 
         not end_of_input;
         separators /= void
      do
         -- Implemented by : Lars Brueckner 
         -- (larsbruk@rbg.informatik.th-darmstadt.de)
         skip_separators_using(separators);
         from  
            last_string.clear;
         until
            end_of_input or else
            separators.has(last_character)
         loop
            last_string.extend(last_character);
            read_character;
         end;
      end;
    
   read_tail_in(str: STRING) is
         -- Read all remaining character of the file in `str'.
      require
         str /= Void
      do
         from
         until
            end_of_input
         loop
            read_character;
            if not end_of_input then
               str.extend(last_character);
            end;
         end;
      ensure
         end_of_input
      end;

feature {NONE}

   eof_code: INTEGER is 
      external "SmallEiffel"
      end;

   read_byte(stream_pointer : POINTER): INTEGER is
      external "SmallEiffel"
      end;

   tmp_read_double: STRING is
      once
         !!Result.make(12);
      end;

end -- INPUT_STREAM

