///////////////////////////////////////////////////////////////////////////////
//                             Lisaac Library                                //
//                                                                           //
//                   LSIIT - ULP - CNRS - INRIA - FRANCE                     //
//                                                                           //
//   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 3 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, see <http://www.gnu.org/licenses/>.   //
//                                                                           //
//                     http://isaacproject.u-strasbg.fr/                     //
///////////////////////////////////////////////////////////////////////////////
Section Header
  
  + name    := ABSTRACT_STRING;

  - export  := STRING;
    
  - copyright   := "2003-2005 Jérome Boutet, 2003-2007 Benoit Sonntag";
  
  - comment := "Generic prototype for STRING and STRING_CONSTANT";
  
Section Inherit
  
  - parent_hashable:HASHABLE := HASHABLE;
  
  - parent_comparable:COMPARABLE := COMPARABLE;
  
Section Public  //ABSTRACT_STRING, ABSTRACT_ENTRY
  
  + storage:NATIVE_ARRAY[CHARACTER];
  
Section Public
  
  + count:INTEGER;
  
  - lower:INTEGER := 1;
  
  - upper:INTEGER <- count;
  
  - capacity:INTEGER <- count;
  
  //
  // Access.
  //
  
  - item index:INTEGER :CHARACTER <-
  // Character at position `index'.
  ( ? {valid_index index};
        
    storage.item (index - 1)
  );
  
  - '@' Left 1 index:INTEGER :CHARACTER <-
  // Character at position `index'.
  (
    item index
  );
  
  //
  // Switch case :
  //
  
  - when value:ABSTRACT_STRING then block:BLOCK :ABSTRACT_STRING <-
  (
    (Self = value).if block;
    Self
  );

  - when value1:ABSTRACT_STRING or value2:ABSTRACT_STRING then block:BLOCK :ABSTRACT_STRING <-
  (
    ((Self = value1) || {Self = value2}).if block;
    Self
  );

  //
  // Testing.
  //
  
  - valid_index index:INTEGER :BOOLEAN <-
  // True when `index' is valid (i.e., inside actual bounds).
  ( index.in_range lower to count );
  
  - is_empty : BOOLEAN <- count == 0;
  
  - hash_code: INTEGER <-
  ( + result:INTEGER;
    
    1.to count do { i:INTEGER;
      result := (5 * result) + (item i).code;
    };
    (result < 0).if	{
      result := ~ result;
    };
    result
  );
  
  - '<' other:ABSTRACT_STRING :BOOLEAN <-
  // Is Current less than `other' ?
  ( + i: INTEGER;
    + result: BOOLEAN;
    
    i := 1;
    {(count < i) || {other.count < i} || {item i != other.item i}}.until_do {
      i := i + 1;
    };
    (count < i).if {
      result := other.count >= i;
    } else {
      (other.count < i).if {
	result := FALSE;
      };
      result := item i < other.item i;
    };
    result
  );
  
  - compare other:ABSTRACT_STRING :INTEGER <-
  ( + i: INTEGER;
    + result:INTEGER;
    i := 1;
    {(count < i) || {other.count < i} || {item i != other.item i}}.until_do {
      i := i + 1;
    };
    (count < i).if {
      (other.count < i).if {
	result := 0;
      } else {
	result := -1;
      };
    } else {
      (other.count < i).if {
	result := 1;
      } else {
	(item i < other.item i).if {
	  result := -1;
	} else {
	  result := 1;
	};
      };
    };
    
    result
  );
  
  - same_as other:ABSTRACT_STRING :BOOLEAN <-
  // Case insensitive `=='.
  ( + s1, s2:NATIVE_ARRAY[CHARACTER];
    + i:INTEGER;
    + result:BOOLEAN;
    ? {other != NULL};

    i := count;
    (i = other.count).if {
      (storage.fast_memcmp (other.storage) until i).if {
	result:=TRUE;
      } else {
	i := i - 1;
	s1 := storage;
	s2 := other.storage;
	result:=TRUE;
	{(i!=0) && {result}}.while_do {
	  result:=s1.item i.same_as (s2.item i);
	  i:=i-1;
	};
      };
    };
    result
  );
  
  - '==' Left 40 other:ABSTRACT_STRING :BOOLEAN <-
  // Has Current the same text as `other' ?
  ( + result:BOOLEAN;
    ? {other != NULL};
    (count = other.count).if {
      (count = 0).if {
	result := TRUE;
      } else {
	result:=storage.fast_memcmp (other.storage) until count;
      };
    };
    result
  );
  
  - item_code i:INTEGER :INTEGER <-
  // Code of character at position `i'.
  (
    ? { valid_index i};
    storage.item (i - 1).code
  );
  
  - index_of ch:CHARACTER since start_index:INTEGER :INTEGER <-
  // Index of first occurrence of `c' at or after `start_index',
  // result = count + 1, if none.
  ( + result:INTEGER;
    ? { start_index.in_range 1 to (count + 1)};
    
    result := start_index;
    
    {(result > count) || {ch = item result}}.until_do {
      result := result + 1;
    };
        
    result
  );
  
  - last_index_of ch:CHARACTER since start_index:INTEGER :INTEGER <-
  // Index of first occurrence of `c' at or before `start_index',
  // 0 if none.
  ( + result:INTEGER;
    ? {start_index.in_range 0 to upper};
    
    result := start_index;
    
    {(result < lower) || {ch = item result}}.until_do {
      result := result - 1;
    };
        
    ? {(result != 0) ->> {item result == ch}};    
    result
  );
  
  - fast_index_of ch:CHARACTER :INTEGER <-
  // Gives the index of the first occurrence `ch' or
  // 0 if none.
  (+ result:INTEGER;
    result := 1 + storage.fast_index_of ch until (count - 1);    
    ?	{(result != count + 1) ->> {item result = ch}};    
    result
  );
  
  - first_index_of c:CHARACTER :INTEGER <-  
  // Index of first occurrence of `c' at index 1 or after index 1.
  (
    fast_index_of c
  );
  
  - fast_last_index_of ch:CHARACTER :INTEGER <-
  // Gives the index of the last occurrence `ch' or
  // 0 if none.
  (+ result:INTEGER;
    result := 1 + storage.fast_reverse_index_of ch from (upper-1);    
    
    ? {(result != 0) ->> {item result = ch}};    
    result
  );
    
  - last_index_of c:CHARACTER :INTEGER <-
  // Index of last occurrence of `c' at index upper or before index upper.
  (
    fast_last_index_of c
  );
  
  - has ch:CHARACTER :BOOLEAN <- storage.fast_has ch until (count - 1);
  // True if `ch' is in the STRING.
  
  - has_substring other:ABSTRACT_STRING :BOOLEAN <-
  // True if `other' is in the STRING.
  ( first_substring_index other != 0 );
    
  - occurrences c:CHARACTER :INTEGER <-
  // Number of times character `c' appears in the string.
  (
    storage.fast_occurrences c until (count - 1)
  );
  
  -  has_suffix s:ABSTRACT_STRING :BOOLEAN <-
  // True if suffix of `Current' is `s'.
  (
    + result:BOOLEAN;
    + i1,i2:INTEGER;
    
    ? { s != NULL };
    
    (s.count <= count).if {
      i1 := count - s.count + 1;
      i2 := 1;
      { (i1 > count) || { i2 > s.count} || { item i1 != s.item i2}}.until_do {
	i1 := i1 + 1;
	i2 := i2 + 1;
      };
      result := i1 > count;
    };
    
    result
  );
  
  - has_prefix p:ABSTRACT_STRING :BOOLEAN <-
  // True if prefix of `Current' is `p'.
  (
    + result:BOOLEAN;
    + i:INTEGER;
    
    ? { p != NULL };
    
    ( p.count <= count ).if {
      i := p.count;
      { (i == 0) || { item i != p.item i}}.until_do {
	i := i - 1;
      };
      result := i == 0;
    };
    
    result
  );
  
  // Testing and Conversion:
  
  - is_boolean:BOOLEAN <-
  // does self represent a BOOLEAN?
  // valid BOOLEANS are "TRUE" and "FALSE".
  (
    (Self == "TRUE") || { Self == "FALSE"}
  );
  
  - to_boolean:BOOLEAN <-
  // Boolean value;
  // "true" yields true, "false" yields false (what a surprise).
  (
    ?{ is_boolean };
    Self == "TRUE"
  );
  
  - is_bit:BOOLEAN <-
  // True when the contents is a sequence of bits (i.e., mixed
  // characters `0' and characters `1').
  ( + i:INTEGER;
    + result:BOOLEAN;
    i := count;
    result := TRUE;
    { (! result) || { i = 0}}.until_do {
      result := item i.is_bit;
      i := i - 1
    };
    ? {result = (count = occurrences '0' + occurrences '1')};
    result
  );

  - is_integer:BOOLEAN <-
  // Does self represent an INTEGER?
  // `Result' is true if and only if the following two conditions hold:
  //
  // 1. In the following BNF grammar, the value of self can be
  // produced by "Integer_literal", if leading and trailing
  // separators are ignored:
  //
  // Integer_literal = [Sign] Integer
  // Sign            = "+" | "-"
  // Integer         = Digit | Digit Integer
  // Digit           = "0"|"1"|"2"|"3"|"4"|"5"|"6"|"7"|"8"|"9"
  //
  // 2. The numerical value represented by self is within the
  // range that can be represented by an instance of type INTEGER.
  (
    + i, state, value: INTEGER;
    + negative: BOOLEAN;
    + result:BOOLEAN;
    + cc: CHARACTER;
    
    // state 0: nothing read.
    // state 1: "+" or "-" read.
    // state 2: in the number.
    // state 3: after the number.
    // state 4: error.
    
    i := 1;
    
    { (state = 4) || {i > count}}.until_do {
      cc := item i;
      ( state = 0).if {
	cc.is_separator.if {
	}.elseif {cc = '+'} then {
	  state := 1;
	}.elseif {cc = '-'} then {
	  negative := TRUE;
	  state := 1;
	}.elseif {cc.is_digit} then {
	  value := cc.decimal_value;
	  state := 2;
	} else {
	  state := 4;
	};
      }.elseif { state = 1} then {
	cc.is_digit.if {
	  value := cc.decimal_value;
	  negative.if {
	    value := - value;
	  };
	  state := 2;
	} else {
	  state := 4;
	};
      }.elseif { state = 2 } then {
	cc.is_digit.if {
	  negative.if {
	    value := 10 * value - cc.decimal_value;
	  } else {
	    value := 10 * value + cc.decimal_value;
	  };
	  // over/underflow check here
	  ((negative && {value > 0}) || { ! negative && {value < 0}}).if {
	    state := 4;
	  };
	}.elseif {cc.is_separator} then {
	  state := 3;
	} else {
	  state := 4;
	};
      }.elseif { state = 3 } then {
	cc.is_separator.if {
	} else {	    
	  state := 4;
	};
      };
      
      i := i + 1;
    };
    ( (state != 0) && { state != 4}).if {
      result := TRUE;
    };
    
    result
  );
  
  - to_integer:INTEGER <-
  // self must look like an INTEGER.
  (
    + i, state:INTEGER;
    + cc: CHARACTER;
    + negative: BOOLEAN;
    + result:INTEGER;
    
    ? { is_integer };
    // state 0: nothing read.
    // state 1: "+" or "-" read.
    // state 2: in the number.
    // state 3: after the number.

    i := 1;

    { i > count }.until_do {
      cc := item i;

      (state = 0).if {
	cc.is_separator.if {
	}.elseif {cc = '+'} then {
	  state := 1;
	}.elseif {cc = '-'} then {
	  negative := TRUE;
	  state := 1;
	} else { // cc.is_digit
	  result := cc.decimal_value;
	  state := 2;
	};
      }.elseif { state = 1 } then {
	// cc.is_digit
	result := cc.decimal_value;
	negative.if {
	  result := - result
	};
	state := 2;
      }.elseif { state = 2} then {
	cc.is_digit.if {
	  negative.if {
	    result := 10 * result - cc.decimal_value;
	  } else {
	    result := 10 * result + cc.decimal_value;
	  };
	} else { // cc.is_separator
	  state := 3;
	};
      }.elseif { state = 3 } then {
	// cc.is_separator
	i := count; // terminate the loop
      };
      
      i := i + 1;
    };
    
    result
  );
  
  - is_hexadecimal :BOOLEAN <-
  ( + j:INTEGER;
    + result:BOOLEAN;
    (is_empty).if_false {
      j := lower;
      {(j > upper) || {! item j.is_hexadecimal_digit}}.until_do {
	j:=j+1;
      };
      result := j > upper;
    };
    result
  );

  - to_hexadecimal :INTEGER_64 <-
  ( + result:INTEGER_64;
    ? {is_hexadecimal};
    lower.to upper do { j:INTEGER;
      result := (result << 4) | item j.hexadecimal_value;
    };
    result
  );

  - is_octal :BOOLEAN <-
  ( + result:BOOLEAN;
    + j:INTEGER;
    (is_empty).if_false {        
      j := lower;
      {(j > upper) || {! item j.is_octal_digit}}.until_do {
	j:=j+1;
      };
      result:= j > upper;
    };
    result
  );

  - to_octal:INTEGER_64 <-
  ( + result:INTEGER_64;
    ? {is_octal};
    
    lower.to upper do { j:INTEGER;
      result := (result << 3) | item j.octal_value;
    };
    result
  );

  - to_binary :INTEGER_64 <-
  ( + result:INTEGER_64;
    ? {is_bit};
    
    lower.to upper do { j:INTEGER;
      result := result << 1;
      (item j = '1').if {
	result := result | 1;
      };
    };
    result
  );
  
  - is_real_16_16:BOOLEAN <-
  // Does self represent an REAl_16_16 ?
  // `Result' is true if and only if the following two conditions hold:
  //
  // 1. In the following BNF grammar, the value of self can be
  // produced by "real_literal", if leading and trailing
  // separators are ignored:
  //
  // Real_literal = [Sign] Integer [Point Integer]
  // Sign            = "+" | "-"
  // Point           = "."
  // Integer         = Digit | Digit Integer
  // Digit           = "0"|"1"|"2"|"3"|"4"|"5"|"6"|"7"|"8"|"9"
  //
  // 2. The numerical value represented by self is within the
  // range that can be represented by an instance of type REAL_16_16.
  (
    + i, state, value_int: INTEGER;
    + value:REAL_16_16;
    + negative: BOOLEAN;
    + result:BOOLEAN;
    + cc: CHARACTER;
    + d:INTEGER;
    
    // state 0: nothing read.
    // state 1: "+" or "-" read.
    // state 2: in the number before the point
    // state 3: in the number after the point
    // state 4: after the number.
    // state 5: error.
    
    i := 1;
    d := 1;
    
    { (state = 5) || {i > count}}.until_do {
      cc := item i;
      ( state = 0).if {
	cc.is_separator.if {
	}.elseif {cc = '+'} then {
	  state := 1;
	}.elseif {cc = '-'} then {
	  negative := TRUE;
	  state := 1;
	}.elseif {cc.is_digit} then {
	  value_int := cc.decimal_value;
	  state := 2;
	} else {
	  state := 5;
	};
      }.elseif { state = 1} then {
	cc.is_digit.if {
	  value_int := cc.decimal_value;
	  state := 2;
	} else {
	  state := 5;
	};
      }.elseif { state = 2 } then {
	cc.is_digit.if {
	  value_int := 10 * value_int + cc.decimal_value;
	  // over/underflow check here
	  ((negative && {value_int > 0}) || { ! negative && {value_int < 0}}).if {
	    state := 5;
	  };
	}.elseif { cc = '.' } then {
	  state := 3;	
	  value := value_int.to_real_16_16;
	}.elseif {cc.is_separator} then {
	  state := 4;
	} else {
	  state := 5;
	};
      }.elseif { state = 3 } then {
	cc.is_digit.if {
	  d := d * 10;
	  value := value + (cc.decimal_value.to_real_16_16) /# d;
	}.elseif {cc.is_separator} then {
	  state := 4;
	} else {
	  state := 5;
	};
      }.elseif { state = 4 } then {
	cc.is_separator.if {
	} else {	    
	  state := 5;
	};
      };
      
      i := i + 1;
    };
    negative.if {
      value := - value;
    };
    ( (state != 0) && { state != 5}).if {
      result := TRUE;
    };
    
    result
  );
  
  - to_real_16_16:REAL_16_16 <-
  (
    + i, state, value_int: INTEGER;
    + value:REAL_16_16;
    + negative: BOOLEAN;    
    + cc: CHARACTER;
    + d:INTEGER;
    
    // state 0: nothing read.
    // state 1: "+" or "-" read.
    // state 2: in the number before the point
    // state 3: in the number after the point
    // state 4: after the number.
    
    i := 1;
    d := 1;

    {i > count}.until_do {
      cc := item i;
      ( state = 0).if {
	cc.is_separator.if {
	}.elseif {cc = '+'} then {
	  state := 1;
	}.elseif {cc = '-'} then {
	  negative := TRUE;
	  state := 1;
	}.elseif {cc.is_digit} then {
	  value_int := cc.decimal_value;
	  state := 2;
	};
      }.elseif { state = 1} then {
	cc.is_digit.if {
	  value_int := cc.decimal_value;
	  state := 2;
	};
      }.elseif { state = 2 } then {
	cc.is_digit.if {
	  value_int := value_int * 10 + cc.decimal_value;
	}.elseif { cc = '.' } then {
	  state := 3;	
	  value := value_int.to_real_16_16;
	} else {  // cc is separator
	  value := value_int.to_real_16_16;
	  state := 4;
	};
      }.elseif { state = 3 } then {
	cc.is_digit.if {
	  d := d * 10;
	  value := value + (cc.decimal_value.to_real_16_16) /# d;
	} else { // cc is separator
	  state := 4;
	};
      }.elseif { state = 4 } then {
	// cc is separator
	i := count;  // terminate the loop
      };
      
      i := i + 1;
    };
    
    (state = 2).if {
      value := value_int.to_real_16_16;
    };
    
    negative.if {
      value := - value;
    };
    
    value
  );
  
  //
  // Modification:
  //
  
  - '+' other:ABSTRACT_STRING :STRING <-
  // Create a new STRING which is the concatenation of
  // `self' and `other'.
  ( + result:STRING;
    ? {other != NULL};
    
    result:=STRING.create (count + other.count);
    result.append Self;
    result.append other;
    
    ? {result.count = count + other.count};
    result
  );
    
  - as_lower:STRING <-
  // New object with all letters in lower case.
  (
    + result:STRING;
    result := STRING.create capacity;
    result.copy Self;
    result.to_lower;
    result
  );
  
  - as_upper:STRING <-
  // New object with all letters in upper case.
  (
    + result:STRING;
    result := STRING.create capacity;
    result.copy Self;
    result.to_upper;
    result
  ); 
  
  // Other features:
  
  - first:CHARACTER <-
  // Access to the very `first' character.
  (
    + result:CHARACTER;
    //? {! is_empty};
    
    result := storage.item 0;
    
    //? { result == item 1};
    result
  );

  - last:CHARACTER <-
  // Access to the very `last' character.
  (
    + result:CHARACTER;
    ? {! is_empty};
    
    result := storage.item (count - 1);
    
    ? { result == item count};
    result
  );
    
  - substring start_index:INTEGER to end_index:INTEGER :STRING <-
  // New string consisting of items [`start_index'.. `end_index'].
  (
    + c:INTEGER;
    + result:STRING;
    
    ? { start_index >= 1 };
    ? { end_index <= count };
    ? { start_index <= end_index + 1 };
    
    c := end_index - start_index + 1;
    result := STRING.create c;
    result.set_count c;
    result.storage.slice_copy storage to 0 from (start_index - 1) to (end_index - 1);

    ? { result.count == end_index - start_index + 1 };
    result
  );
    
  - substring_index (other:ABSTRACT_STRING,start_index:INTEGER) :INTEGER <-
  // Position of the first occurrence of `other' at or after 1
  // or 0 f none.
  ( + i,s,result:INTEGER;
    
    //? {! other.is_empty };
    ? { (start_index >=1) && { start_index <= count + 1 }};
    
    s := start_index;
    {(result != 0) || {(s + other.count - 1) > count }}.until_do {
      i := 1;
      {(i > other.count) || {item (s + i - 1) != other.item i}}.until_do {
	i := i + 1;
      };
      
      (i > other.count).if {
	result := s;
      } else {
	s := s + 1;
      };
    };
    
    result
  );
  
  - first_substring_index other:ABSTRACT_STRING :INTEGER <-
  // Position of the first occurrence of `other' at or after 1
  // or 0 if none.
  (
    ? {! other.is_empty };
    
    substring_index (other,1)
  );
  
  //
  // Splitting a STRING:
  //
  
  - split:ARRAY[STRING] <-
  // Split the string into an array of words. Uses `is_separator' of
  // CHARACTER to find words. Gives Void or a non empty array.
  (
    + result:ARRAY[STRING];
    ( count > 0 ).if {
      split_buffer.clear;
      split_in split_buffer;
      (! split_buffer.is_empty).if {
	result := ARRAY[STRING].create (split_buffer.lower) to (split_buffer.upper);
	result.copy split_buffer;
      };
    };
    ? { (result != NULL) ->> { ! result.is_empty }};
    
    result
  );
  
  - split_in words:COLLECTION[STRING] <-
  // Same jobs as `split' but result is appended in `words'.
  (
    + state,old_count: INTEGER;
    // state = 0: waiting next word.
    // state = 1: inside a new word.
    + c: CHARACTER;
    
    ? { words != NULL};
    old_count := words.count;
    (count > 0).if {
      1.to upper do { i:INTEGER;
	c := item i;
	(state = 0).if {
	  (! c.is_separator).if {
	    string_buffer.clear;
	    string_buffer.append_character c;
	    state := 1;
	  };
	} else {
	  (! c.is_separator).if {
	    string_buffer.append_character c;
	  } else {
	    words.add_last (string_buffer.twin);	    
	    state := 0;
	  };
	};
      };
      ( state == 1).if {
	words.add_last (string_buffer.twin);
      };
    };
    
    ? { (words.count) >= old_count };
  );
  
  - get_new_iterator:ITERATOR[CHARACTER] <-
  (
    ITERATOR_ON_STRING[CHARACTER].create Self
  );
  
  - same_string other:ABSTRACT_STRING :BOOLEAN <-
  // Do self and other have the same character sequence?
  // Useful in proper descendants of STRING.
  (
    ? { other != NULL };
    string == other.to_string
  );
  
  - to_string:Strict STRING <-
  // New STRING having the same character sequence as self.
  // Useful in proper descendants of STRING.
  (
    STRING.create_from_string Self
  );
  
  - string_buffer:STRING := STRING.create 256; // Private, temporary once buffer.
  
  - split_buffer:ARRAY[STRING] := ARRAY[STRING].create_with_capacity 4 lower 1;
    
  //
  // Display.
  //
  
  - print <- 
  (
    IO.put_string Self;  
  );

  -  printline <-
  (
    IO.put_string Self;
    IO.put_string "\n";
  );

  
  - println <-
  [ -? {storage.item count = '\0'}; ]
  (
    to_external.println;
  );
                                   

  //
  // The guru section
  //
  
  - to_external:NATIVE_ARRAY[CHARACTER] <- 
  // 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 Lisaac STRING.
  (
    deferred;
    NULL
  );



