-- 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
--
class STRING
   --
   -- Resizable character STRINGs indexed from `1' to `count'. 
   --
   
inherit
   HASHABLE;
   COMPARABLE 
      redefine 
         is_equal, copy, compare, three_way_comparison, 
         out_in_tagged_out_memory, fill_tagged_out_memory
      end;
   
creation make, copy, blank, from_external, from_external_copy,
   make_from_string

feature {STRING}
   
   storage: NATIVE_ARRAY[CHARACTER];
         -- The place where characters are stored.
   
feature 

   count: INTEGER;
         -- String length which is also the maximum valid index.
   
   capacity: INTEGER;
         -- Capacity of the `storage' area.
   
   lower: INTEGER is 1;
	 -- Minimum index (which is in fact always 1).

   upper: INTEGER is
	 -- Maximum index (same value as `count').
      do
	 Result := count;
      ensure
	 Result = count
      end;
   
feature -- Creation / Modification :
   
   make(needed_capacity: INTEGER) is
         -- Initialize the string to have at least `needed_capacity'
         -- characters of storage.
      require
          non_negative_size: needed_capacity >= 0;
      do
         if needed_capacity > 0 then
            if capacity < needed_capacity then
               storage := storage.calloc(needed_capacity);
               capacity := needed_capacity;
            end;
         end;
         count := 0;
      ensure
         needed_capacity <= capacity;
         empty_string: count = 0
      end;

   blank(nr_blanks: INTEGER) is
         -- Initialize string with `nr_blanks' blanks.
      require
         nr_blanks >= 0;
      do
         make(nr_blanks);
         count := nr_blanks;
         fill_with(' ');
      ensure
         count = nr_blanks;
         nb_occurrences(' ') = count
      end;

feature -- Testing :
   
   is_empty: BOOLEAN is
         -- Has string length 0 ?
      do
         Result := (count = 0);
      end;

   empty: BOOLEAN is
      obsolete "The new name of this feature is `is_empty'."
      do
         Result := is_empty;
      end;

   item, infix "@" (index: INTEGER): CHARACTER is
         -- Character at position `index'.
      require
         valid_index(index)
      do
         Result := storage.item(index - 1);
      end;

   valid_index(index: INTEGER): BOOLEAN is
         -- True when `index' is valid (i.e., inside actual bounds).
      do
         Result := 1 <= index and then index <= count;
      ensure
         Result = (1 <= index and then index <= count)
      end;
   
   hash_code: INTEGER is
      local
         i, j: INTEGER
      do
         from
	    j := count;
	    i := 1
	 until
	    j <= 0 
	 loop
            Result := 5 * Result + item(i).code;
            i := i + 1;
	    j := j - 1;
         end
         if Result < 0 then
            Result := - (Result + 1);
         end
      end

   infix "<" (other: like Current): BOOLEAN is
         -- Is Current less than `other' ?
      local
         i: INTEGER;
      do
         from  
            i := 1;
         until
            count < i or else other.count < i 
            or else item(i) /= other.item(i)
         loop
            i := i + 1;
         end;
         if count < i then
            Result := other.count >= i;
         elseif other.count < i then
            Result := false;
         else
            Result := item(i) < other.item(i);
         end;
      end;

   compare, three_way_comparison(other: like Current): INTEGER is
      local
         i: INTEGER;
      do
         from  
            i := 1;
         until
            count < i or else other.count < i 
            or else item(i) /= other.item(i)
         loop
            i := i + 1;
         end;
         if count < i then
            if other.count < i then
            else
               Result := -1;
            end;
         elseif other.count < i then
            Result := 1;
         elseif item(i) < other.item(i) then
            Result := -1;
         else
            Result := 1;
         end;
      end;

   same_as(other: STRING): BOOLEAN is
         -- Case insensitive `is_equal'.
      require
         other /= Void
      local
         s1, s2: like storage;
         i: INTEGER;
      do
         i := count;
         if i = other.count then
            if storage.fast_memcmp(other.storage,i) then
               Result := true;
            else
               from
                  i := i - 1;
                  s1 := storage;
                  s2 := other.storage;
                  Result := true;
               until
                  i < 0
               loop
                  if s1.item(i).same_as(s2.item(i)) then
                     i := i - 1;
                  else
                     i := -1;
                     Result := false;
                  end;
               end;
            end;
         end;
      end;
   
   is_equal(other: like Current): BOOLEAN is
         -- Has Current the same text as `other' ?
      do
         if count = other.count then
            Result := storage.fast_memcmp(other.storage,count);
         end;
      end;
   
   index_of(ch: CHARACTER): INTEGER is
         -- Gives the index of the first occurrence `ch' or 
         -- `count + 1' if none.
      do
         Result := 1 + storage.fast_index_of(ch,count - 1);
      ensure
         (Result /= count + 1) implies (item(Result) = ch);
      end;
   
   index_of_string(other: STRING): INTEGER is
         -- Position of the first occurrence of `other'
         -- or `count + 1' if none.
      require
         not other.is_empty
      local
         stop: BOOLEAN;
         i1, i2, i3: INTEGER;
      do
         from
            i1 := 1;
            i2 := other.count;
            i3 := i2;
         invariant
            i3 = i2 - i1 + 1; 
         variant
            count - i1 + 1
         until
            Result /= 0
         loop
            if i2 > count then
               Result := count + 1;
            else
               from
                  stop := false;
               invariant
                  i3 = i2 - i1 + 1
               variant
                  (i3 + i2)
               until
                  stop
               loop
                  if i3 = 0 then
                     stop := true;
                     Result := i1;
                  elseif other.item(i3) /= item(i2) then
                     stop := true;
                  end;
                  i3 := i3 - 1;
                  i2 := i2 - 1;
               end;
            end;
            i1 := i1 + 1;
            i3 := other.count;
            i2 := i1 + i3 - 1;
         end;
      end;

   has(ch: CHARACTER): BOOLEAN is
         -- True if `ch' is in the STRING.
      do
         Result := storage.fast_has(ch,count - 1);
      end;

   has_string(other: STRING): BOOLEAN is
         -- True if `other' is in the STRING.
      do
         Result := index_of_string(other) /= (count + 1);
      end;

   nb_occurrences, occurrences_of, occurrences(c: CHARACTER): INTEGER is
         -- Number of times character `c' appears in the string.
      do
         Result := storage.fast_nb_occurrences(c,count - 1);
      ensure
         Result >= 0
      end;
   
   has_suffix(s: STRING): BOOLEAN is
         -- True if suffix of Current is `s'.
      require
         s /= Void
      local
         i1, i2: INTEGER;
      do
         if s.count <= count then
            from  
               i1 := count - s.count + 1;
               i2 := 1;
            until
               i1 > count or else
               i2 > s.count or else
               item(i1) /= s.item(i2)
            loop
               i1 := i1 + 1;
               i2 := i2 + 1;
            end;
            Result := i1 > count; 
         end;
      end;
   
   has_prefix(p: STRING): BOOLEAN is
         -- True if prefix of Current is `p'.
      require
         p /= Void;
      local
         i: INTEGER;
      do
         if p.count <= count then
            from  
               i := p.count;
            until
               i = 0 or else item(i) /= p.item(i)
            loop
               i := i - 1;
            end;
            Result := i = 0; 
         end;
      end;

   replace_all(old_character, new_character: like item) is
         -- Replace all occurrences of the element `old_character' by 
         -- `new_character'.
      do
         storage.fast_replace_all(old_character,new_character,count - 1);
      ensure
         count = old count;
         nb_occurrences(old_character) = 0
      end;

   is_integer: BOOLEAN is
 	 -- Can contents be read as an INTEGER ?
      local
         i, state: INTEGER;
         cc: CHARACTER;
      do
         -- state 0 : nothing read.
         -- state 1 : "+" or "-" read.
         -- state 2 : in the number.
         -- state 3 : after the number.
         -- state 4 : error.
         from
            i := 1;
         until
            i > count or else state = 4
         loop
            cc := item(i);
            inspect 
               state
            when 0 then
               if cc.is_separator then
               elseif cc = '+' then
                  state := 1;
               elseif cc = '-' then
                  state := 1;
               elseif cc.is_digit then
                  state := 2;
                  Result := true;
               else
                  state := 4;
                  Result := false;
               end;
            when 1 then
               if cc.is_separator then
               elseif cc.is_digit then
                  state := 2;
                  Result := true;
               else
                  state := 4;
                  Result := false;
               end;
            when 2 then
               if cc.is_digit then
               elseif cc.is_separator then
                  state := 3;
               else
                  state := 4;
                  Result := false;
               end;
            when 3 then
               if cc.is_separator then
               else 
                  state := 4;
                  Result := false;
               end;
            end;
            i := i + 1;
         end;
      end; 

   is_real: BOOLEAN is
         -- Can contents be read as a REAL ?
      local
         d: DOUBLE;
      do
         if is_double then
            d := to_double
            if Minimum_real <= d and then d <= Maximum_real then
               Result := true
               -- This gives only approximate accuracy; the comparison 
               -- is not accurate to nearly as many significant figures
               -- as are displayed for the limits.
            end
         end
      end

   is_double: BOOLEAN is
         -- Can contents be read as a DOUBLE ?
      local
         i, state: INTEGER;
         cc: CHARACTER;
         begun: BOOLEAN;
      do
         -- state 0 : nothing read.
         -- state 1 : "+" or "-" read.
         -- state 2 : in the number.
         -- state 3 : decimal point read
         -- state 4 : in fractional part
         -- state 5 : read 'E' or 'e' for scientific notation
         -- state 6 : read "-" sign of exponent
         -- state 7 : in exponent
         -- state 8 : after the number.
         -- state 9 : error.
         from
            i := 1;
         until
            i > count or else state = 9
         loop
            cc := item(i);
            inspect 
               state
            when 0 then
               if cc.is_separator then
               elseif cc = '+' then
                  state := 1;
               elseif cc = '-' then
                  state := 1;
               elseif cc.is_digit then
                  state := 2;
                  Result := true;
               elseif cc = '.' then
                  state := 3
               else
                  state := 9;
                  Result := false;
               end;
            when 1 then
               if cc.is_separator then
               elseif cc.is_digit then
                  state := 2;
                  Result := true;
               elseif cc = '.' then
                  state := 3
               else
                  state := 9;
                  Result := false;
               end;
            when 2 then
               if cc.is_digit then
                  begun := true
               elseif cc = '.' then
                  state := 3
               else
                  state := 9;
                  Result := false;
               end;
            when 3 then
               if cc.is_separator then
                  state := 8;
               elseif cc.is_digit then
                  state := 4
                  Result := true;
                  begun := true
               else
                  state := 9
                  Result := false
               end
            when 4 then
               if cc.is_digit then
                  begun := true
               elseif cc.is_separator then
                  state := 8;
               elseif (cc = 'e' or cc = 'E') and begun then
                  state := 5
               else
                  state := 9;
                  Result := false;
               end;
            when 5 then
               if cc = '-' then
                  state := 6
               elseif cc.is_digit then
                  state := 7
               else
                  state := 9;
                  Result := false;
               end;
            when 6 then
               if cc.is_digit then
                  state := 7
               else
                  state := 9;
                  Result := false;
               end;
            when 7 then
               if cc.is_digit then
               elseif cc.is_separator then
                  state := 8
               else
                  state := 9;
                  Result := false;
               end;
            when 8 then
               if cc.is_separator then
               else 
                  state := 9;
                  Result := false;
               end;
            end;
            i := i + 1;
         end;
         if state < 3 then
            -- exclude integers
            Result := false
         end
         -- We should check the value is within the system limits, but
         -- I can't see how to do that without trying to convert it.
      end; 

   is_number: BOOLEAN is
 	 -- Can contents be read as a NUMBER ?
      local
	 number_tools: NUMBER_TOOLS;
      do
	 Result := number_tools.is_number(Current);
      end;

   is_bit: BOOLEAN is
         -- True when the contents is a sequence of bits (i.e., mixed 
         -- characters `0' and characters `1').
      local
         i: INTEGER;
      do
         from
            i := count;
            Result := true;
         until
            not Result or else i = 0
         loop
            Result := item(i).is_bit;
            i := i - 1;
         end;
      ensure
         Result = (count = nb_occurrences('0') + nb_occurrences('1'))
      end;

feature -- Modification :

   resize(new_count: INTEGER) is
         -- Resize Current. When `new_count' is greater than
         -- `count', new positions are initialized with the
         -- default value of type CHARACTER ('%U').
      require
         new_count >= 0
      local
	 s: like storage;
      do
         if new_count <= count then
            count := new_count;
	 elseif capacity < new_count then
	    if capacity = 0 then
	       storage := storage.calloc(new_count);
	    else
	       storage := storage.realloc(capacity,new_count);
	    end;
	    capacity := new_count;
	    count := new_count;
         else
	    storage.clear(count,new_count - 1);
	    count := new_count
	 end;
      ensure
         count = new_count;
         capacity >= old capacity
      end;
   
   clear, wipe_out is
         -- Clear out the current STRING.
         -- Note : internal `storage' memory is neither released nor shrunk.
      do
         count := 0;
      ensure
         count = 0;
      end;
   
   copy(other: like Current) is
         -- Copy `other' onto Current.
      do
         count := other.count;
         if count > 0 then
            if capacity < count then
               storage := storage.calloc(count);
               capacity := count;
            end;
            storage.copy_from(other.storage,count-1);
         end;
      ensure then
         count = other.count
      end;

   fill, fill_with(c: CHARACTER) is
         -- Replace every character with `c'.
      do
         storage.set_all_with(c,count - 1);
      ensure
         nb_occurrences(c) = count
      end;
   
   fill_blank is 
         -- Fill entire string with blanks
      do  
         fill_with(' ');
      ensure
         nb_occurrences(' ') = count
      end;     

   append, append_string(other: STRING) is
         -- Append `other' to Current.
      require
         other /= Void
      local
         other_count, needed_capacity: INTEGER;
      do
         other_count := other.count;
         needed_capacity := count + other_count;
         if capacity < needed_capacity then
            if capacity = 0 then
               capacity := needed_capacity;
               storage := storage.calloc(capacity);
            else
               storage := storage.realloc(capacity,needed_capacity);
               capacity := needed_capacity;
            end;
         end;
         storage.copy_at(count,other.storage,other_count);
         count := needed_capacity;
      end;

   prepend(other: STRING) is
         -- Prepend `other' to Current
      require
         other /= Void
      local
         i, j: INTEGER;
      do
	 i := count;
	 j := other.count;
	 resize(i + j);
	 if i > 0 and then j > 0 then
	    storage.move(0, i - 1, j);
	 end;
	 storage.copy_from(other.storage, j - 1);
      ensure 
	 Current.is_equal((old other.twin) + (old Current.twin))
      end;

   infix "+" (other: STRING): STRING is
         -- Create a new STRING which is the concatenation of 
         -- `Current' and `other'.
      require
         other /= Void
      do
         !!Result.make(count + other.count);
         Result.append(Current);
         Result.append(other);
      ensure
         Result.count = count + other.count
      end;
   
   put(ch: CHARACTER; index: INTEGER) is
         -- Put `ch' at position `index'.
      require
         valid_index(index)
      do
         storage.put(ch,index - 1);
      ensure
         item (index) = ch
      end;
   
   swap(i1, i2: INTEGER) is
      require
         valid_index(i1);
         valid_index(i2)
      local
         tmp: CHARACTER;
      do
         tmp := item(i1);
         put(item(i2),i1);
         put(tmp,i2);
      ensure
         item(i1) = old item(i2);
         item(i2) = old item(i1);
      end;
   
   insert(ch: CHARACTER; index: INTEGER) is
         -- Insert `ch' after position `index'.
      require
         0 <= index and index <= count;
      local
         i: INTEGER;
      do
         from  
            i := count;
            extend(' ');
         until
            i = index
         loop
            put(item(i),i + 1);
            i := i - 1;
         end;
         put(ch,index + 1);
      ensure
         item (index + 1) = ch
      end;

   shrink(min_index, max_index: INTEGER) is
         -- Keep only the slice [`min_index' .. `max_index'] or nothing
         -- when the slice is empty.
      require
         1 <= min_index;
         max_index <= count;
         min_index <= max_index + 1
      do
         if max_index < min_index then
            count := 0;
         elseif min_index = 1 then
            count := max_index;
         else
	    storage.copy_slice(0,storage,min_index - 1,max_index - 1);
	    count := max_index - min_index + 1
         end;
      ensure
         count = max_index - min_index + 1;
      end;
   
   remove(index: INTEGER) is
         -- Remove character at position `index'.
      require
         valid_index(index)
      do
         remove_between(index,index);
      ensure
         count = (old count) - 1
      end;

   add_first(ch: CHARACTER) is
         -- Add `ch' at first position.
      local
         i: INTEGER;
      do
         from  
            extend(' ');
            i := count;
         until
            i = 1
         loop
            put(item(i - 1),i);
            i := i - 1;
         end;
         put(ch,1);
      ensure
         count = 1 + old count;
         item(1) = ch
      end;

   add_last, extend, append_character(ch: CHARACTER) is
         -- Append `ch' to string
      local
         new_capacity: INTEGER;
      do
         if capacity > count then
         elseif capacity = 0 then
            capacity := 32;
            storage := storage.calloc(capacity);
         else
            new_capacity := 2 * capacity;
            storage := storage.realloc(capacity,new_capacity);
            capacity := new_capacity;
         end;
         storage.put(ch,count);
         count := count + 1;
      ensure
         count = 1 + old count;
         item (count) = ch
      end;

   precede(ch: CHARACTER) is
         -- Prepend `ch' to string
      local
         i: INTEGER;
      do
         from  
            extend(' ');
            i := count;
         until
            i = 1
         loop
            put(item(i-1),i);
            i := i - 1;
         end;
         put(ch,1);
      ensure
         item (1) = ch
      end;

   to_lower is
         -- Convert all characters to lower case.
      local
         i: INTEGER;
      do
         from  
            i := count;
         until
            i = 0
         loop
            put(item(i).to_lower,i);
            i := i - 1;
         end;
      end;
   
   to_upper is
         -- Convert all characters to upper case.
      local
         i: INTEGER;
      do
         from  
            i := count;
         until
            i = 0
         loop
            put(item(i).to_upper,i);
            i := i - 1;
         end;
      end;

   remove_first(nb: INTEGER) is
         -- Remove `nb' first characters.
      require
         nb >= 0; 
         count >= nb;
      do
         if nb > 0 then
            remove_between(1, nb)
         end;
      ensure
         count = (old count) - nb
      end; 
   
   remove_last(nb: INTEGER) is
         -- Remove `nb' last characters.
      require
         0 <= nb;
         nb <= count;
      do
         count := count - nb;
      ensure
         count = old count - nb
      end; 
   
   remove_between(low, up : INTEGER) is
         -- Remove character between positions `low' and `up'.
      require
         valid_index(low);
         valid_index(up);
         low <= up
      local
         i : INTEGER;
      do
         from  
            i := up;
         until
            i >= count
         loop
            put(item(i + 1), low + i - up);
            i := i + 1;
         end;
         count := count - (up - low + 1);
      ensure
         count = (old count) - (up - low + 1)
      end;

   remove_suffix(s: STRING) is
      -- Remove the suffix `s' of current string.
      require
         has_suffix(s);
      do
         remove_last(s.count);
      ensure
         (old Current.twin).is_equal(Current + (old s.twin))
      end;

   remove_prefix(s: STRING) is
      -- Remove the prefix `s' of current string.
      require
         has_prefix(s);
      do
         remove_first(s.count);
      ensure
         (old Current.twin).is_equal((old s.twin) + Current)
      end;

   left_adjust is
         -- Remove leading blanks.
      local
         i: INTEGER;
      do
         from
         until
            i + 1 > count or else item(i + 1) /= ' '
         loop
            i := i + 1;
         end;
         remove_first(i);
      ensure
         stripped: is_empty or else item (1) /= ' '
      end;

   right_adjust is
         -- Remove trailing blanks.
      do
         from
         until
            count = 0 or else item(count) /= ' '
         loop
            count := count - 1;
         end
      ensure
         stripped: is_empty or else item (count) /= ' '
      end;

feature -- Features which don't modify the string

   first: CHARACTER is
      require
         not is_empty
      do
         Result := storage.item(0);
      end;

   last: CHARACTER is
      require
         not is_empty;
      do
         Result := item(count);
      end;

feature -- Conversion :

   to_integer: INTEGER is
         -- Current must looks like an INTEGER.
      require
         is_integer
      local
         i, state: INTEGER;
         cc: CHARACTER;
         minus: BOOLEAN;
      do
         -- state 0 : nothing read.
         -- state 1 : "+" or "-" read.
         -- state 2 : in the number.
         -- state 3 : after the number.
         -- state 4 : error.
         from
            i := 1;
         until
            i > count 
         loop
            cc := item(i);
            inspect 
               state
            when 0 then
               if cc.is_separator then
               elseif cc = '+' then
                  state := 1;
               elseif cc = '-' then
                  minus := true;
                  state := 1;
               else
                  check
                     cc.is_digit
                  end;
                  Result := cc.value;
                  state := 2;
               end;
            when 1 then
               if cc.is_separator then
               else
                  check
                     cc.is_digit
                  end;
                  Result := cc.value;
                  state := 2;
               end;
            when 2 then
               if cc.is_digit then
                  Result := (Result * 10) + cc.value;
               else
                  check
                     cc.is_separator
                  end;
                  state := 3;
               end;
            when 3 then
               check
                  cc.is_separator
               end;
            end;
            i := i + 1;
         end;
         if minus then
            Result := - Result;
         end;
      end; 

   to_real: REAL is
         -- Conversion to the corresponding REAL value. 
         -- The string must looks like a REAL (or like an 
         -- INTEGER because fractionnal part is optional).
      require
         is_integer or is_real
      do
         Result := to_double.to_real;
      end;
   
   to_double: DOUBLE is
         -- Conversion to the corresponding DOUBLE value. 
         -- The string must looks like a DOUBLE, like 
         -- a REAL (or like an INTEGER because fractionnal 
         -- part is optional).
      require
         is_integer or is_real or is_double
      local
         negative: BOOLEAN;
         p: POINTER;
      do
         tmp_string.copy(Current);
         tmp_string.left_adjust;
         if tmp_string.first = '-' then
            negative := true;
            tmp_string.remove_first(1);
         elseif tmp_string.first = '+' then
            tmp_string.remove_first(1);
         end;
         tmp_string.left_adjust;
         p := tmp_string.to_external;
         Result := se_string2double(p);
         if negative then
            Result := -Result;
         end;
      end;

   to_number: NUMBER is
         -- Current must looks like an INTEGER.
      require
	 is_number
      local
	 number_tools: NUMBER_TOOLS;
      do
	 Result := number_tools.from_string(Current);
      end;

   binary_to_integer: INTEGER is
         -- Assume there is enougth space in the INTEGER to store
         -- the corresponding decimal value.
      require
         is_bit;
         count <= Integer_bits
      local
         i: INTEGER;
      do
         from
            i := 1;
         until
            i > count
         loop
            if item(i) = '1' then
               Result := (2 * Result) + 1;
            else
               Result := 2 * Result;
            end;
            i := i + 1;
         end;
      end;

   to_hexadecimal is
         -- Convert Current bit sequence into the corresponding 
         -- hexadecimal notation.
      require
         is_bit
      local
         i, k, new_count: INTEGER;
         value: INTEGER;
      do
         from
            i := 1;
            k := count \\ 4;
            if k > 0 then
               new_count := 1;
            end;
         until
            k = 0
         loop
            value := value * 2 + item(i).value;
            i := i + 1;
            k := k - 1;
         end;
         if new_count > 0 then
            put(value.hexadecimal_digit,new_count);
         end;
         from
         until
            i > count 
         loop
            from
               value := item(i).value;
               i := i + 1;
               k := 3;
            until
               k = 0
            loop
               value := value * 2 + item(i).value;
               i := i + 1;
               k := k - 1;
            end;
            new_count := new_count + 1;
            put(value.hexadecimal_digit,new_count);
         end;
         count := new_count;
      end;

feature -- Printing :
   
   out_in_tagged_out_memory is
      do
         tagged_out_memory.append(Current); 
      end;
   
   fill_tagged_out_memory is
      do
         tagged_out_memory.append("count: "); 
         count.append_in(tagged_out_memory);
         tagged_out_memory.append("capacity: "); 
         capacity.append_in(tagged_out_memory);
         tagged_out_memory.append("storage: %""); 
         tagged_out_memory.append(Current); 
         tagged_out_memory.extend('%"'); 
      end;
   
feature -- Other features :
   
   substring(min_index, max_index: INTEGER): like Current is
         -- New string consisting of items [`min_index'.. `max_index'].  
      require
         1 <= min_index;
         max_index <= count; 
         min_index <= max_index + 1
      local
	 c: INTEGER;
      do
	 c := max_index - min_index + 1;
	 !!Result.make(c);
	 Result.set_count(c);
         Result.storage.copy_slice(0,storage,min_index - 1, max_index - 1);
      ensure
         Result.count = (max_index - min_index + 1)
      end;

   extend_multiple(c: CHARACTER; n: INTEGER) is
         -- Extend Current with `n' times character `c'.
      require
         n >= 0
      local
         i: INTEGER;
      do
         from
            i := n;
         until
            i = 0
         loop
            extend(c);
            i := i - 1;
         end;
      ensure
         count = n + old count
      end;

   precede_multiple(c: CHARACTER; n: INTEGER) is
         -- Prepend `n' times character `c' to Current.
      require
         n >= 0
      local
         old_count: INTEGER;
      do
         if n > 0 then
            old_count := count;
            if old_count = 0 then
               extend_multiple(c,n);
            else
               extend_multiple('%U',n);
               storage.move(0,old_count - 1,n);
               storage.set_all_with(c,n - 1);
            end;
         end;
      ensure
         count = n + old count
      end;

   extend_to_count(c: CHARACTER; needed_count: INTEGER) is
         -- Extend Current with `c' until `needed_count' is reached.
         -- Do nothing if `needed_count' is already greater or equal 
         -- to `count'.
      require
         needed_count >= 0
      local
         offset: INTEGER;
      do
         from
            offset := needed_count - count;
         until
            offset <= 0
         loop
            extend(c);
            offset := offset - 1;
         end;
      ensure
         count >= needed_count
      end;

   precede_to_count(c: CHARACTER; needed_count: INTEGER) is
         -- Prepend `c' to Current until `needed_count' is reached.
         -- Do nothing if `needed_count' is already greater or equal 
         -- to `count'.
      require
         needed_count >= 0
      local
         offset, old_count: INTEGER;
      do
         old_count := count;
         offset := needed_count - old_count;
         if offset > 0 then
            extend_to_count('%U',needed_count);
            storage.move(0,old_count - 1,offset);
            storage.set_all_with(c,offset - 1);
         end;
      ensure
         count >= needed_count
      end;

   reverse is
         -- Reverse the string.
      local
         i1, i2: INTEGER;
      do
         from  
            i1 := 1;
            i2 := count;
         until
            i1 >= i2
         loop
            swap(i1,i2);
            i1 := i1 + 1;
            i2 := i2 - 1;
         end;
      end;
   
   remove_all_occurrences(ch: CHARACTER) is
         -- Remove all occurrences of `ch'.
      local
         i, j: INTEGER;
      do
         from  
            i := 1;
            j := 1;
         until
            i > count
         loop
            if item(i) /= ch then
               put(item(i),j);
               j := j + 1;
            end;
            i := i + 1;
         end;
         count := j - 1; 
      ensure
         count = old count - old nb_occurrences(ch)
      end;

   substring_index(other: STRING; start: INTEGER): INTEGER is
         -- Position of first occurrence of `other' at or after 
         -- `start';
         -- 0 if none.
      require
         start_large_enough: start >= 1;
         start_small_enough: start <= count;
         string_exist: other /= Void
      local
         i, s: INTEGER;
      do
         from
            s := start;
         until
            Result /= 0 or else (s + other.count - 1) > count
         loop
            from
               i := 1;
            until
               i > other.count or else item(s + i - 1) /= other.item(i)
            loop
               i := i + 1;
            end;
            if i > other.count then
               Result := s;
            else
               s := s + 1
            end;
         end;
      end;

feature -- Splitting a STRING :
   
   split: ARRAY[STRING] is
         -- Split the string into an array of words.
         -- Uses `is_separator' of CHARACTER to find words.
         -- Gives Void or a non empty array.
      do
         if count > 0 then
            split_buffer.clear;
            split_in(split_buffer);
            if not split_buffer.is_empty then
               Result := split_buffer.twin;
            end;
         end;
      ensure
         Result /= Void implies not Result.is_empty
      end;

   split_in(words: COLLECTION[STRING]) is
         -- Same jobs as `split' but result is appended in `words'.
      require
         words /= Void
      local 
         state, i: INTEGER;
         -- state = 0 : waiting next word.
         -- state = 1 : inside a new word.
         c: CHARACTER;
      do
         if count > 0 then
            from
               i := 1;
            until
               i > count
            loop
               c := item(i);
               if state = 0 then
                  if not c.is_separator then
                     tmp_string.clear;
                     tmp_string.extend(c);
                     state := 1;
                  end;
               else
                  if not c.is_separator then
                     tmp_string.extend(c);
                  else
                     words.add_last(tmp_string.twin);
                     state := 0;
                  end;
               end;
               i := i + 1;
            end;
            if state = 1 then 
               words.add_last(tmp_string.twin);
            end;
         end;
      ensure
         words.count >= old (words.count)
      end;

feature -- Other feature :

   set_last(ch: CHARACTER) is
      obsolete "Since release -0.77, the new name for this feature %
               %is `extend_unless'. Please update your code."
      do
         extend_unless(ch);
      end;

   extend_unless(ch: CHARACTER) is
         -- Extend `Current' (using `extend') with `ch' unless `ch' is 
         -- already the `last' character.
      do
         if count = 0 or else item(count) /= ch then
            extend(ch);
         end;
      ensure
         last = ch;
         count >= old count
      end;

   get_new_iterator: ITERATOR[CHARACTER] is
      do
         !ITERATOR_ON_STRING!Result.make(Current);
      end;

feature -- Interfacing with C string :
   
   to_external: POINTER is
         -- Gives C access to the internal `storage' (may be dangerous).
         -- To be compatible with C, a null character is added at the end 
         -- of the internal `storage'. This extra null character is not 
         -- part of the Eiffel STRING. 
      do
         if capacity > count then
            count := count + 1;
            if item(count) /= '%U' then
               put('%U',count);
            end;
         else
            extend('%U');
         end;
         count := count - 1;
         Result := storage.to_pointer;
      ensure
         count = old count;
         Result.is_not_null
      end;

   from_external(p: POINTER) is
         -- Internal `storage' is set using `p' (may be dangerous because
         -- the external C string `p' is not duplicated).
         -- Assume `p' has a null character at the end in order to 
         -- compute the Eiffel `count'. This extra null character
         -- is not part of the Eiffel STRING.
         -- Also consider `from_external_copy' to choose the most appropriate. 
      require
         p.is_not_null
      do
         from
            storage := storage.from_pointer(p);
            count := 0;
         until
            storage.item(count) = '%U'
         loop
            count := count + 1;
         end;
         capacity := count + 1;
      ensure
         capacity = count + 1;
         p = to_external
      end;

   from_external_copy(p: POINTER) is
         -- Internal `storage' is set using a copy of `p'.
         -- Assume `p' has a null character at the end in order to 
         -- compute the Eiffel `count'. This extra null character
         -- is not part of the Eiffel STRING.
         -- Also consider `from_external' to choose the most appropriate.
      require
	 p.is_not_null
      local
         s: like storage;
         i: INTEGER;
      do
         from
            s := s.from_pointer(p);
	    count := 0;
         until
            s.item(i) = '%U'
         loop
            extend(s.item(i));
            i := i + 1;
         end;
      end;

feature -- Other feature here for ELKS'95 compatibility :

   make_from_string(model: STRING) is
         -- Initialize from the characters of `model'.
         -- (Useful in proper descendants of class STRING, to 
         -- initialize a string-like object from a manifest string.)
      require
         model /= Void
      do
	 if capacity < model.count then
	    capacity := model.count;
	    storage := storage.calloc(capacity);
	 end;
	 count := model.count;
	 storage.copy_from(model.storage,count - 1);
      ensure
         count = model.count
      end;

   head(n: INTEGER) is
      -- Remove all characters except fo the first `n'.
      -- Do nothing if n >= count.
      require
         n >= 0
      do
         if n < count then
            remove_last(count - n);
         end;
      ensure
         count = n.min(old count)
      end;
   
   tail(n: INTEGER) is
      -- Remove all characters except for the last `n'.
      -- Do nothing if n >= count.
      require
         n >= 0
      do
         if n < count then
            remove_first(count - n);
         end;
      ensure
         count = n.min(old count)
      end;

feature {STRING}

   set_count(new_count: INTEGER) is
      do
	 count := new_count;
      end;

   se_string2double(number_view: POINTER):DOUBLE is
      external "SmallEiffel"
      end; 

feature {NONE}
   
   tmp_string: STRING is 
      once
         !!Result.make(256);
      end;

   split_buffer: ARRAY[STRING] is
      once
         !!Result.with_capacity(4,1);
      end;

invariant

   0 <= count;

   count <= capacity;

   capacity > 0 implies storage.is_not_null;
   
end -- STRING
