-- 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://www.loria.fr/SmallEiffel
--
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'.
      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

