-- 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 -- class STRING -- -- Resizable character STRINGs indexed from `1' to `count'. -- inherit HASHABLE undefine is_equal end COMPARABLE redefine is_equal, copy, compare, three_way_comparison, out_in_tagged_out_memory, fill_tagged_out_memory end creation make, copy, make_empty, make_filled, from_external, from_external_copy, make_from_string, blank feature {STRING, STRING_HANDLER} 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; actually, this is always 1 (this feature is -- here to mimic the one of the COLLECTIONs hierarchy). upper: INTEGER is -- Maximum index; actually the same value as `count' (this -- feature is here to mimic the one of the COLLECTION hierarchy). 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 make_empty is -- Create an empty string. do make(0) end make_filled(c: CHARACTER; n: INTEGER) is -- Initialize string with `n' copies of `c'. require valid_count: n >= 0 do make(n) count := n fill_with(c) ensure count_set: count = n filled: occurrences(c) = count end blank(nr_blanks: INTEGER) is obsolete "Use ELKS 2001 conform feature `make_filled' instead." do make_filled(' ',nr_blanks) end feature -- Testing: is_empty: BOOLEAN is -- Has string length 0? do Result := (count = 0) end item, infix "@" (i: INTEGER): CHARACTER is -- Character at position `i'. require valid_index: valid_index(i) do Result := storage.item(i - 1) end valid_index(i: INTEGER): BOOLEAN is -- True when `i' is valid (i.e., inside actual bounds). do Result := 1 <= i and then i <= count ensure definition: Result = (1 <= i and i <= 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 is_equal(other: like Current): BOOLEAN is -- Do both strings have the same character sequence? -- (Redefined from GENERAL) do if count = other.count then Result := storage.fast_memcmp(other.storage,count) 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 item_code(i: INTEGER): INTEGER is -- Code of character at position `i'. require valid_index: valid_index (i) do Result := storage.item(i - 1).code end index_of(c: CHARACTER; start_index: INTEGER): INTEGER is -- Index of first occurrence of `c' at or after `start_index', -- 0 if none. -- -- Note: see also `first_index_of' to start searching at 1. -- Actually `first_index_of' is not exactely the equivalent of -- `index_of' in release -0.76: when the search failed the result is -- now 0 (and no longer `count' + 1). So, to update your code from -- release -0.76 to release -0.75, replace `index_of' with -- `first_index_of' and be careful to see what's done with the -- result ! require valid_start_index: start_index >= 1 and start_index <= count + 1 do from Result := start_index until Result > count or else c = item(Result) loop Result := Result + 1 end if Result > count then Result := 0 end ensure (Result /= 0) implies (item(Result) = c) end first_index_of(c: CHARACTER): INTEGER is -- Index of first occurrence of `c' at index 1 or after index 1. do Result := index_of(c,1) ensure definition: Result = index_of(c,1) end has(c: CHARACTER): BOOLEAN is -- True if `c' is in the STRING. do Result := storage.fast_has(c,count - 1) end has_substring(other: STRING): BOOLEAN is -- True if `Current' contains `other'. require other_not_void: other /= Void do Result := substring_index(other,1) /= 0 end occurrences(c: CHARACTER): INTEGER is -- Number of times character `c' appears in the string. do Result := storage.fast_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 feature -- Testing and Conversion: is_boolean: BOOLEAN is -- Does `Current' represent a BOOLEAN? -- Valid BOOLEANS are "true" and "false". do Result := (once "true").is_equal(Current) or else (once "false").is_equal(Current) end to_boolean: BOOLEAN is -- Boolean value -- "true" yields true, "false" yields false (what a surprise). require represents_a_boolean: is_boolean do Result := (once "true").is_equal(Current) end is_integer: BOOLEAN is -- Does 'Current' represent an INTEGER? -- `Result' is true if and only if the following two conditions -- hold: -- -- 1. In the following BNF grammar, the value of `Current' 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 `Current' is within the -- range that can be represented by an instance of type INTEGER. local i, state, value: INTEGER; negative: BOOLEAN; 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 variant count - i until state = 4 or else i > count loop cc := item(i) inspect state when 0 then if cc.is_separator then 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 end when 1 then if cc.is_digit then value := cc.decimal_value if negative then value := -1 * value end state := 2 else state := 4 end when 2 then if cc.is_digit then if negative then value := 10 * value - cc.decimal_value else value := 10 * value + cc.decimal_value end -- over/underflow check here if (negative and then value > 0) or else (not negative and then value < 0) then state := 4 end elseif cc.is_separator then state := 3 else state := 4 end when 3 then if cc.is_separator then else state := 4 end end i := i + 1 end if state /= 0 and then state /= 4 then Result := true end end to_integer: INTEGER is -- `Current' must look like an INTEGER. require is_integer local i, state: INTEGER; cc: CHARACTER; negative: BOOLEAN do -- state 0: nothing read. -- state 1: "+" or "-" read. -- state 2: in the number. -- state 3: after the number. from i := 1 variant count - i 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 negative := true state := 1 else -- cc.is_digit Result := cc.value state := 2 end when 1 then -- cc.is_digit Result := cc.value if negative then Result := -1 * Result end state := 2 when 2 then if cc.is_digit then if negative then Result := 10 * Result - cc.decimal_value else Result := 10 * Result + cc.decimal_value end else -- cc.is_separator state := 3 end when 3 then -- cc.is_separator i := count; -- terminate the loop end i := i + 1 end end is_double: BOOLEAN is -- Can contents be read as a DOUBLE? -- Fails for numbers where the base or "10 ^ exponent" are not in -- the range `Minimum_double' ... `Maximum_double'. Parsing is done -- positive. That means if `Minimum_double.abs' is not equal to -- `Maximum_double' it will not work correctly. Furthermore the -- arithmetric package used must support the value 'inf' for a -- number greater than Maximum_double. -- `Result' is true if and only if the following two conditions -- hold: -- -- 1. In the following BNF grammar, the value of `Current' can be -- produced by "Real_literal", if leading or trailing separators -- are ignored. -- -- Real_literal = Mantissa [Exponent_part] -- Exponent_part = "E" Exponent -- | "e" Exponent -- Exponent = Integer_literal -- Mantissa = Decimal_literal -- Decimal_literal = Integer_literal ["." Integer] -- 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 `Current' is within the -- range that can be represented by an instance of type DOUBLE. local i, state: INTEGER; cc: CHARACTER base, exp, value, multiplier: DOUBLE negative, neg_exp: 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 "-" or "+" sign of exponent -- state 7: in exponent -- state 8: after the number. -- state 9: error. from i := 1 variant count - i until state = 9 or else i > count loop cc := item(i) inspect state when 0 then if cc.is_separator then elseif cc = '+' then state := 1 elseif cc = '-' then negative := true state := 1 elseif cc.is_digit then base := cc.decimal_value state := 2 elseif cc = '.' then state := 3 else state := 9 end when 1 then if cc.is_digit then base := cc.decimal_value state := 2 elseif cc = '.' then state := 3 else state := 9 end when 2 then if cc.is_digit then base := base * 10 + cc.decimal_value elseif cc = '.' then state := 3 elseif (cc = 'e' or else cc = 'E') then state := 5 elseif cc.is_separator then state := 8 else state := 9 end when 3 then multiplier := 0.1 if cc.is_digit then base := base + multiplier * cc.decimal_value state := 4 else state := 9 end when 4 then multiplier := multiplier * 0.1 if cc.is_digit then base := base + multiplier * cc.decimal_value elseif cc.is_separator then state := 8 elseif (cc = 'e' or else cc = 'E') then state := 5 else state := 9 end when 5 then if cc = '-' then neg_exp := true state := 6 elseif cc = '+' then state := 6 elseif cc.is_digit then exp := cc.decimal_value state := 7 else state := 9 end when 6 then if cc.is_digit then exp := cc.decimal_value state := 7 else state := 9 end when 7 then if cc.is_digit then exp := exp * 10 + cc.decimal_value elseif cc.is_separator then state := 8 else state := 9 end when 8 then if cc.is_separator then else state := 9 end end i := i + 1 end if state /= 0 and then state /= 9 then Result := true if neg_exp then exp := -1 * exp end value := base * (10.0).pow(exp) if value > Maximum_double then -- can only happen if value = inf Result := false end end end to_double: DOUBLE is -- Conversion to the corresponding DOUBLE value. The string must -- looks like a DOUBLE (or like an INTEGER because fractionnal part -- is optional). For an exact definition see 'is_double'. -- Note that this conversion might not be exact. require represents_a_double: is_double local i, state: INTEGER; cc: CHARACTER; base, exp, multiplier: DOUBLE negative, neg_exp: 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 "-" or "+" sign of exponent -- state 7: in exponent -- state 8: after the number. from i := 1 variant count - i 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 negative := true state := 1 elseif cc.is_digit then base := cc.decimal_value state := 2 else -- cc = '.' state := 3 end when 1 then if cc.is_digit then base := cc.decimal_value state := 2 else -- cc = '.' state := 3 end when 2 then if cc.is_digit then base := base * 10 + cc.decimal_value elseif cc = '.' then state := 3 elseif cc.is_separator then state := 8 else -- cc = 'e' or else cc = 'E' state := 5 end when 3 then multiplier := 0.1 if cc.is_separator then state := 8 else -- cc.is_digit base := base + multiplier * cc.decimal_value state := 4 end when 4 then multiplier := multiplier * 0.1 if cc.is_digit then base := base + multiplier * cc.decimal_value elseif cc.is_separator then state := 8 else -- cc = 'e' or else cc = 'E' state := 5 end when 5 then if cc = '-' then neg_exp := true state := 6 elseif cc = '+' then state := 6 else -- cc.is_digit exp := cc.decimal_value state := 7 end when 6 then -- cc.is_digit exp := cc.decimal_value state := 7 when 7 then if cc.is_digit then exp := exp * 10 + cc.decimal_value else -- cc.is_separator state := 8 end when 8 then -- cc.is_separator i := count; -- terminate the loop end i := i + 1 end if neg_exp then exp := -1 * exp end if negative then Result := -1 * base * (10.0).pow(exp) else Result := base * (10.0).pow(exp) 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 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 is_number: BOOLEAN is -- Can contents be read as a NUMBER? local number_tools: NUMBER_TOOLS do Result := number_tools.is_number(Current) 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 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 = occurrences('0') + occurrences('1')) 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 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 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 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 else storage.clear(count,new_count - 1) end count := new_count 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. local c: INTEGER do c := other.count if c > 0 then if capacity < c then storage := storage.calloc(c) capacity := c end storage.copy_from(other.storage,c - 1) end count := c ensure then count = other.count end fill_with(c: CHARACTER) is -- Replace every character with `c'. do storage.set_all_with(c,count - 1) ensure occurrences(c) = count 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 occurrences(old_character) = 0 end append, append_string(s: STRING) is -- Append a copy of 's' to `Current'. require s_not_void: s /= Void local s_count, needed_capacity: INTEGER; new_capacity: INTEGER do s_count := s.count needed_capacity := count + s_count if needed_capacity > capacity then if capacity = 0 then storage := storage.calloc(needed_capacity) capacity := needed_capacity else new_capacity := 2 * capacity if needed_capacity > new_capacity then storage := storage.realloc(capacity,needed_capacity) capacity := needed_capacity else storage := storage.realloc(capacity,new_capacity) capacity := new_capacity end end end storage.copy_at(count,s.storage,s_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 insert_string(s: STRING; i: INTEGER) is -- Insert `s' at index `i', shifting characters from index `i' -- to `count' rightwards. require string_not_void: s /= Void valid_insertion_index: 1 <= i and i <= count + 1 local j, k: INTEGER do j := count k := s.count resize(j + k) if i <= j then storage.move(i - 1, j - 1, k) end storage.copy_at(i - 1, s.storage, k) end replace_substring(s: STRING; start_index, end_index: INTEGER) is -- Replace the substring from `start_index' to `end_index', -- inclusive, with `s'. require string_not_void: s /= Void valid_start_index: 1 <= start_index valid_end_index: end_index <= count meaningful_interval: start_index <= end_index + 1 local remove_len, insert_len, difference, old_count: INTEGER do old_count := count remove_len := end_index - start_index + 1 insert_len := s.count difference := insert_len - remove_len if difference > 0 then resize(old_count + difference) if end_index < old_count then -- something to move? storage.move(end_index + 1 - 1 , old_count - 1, difference) end elseif difference < 0 then if end_index < count then -- something to move? storage.move(end_index + 1 - 1, old_count - 1, difference) end resize(old_count + difference) end storage.copy_at(start_index - 1, s.storage, s.count) end infix "+" (other: STRING): like Current is -- Create a new STRING which is the concatenation of -- `Current' and `other'. require other_exists: other /= Void do !!Result.make(count + other.count) Result.append(Current) Result.append(other) ensure result_count: Result.count = count + other.count end put(c: CHARACTER; i: INTEGER) is -- Put `c' at index `i'. require valid_index: valid_index(i) do storage.put(c,i - 1) ensure item (i) = c 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_character(c: CHARACTER; i: INTEGER) is -- Inserts `c' at index `i', shifting characters from -- position 'i' to `count' rightwards. require valid_insertion_index: 1 <= i and i <= count + 1 local j: INTEGER do from j := count append_character(' ') until j = i - 1 loop put(item(j), j + 1) j := j - 1 end put(c, i) ensure item (i) = c 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(i: INTEGER) is -- Remove character at position `i'. require valid_removal_index: valid_index(i) do remove_between(i,i) ensure count = (old count) - 1 end add_first, precede(c: CHARACTER) is -- Add `c' at first position. local i: INTEGER do from append_character(' ') i := count until i = 1 loop put(item(i - 1),i) i := i - 1 end put(c,1) ensure count = 1 + old count item(1) = c end add_last, append_character, extend(c: CHARACTER) is -- Append `c' to string. local new_capacity: INTEGER do if capacity > count then elseif capacity = 0 then new_capacity := 32 storage := storage.calloc(new_capacity) capacity := new_capacity else new_capacity := 2 * capacity storage := storage.realloc(capacity,new_capacity) capacity := new_capacity end storage.put(c,count) count := count + 1 ensure count = 1 + old count item (count) = c 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 as_lower: like Current is -- New object with all letters in lower case. do create Result.copy(Current) Result.to_lower end as_upper: like Current is -- New object with all letters in upper case. do create Result.copy(Current) Result.to_upper end keep_head(n: INTEGER) is -- Remove all characters except for the first `n'. -- Do nothing if `n' >= `count'. require n_non_negative: n >= 0 do if n < count then remove_last(count - n) end ensure count = n.min(old count) end keep_tail(n: INTEGER) is -- Remove all characters except for the last `n'. -- Do nothing if `n' >= `count'. require n_non_negative: n >= 0 do if n < count then remove_first(count - n) end ensure count = n.min(old count) end remove_head, remove_first(n: INTEGER) is -- Remove `n' first characters. If `n' >= `count', remove all. require n_non_negative: n >= 0 do if (n > count) then count := 0 else if n > 0 then remove_between(1, n) end end ensure count = (0).max((old count) - n) end remove_tail, remove_last(n: INTEGER) is -- Remove `n' last characters. If `n' >= `count', remove all. require n_non_negative: n >= 0 do if (n > count) then count := 0 else count := count - n end ensure count = (0).max(old count - n) end remove_substring, remove_between(start_index, end_index: INTEGER) is -- Remove all characters from `strt_index' to `end_index' inclusive. require valid_start_index: 1 <= start_index valid_end_index: end_index <= count meaningful_interval: start_index <= end_index + 1 local i, len: INTEGER do len := end_index - start_index + 1 if (len > 0) then from i := end_index + 1 until i > count loop put(item(i), i - len) i := i + 1 end count := count - len end ensure count = (old count) - (end_index - start_index + 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 i := 1 until i > count or else item(i) /= ' ' loop i := i + 1 end remove_first(i - 1) 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 -- Printing: out_in_tagged_out_memory is do tagged_out_memory.append(Current) end fill_tagged_out_memory is do tagged_out_memory.append(once "count: ") count.append_in(tagged_out_memory) tagged_out_memory.append(once "capacity: ") capacity.append_in(tagged_out_memory) tagged_out_memory.append(once "storage: %"") tagged_out_memory.append(Current) tagged_out_memory.append_character('%"') end feature -- Other features: first: CHARACTER is -- Access to the very `first' character. require not is_empty do Result := storage.item(0) ensure definition: Result = item(1) end last: CHARACTER is -- Access to the very `last' character. require not is_empty do Result := storage.item(count - 1) ensure definition: Result = item(count) end substring(start_index, end_index: INTEGER): like Current is -- New string consisting of items [`start_index'.. `end_index']. require valid_start_index: 1 <= start_index valid_end_index: end_index <= count meaningful_interval: start_index <= end_index + 1 local c: INTEGER do c := end_index - start_index + 1 !!Result.make(c) Result.set_count(c) Result.storage.copy_slice(0, storage, start_index - 1, end_index - 1) ensure substring_count: Result.count = end_index - start_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 append_character(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 append_character(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 occurrences(ch) end substring_index(other: STRING; start_index: INTEGER): INTEGER is -- Position of first occurrence of `other' at or after `start' -- 0 if none. require other_not_void: other /= Void valid_start_index: start_index >= 1 and start_index <= count + 1 local i, s: INTEGER do from s := start_index 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 first_substring_index(other: STRING): INTEGER is -- Position of first occurrence of `other' at or after 1, 0 if none. require other_not_void: other /= Void do Result := substring_index(other,1) ensure definition: Result = substring_index(other,1) 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 string_buffer.clear string_buffer.append_character(c) state := 1 end else if not c.is_separator then string_buffer.append_character(c) else words.add_last(string_buffer.twin) state := 0 end end i := i + 1 end if state = 1 then words.add_last(string_buffer.twin) end end ensure words.count >= old (words.count) end feature -- Other features: 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 append_character(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 append_character('%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, from_c(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 append_character(s.item(i)) i := i + 1 end end feature -- Other features here for ELKS compatibility: make_from_string(model: STRING) is -- (Here for ELKS compatibility.) -- Initialize from the characters of `model'. -- Useful in proper descendants of STRING. require model /= Void local c: INTEGER do c := model.count if capacity < c then storage := storage.calloc(c) capacity := c end count := c storage.copy_from(model.storage,c - 1) ensure count = model.count end head(n: INTEGER) is obsolete "Since release -0.74, the new name for this feature is `keep_head'." do keep_head(n) end tail(n: INTEGER) is obsolete "Since release -0.74, the new name for this feature is `keep_tail'." do keep_tail(n) end same_string(other: STRING): BOOLEAN is -- (Here for ELKS compatibility.) -- Do `Current' and `other' have the same character sequence? -- Useful in proper descendants of STRING. require other_not_void: other /= Void do Result := string.is_equal(other.string) end string: STRING is -- (Here for ELKS compatibility.) -- New STRING having the same character sequence as `Current'. -- Useful in proper descendants of STRING. do create Result.make_from_string(Current) end feature {STRING,STRING_HANDLER} set_count(new_count: INTEGER) is require new_count <= capacity do count := new_count end feature {NONE} string_buffer: STRING is -- Private, temporary once buffer. once create Result.make(256) end split_buffer: ARRAY[STRING] is once create Result.with_capacity(4,1) end invariant 0 <= count count <= capacity capacity > 0 implies storage.is_not_null end -- STRING