ST_STRING

Colin Paul Adams <[email protected]> Sat, 15 Nov 2008 22:05:45 +0000
Newsgroups gmane.comp.lang.eiffel.gobo.devel
Message-ID <[email protected]>
indexing

	description:

		"[
	     Fast UTF-32 read-only strings.
        The emphasis is on cheap substring operations.
        Therefore `area' does not include an extra item for NULL termination (for C-strings)
        nor an initial extra item to avoid the cost of index translation.
      ]"

	library: "Gobo Eiffel String Library"
	copyright: "Copyright (c) 2008, Colin Adams and others"
	license: "MIT License"
	date: "$Date:  $"
	revision: "$Revision:  $"

class ST_STRING

inherit

	IMMUTABLE_STRING_GENERAL
		redefine
			is_equal
		end

	HASHABLE
		redefine
			is_equal
		end

	TO_SPECIAL [NATURAL_32]
		rename
			item as special_item,
			infix "@" as special_item_alias
		export
			{NONE} all
			{ST_STRING} area
		redefine
			is_equal, valid_index
		end

	ASCII
		export
			{NONE} all
		redefine
			is_equal
		end
	
	UC_IMPORTED_UNICODE_ROUTINES
		export
			{NONE} all
		redefine
			is_equal
		end
	
	ST_STRING_ROUTINES
		export
			{NONE} all
		redefine
			is_equal
		end

	ST_SHARED_STRINGS
		export
			{NONE} all
		redefine
			is_equal
		end

	ST_IMPORTED_UNICODE_FULL_CASE_MAPPING
		export
			{NONE} all
		redefine
			is_equal
		end
	
	ST_IMPORTED_UNICODE_NORMALIZATION_ROUTINES
		export
			{NONE} all
		redefine
			is_equal
		end

create

	make, make_from_string, make_shared, make_from_codepoints,
	make_from_codes_list, make_empty, make_filled
	
convert

	to_string: {STRING_32}, to_utf8_string: {UC_UTF8_STRING, STRING_8}

feature {NONE} -- Initialization

	make (a_size: INTEGER) is
			-- Initialize to `a_size' NULLs.
		require
			a_size_non_negative: a_size >= 0
		do
			make_area (a_size)
			if a_size > 0 then
				lower := 1
			end
		ensure
			correct_count: count = a_size
			lower_is_zero_for_empty_string: a_size = 0 implies lower = 0
			lower_is_one: a_size > 0 implies lower = 1
		end

	make_empty is
			-- Initialize as empty string.
		do
			make (0)
		ensure
			empty: is_empty
		end
	
	make_filled (a_code: NATURAL_32; a_size: INTEGER) is
			-- Initialize with `a_size' characters all of codepoint `a_code'.
		require
			a_code_valid: valid_code (a_code)
			a_size_non_negative: a_size >= 0
		local
			i: INTEGER
		do
			make_area (a_size)
			if a_size > 0 then
				lower := 1
			end
			from
				i := 1
				upper := a_size
			until
				i > upper
			loop
				area.put (a_code, i - 1)
				i := i + 1
			end
		ensure
			correct_count: count = a_size
			all_characters_correct: -- not yet supported by ISE: for_all (agent a_code.is_equal)
		end

	make_from_string (a_string: READABLE_STRING_GENERAL) is
			-- Initialize to same characters as `a_string'.
		require
			a_string_not_void: a_string /= Void
		local
			i: INTEGER
		do
			make_area (a_string.count)
			if a_string.count > 0 then
				lower := 1
			end
			from
				i := 1
				upper := a_string.count
			until
				i > upper
			loop
				area.put (a_string.code (i), i - 1)
				i := i + 1
			end
		ensure
			correct_count: count = a_string.count
			correct_character_sequence: for_all_with_index (agent same_item_as_string (a_string, ?, ?))
		end

	make_from_codepoints (a_array: ARRAY [NATURAL_32]) is
			-- Initialize `Current' by copying codepoints from `a_array'.
		require
			a_array_not_void: a_array /= Void
			a_array_all_valid: a_array.for_all (agent valid_code) 
		local
			i: INTEGER
		do
			make_area (a_array.count)
			if a_array.count > 0 then
				lower := 1
			end
			from
				i := 1
				upper := a_array.count
			until
				i > upper
			loop
				area.put (a_array.item (i), i - 1)
				i := i + 1
			end
		ensure
			correct_count: count = a_array.count
			correct_character_sequence: for_all_with_index (agent same_item_as_array (a_array, ?, ?))
		end

	make_from_codes_list (a_array: DS_ARRAYED_LIST [INTEGER_32]) is
			-- Initialize `Current' by copying codepoints from `a_array'.
		require
			a_array_not_void: a_array /= Void
		local
			i: INTEGER
		do
			make_area (a_array.count)
			if a_array.count > 0 then
				lower := 1
			end
			from
				i := 1
				upper := a_array.count
			until
				i > upper
			loop
				area.put (a_array.item (i).as_natural_32, i - 1)
				i := i + 1
			end
		ensure
			correct_count: count = a_array.count
		end

	make_shared (a_area: like area; a_start, a_end: INTEGER) is
			--	Initializing sharing `a_area' from `a_start' to `a_end' inclusive.
		require
			a_area_not_void: a_area /= Void
			a_start_large_enough: a_start > 0
			a_end_large_enough: a_end > 0
			a_end_not_smaller_than_a_start: a_end >= a_start
		do
			area := a_area
			lower := a_start
			upper := a_end
		ensure
			area_set: area = a_area
			lower_set: lower = a_start
			upper_set: upper = a_end
		end

feature -- Access

	lower: INTEGER
			-- Index of first character

	upper: INTEGER
			-- Index of last character

	code (a_index: INTEGER): NATURAL_32 is
			-- Unicode codepoint at `a_index'
		do
			Result := area.item (a_index + lower - 2)
		ensure then
			valid_code: valid_code (Result)
		end

	item, infix "@" (a_index: INTEGER): CHARACTER_32 is
			-- Unicode character at `a_index'
		require
			a_index_valid: valid_index (a_index)
		do
			Result := code (a_index).to_character_32
		ensure
			valid_character: unicode.valid_non_surrogate_code (Result.code)
		end

	hash_code: INTEGER is
			-- Hash code value
		local
			i: INTEGER
			l_area: like area
		do
			Result := internal_hash_code
			if Result = 0 then
				if not is_empty then
					-- The magic number `8388593' below is the greatest prime lower than
					-- 2^23 so that this magic number shifted to the left does not exceed 2^31.
					from
						i := lower - 1
						l_area := area
					until
						i = upper
					loop
						Result := ((Result \\ 8388593) |<< 8) + l_area.item (i).to_integer_32
						i := i + 1
					end
					internal_hash_code := Result
				end
			end
		end
	
	maximal_split (a_separators: READABLE_STRING_GENERAL): DS_LIST [ST_STRING] is
			-- `Current' tokenized by `a_separators';
			-- Each character in `Current' matching any in `a_separators' is discarded,
			--  and a (possibly empty) token is extracted.
			-- E.g. "//my/shares/top\directory/in/path/".maximal_split ("/\") yields:
			-- ("","","my","shares","top","directory","in","path","").
		require
			a_separators_not_void: a_separators /= Void
		do
			Result := internal_split (a_separators, True)
		ensure
			maximal_split_not_void: Result /= Void
			no_void_item: not Result.has (Void)
		end

	minimal_split (a_separators: READABLE_STRING_GENERAL): DS_LIST [ST_STRING] is
			-- `Current' tokenized by `a_separators';
			-- Each character in `Current' matching any in `a_separators' is discarded,
			--  and a token is extracted. Consecutive separator characters do not yield
			--  additional tokens.
			-- E.g. "//my/shares/top\directory/in/path/".maximal_split ("/\") yields:
			-- ("","my","shares","top","directory","in","path","").
		require
			a_separators_not_void: a_separators /= Void
		do
			Result := internal_split (a_separators, False)
		ensure
			minimal_split_not_void: Result /= Void
			no_void_item: not Result.has (Void)
		end
	
	trimmed: ST_STRING is
			-- Version of `Current' without leading or trailing XML white space characters (SPACE, TAB, LF and CR)
		do
			Result := stripped_of (" %T%N%R", True, True)
		ensure
			trimmed_not_void: Result /= Void
			not_longer: Result.count <= count
			no_leading_white_space: not Result.is_empty implies not (" %T%N%R").has_code (Result.code (1))
			no_trailing_forbidden_character: not Result.is_empty implies not (" %T%N%R").has_code (Result.code (Result.count))
		end
	
	stripped_of (a_forbidden: READABLE_STRING_GENERAL; a_leading_stripped, a_trailing_stripped: BOOLEAN): ST_STRING is
			-- Version of `Current' without any leading/trailing characters from `a_forbidden';
		require
			a_forbidden_not_void: a_forbidden /= Void
		local
			l_leading_index, l_trailing_index: INTEGER
		do
			l_leading_index := 1
			l_trailing_index := count
			if a_leading_stripped then
				from
				until
					not valid_index (l_leading_index) or else not a_forbidden.has_code (code (l_leading_index))
				loop
					l_leading_index := l_leading_index + 1
				end
			end
			if a_trailing_stripped then
				from
				until
					(not valid_index (l_trailing_index) or l_trailing_index = l_leading_index) or else (not a_forbidden.has_code (code (l_trailing_index)))
				loop
					l_trailing_index := l_trailing_index - 1
				end				
			end
			Result := substring (l_leading_index, l_trailing_index)
		ensure
			stripped_of_not_void: Result /= Void
			not_longer: Result.count <= count
			no_leading_forbidden_character: (a_leading_stripped and not Result.is_empty) implies not a_forbidden.has_code (Result.code (1))
			no_trailing_forbidden_character: (a_trailing_stripped and not Result.is_empty) implies not a_forbidden.has_code (Result.code (Result.count))
		end
	
	substring_index (a_other: ST_STRING; a_start_index: INTEGER): INTEGER is
			-- Index of first occurrence of `a_other' at or after `a_start_index';
			-- 0 if none
		require
			a_other_not_void: a_other /= Void
			valid_a_start_index: a_start_index >= 1 and a_start_index <= count + 1
		do
			Result := string_searcher.substring_index (Current, a_other, a_start_index, count)
		ensure
			valid_result: Result = 0 or else
				(a_start_index <= Result and Result <= count - a_other.count + 1)
			zero_if_absent: (Result = 0) =
				not substring (a_start_index, count).has_substring (a_other)
			at_this_index: Result >= a_start_index implies
				a_other.same_string (substring (Result, Result + a_other.count - 1))
			none_before: Result > a_start_index implies
				not substring (a_start_index, Result + a_other.count - 2).has_substring (a_other)
		end

feature -- Measurement

	count: INTEGER
			-- Number of characters in `Current'
		do
			if upper = 0 then
				Result := 0
			else
				Result := upper - lower + 1
			end
		end

	capacity: INTEGER is
			-- Number of characters allocated in Current
		do
			Result := count
		end
	
	occurrences (c: NATURAL_32): INTEGER is
			-- Number of times `c' appears in the string
		local
			i, l_count: INTEGER
			a: SPECIAL [NATURAL_32]
		do
			from
				l_count := count
				a := area
			until
				i = l_count
			loop
				if a.item (i) = c then
					Result := Result + 1
				end
				i := i + 1
			end
		ensure then
			zero_if_empty: count = 0 implies Result = 0
			recurse_if_not_found_at_first_position:
				(count > 0 and then code (1) /= c) implies
					Result = substring (2, count).occurrences (c)
			recurse_if_found_at_first_position:
				(count > 0 and then code (1) = c) implies
					Result = 1 + substring (2, count).occurrences (c)
		end

feature -- Comparison
	
	infix "<" (a_other: like Current): BOOLEAN is
			-- Is current object less than `other'?
		local
			l_count, l_other_count: INTEGER
		do
			if a_other /= Current then
				l_count := count
				l_other_count := a_other.count
				if l_count < l_other_count then
					Result := (leading_string_comparison (a_other, l_count) >= 0)
				else
					Result := (leading_string_comparison (a_other, l_other_count) > 0)
				end
			end
		end

	is_equal (a_other: like Current): BOOLEAN is
			-- Is `a_other' attached to an object of the same type
			-- as current object and identical to it?
		do
			Result := same_string (a_other)
		end

	same_case_insensitive (a_other: READABLE_STRING_GENERAL): BOOLEAN is
			-- Does `Current' represent the same character sequence (ignoring_case) as `a_other'?
			-- Full case folding is not yet implemented (TODO).
			-- Needs a more efficient implementation (TODO).
			-- Normalization is not considered. To check that two strings are
			--  equal under a given normalization form, compare normalized
			--  copies of the two strings.
		require
			a_other_not_void: a_other /= Void
		local
			l_string: ST_STRING
		do
			if a_other = Current then
				Result := True
			elseif a_other.count = count then
				l_string ?= a_other
				if l_string = Void then
					create l_string.make_from_string (a_other)
				end
				Result := to_lower.same_string (l_string.to_lower)
			end
		end
			
feature -- Status report

	valid_index (i: INTEGER): BOOLEAN is
			-- Is `i' within the bounds of the string?
		do
			Result := i > 0 and i <= count			
		end

	valid_code (a_code: like code): BOOLEAN is
			-- Is `a_code' a valid Unicode codepoint?
		do
			Result := unicode.valid_non_surrogate_code (a_code.as_integer_32)
		end
	
	is_natural, is_natural_32: BOOLEAN is
			-- Does `Current' represent a 32-bit unsigned integer?
		do
			Result := is_valid_integer_or_natural ({NUMERIC_INFORMATION}.type_natural_32)
		end
	
	is_integer, is_integer_32: BOOLEAN is
			-- Does `Current' represent a 32-bit signed integer?
		do
			Result := is_valid_integer_or_natural ({NUMERIC_INFORMATION}.type_integer_32)
		end
	
	is_integer_64: BOOLEAN is
			-- Does `Current' represent a 64-bit signed_integer?
		do
			Result := is_valid_integer_or_natural ({NUMERIC_INFORMATION}.type_integer_64)
		end

	is_double: BOOLEAN is
			-- Does `Current' represent a DOUBLE?
		local
			l_convertor: like ctor_convertor
		do
			if is_valid_as_string_8 then
				l_convertor := ctor_convertor
				l_convertor.parse_string_with_type (Current, {NUMERIC_INFORMATION}.type_double)
				Result := l_convertor.is_integral_double
			end
		ensure
			syntax_and_range:
				-- 'Result' is True if and only if the following two
				-- conditions are satisfied:
				--
				-- 1. In the following BNF grammar, the value of
				--	'Current' can be produced by "Real_literal":
				--
				-- Real_literal	= Mantissa [Exponent_part]
				-- Exponent_part = "E" Exponent
				--				 | "e" Exponent
				-- Exponent		= Integer_literal
				-- Mantissa		= Decimal_literal
				-- Decimal_literal = Integer_literal ["." [Integer]] | "." 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.
		end
	
	is_real: BOOLEAN is
			-- Does `Current' represent a REAL?
		do
			Result := is_double
		ensure
			syntax_and_range:
				-- 'Result' is True if and only if the following two
				-- conditions are satisfied:
				--
				-- 1. In the following BNF grammar, the value of
				--	'Current' can be produced by "Real_literal":
				--
				-- Real_literal	= Mantissa [Exponent_part]
				-- Exponent_part = "E" Exponent
				--				 | "e" Exponent
				-- Exponent		= Integer_literal
				-- Mantissa		= Decimal_literal
				-- Decimal_literal = Integer_literal ["." [Integer]] | "." 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 REAL.
		end

	is_empty: BOOLEAN is
			-- Is structure empty?
		do
			Result := (count = 0)
		end

	is_string_8: BOOLEAN is
			-- Is `Current' a STRING_8?
		do
		end

	is_string_32: BOOLEAN is
			-- Is `Current' a STRING_32?
		do
		end

	is_valid_as_string_8: BOOLEAN is
			-- Is `Current' convertible to STRING_8 without information loss?
		do
			Result := for_all (agent is_character_8)
		end
	
	is_hexadecimal: BOOLEAN is
			-- Is `Current' made up of characters 0-9 or A-F or a-f?
		local
			i, l_count: INTEGER
			l_code: NATURAL_32
		do
			l_count := count
			if l_count = 0 then
				Result := False
			else
				Result := True
				from i := 1 until i > l_count loop
					l_code := code (i)
					if (l_code < Zero.as_natural_32 or l_code > Nine.as_natural_32) and (l_code < Lower_a.as_natural_32 or l_code > Lower_f.as_natural_32) and
						(l_code < Upper_a.as_natural_32 or l_code > Upper_f.as_natural_32)
					 then
						Result := False
						i := l_count + 1 -- Jump out of the loop.
					else
						i := i + 1
					end
				end
			end
		end

	is_base64: BOOLEAN is
			-- Is `Current' made up of characters +, /, =, XML whitespace, 0-9 or A-Z or a-z?
		local
			i, l_count: INTEGER
			l_code: NATURAL_32
		do
			l_count := count
			if l_count = 0 then
				Result := False
			else
				Result := True
				from i := 1 until i > l_count loop
					l_code := code (i)
					if (l_code < Zero.as_natural_32 or l_code > Nine.as_natural_32) and (l_code < Lower_a.as_natural_32 or l_code > Lower_z.as_natural_32) and
						(l_code < Upper_a.as_natural_32 or l_code > Upper_z.as_natural_32) and
						l_code /= Plus.as_natural_32 and l_code /= Slash.as_natural_32 and
						l_code /= Equal_sign.as_natural_32 and l_code /= Blank.as_natural_32 and
						l_code /= Tabulation.as_natural_32 and l_code /= Carriage_return.as_natural_32 and l_code /= Line_feed.as_natural_32
					then
						Result := False
						i := l_count + 1 -- Jump out of the loop.
					else
						i := i + 1
					end
				end
			end
		end
	
	has_substring (a_other: ST_STRING): BOOLEAN is
			-- Does `Current' contain `a_other'?
		require
			a_other_not_void: a_other /= Void
		do
			if a_other = Current then
				Result := True
			elseif a_other.count <= count then
				Result := substring_index (a_other, 1) > 0
			end
		ensure
			false_if_too_small: count < a_other.count implies not Result
			true_if_initial: (count >= a_other.count and then
				a_other.same_string (substring (1, a_other.count))) implies Result
			recurse: (count >= a_other.count and then
				not a_other.same_string (substring (1, a_other.count))) implies
					(Result = substring (2, count).has_substring (a_other))
		end
	
	is_nfd: BOOLEAN is
			-- Is `Current' in NFD normal form?
		do
			Result := normalization.is_nfd (Current)
		end

	is_nfkd: BOOLEAN is
			-- Is `Current' in NFKD normal form?
		do
			Result := normalization.is_nfkd (Current)
		end

	is_nfc: BOOLEAN is
			-- Is `Current' in NFC normal form?
		do
			Result := normalization.is_nfc (Current)
		end

	is_nfkc: BOOLEAN is
			-- Is `Current' in NFKC normal form?
		do
			Result := normalization.is_nfkc (Current)
		end

feature -- New strings

	concatenated_before (a_other: READABLE_STRING_GENERAL): ST_STRING is
			-- New string formed by concatenating `Current' before `a_other'
		require
			a_other_not_void: a_other /= Void
		local
			l_builder: ST_STRING_BUILDER
		do
			create l_builder.make (count + a_other.count)
			l_builder.append_strings (<<Current, a_other>>)
			l_builder.set_built
			Result := l_builder.new_string
		ensure
			concatenated_with_not_void: Result /= Void
			correct_count: Result.count = count + a_other.count
			correct_prefix: Result.substring (1, count).same_string (Current)
			correct_suffix: Result.substring (1 + count, Result.count).same_string (a_other)
		end
	
	concatenated_after (a_other: READABLE_STRING_GENERAL): ST_STRING is
			-- New string formed by concatenating `Current' after `a_other'
		require
			a_other_not_void: a_other /= Void
		local
			l_builder: ST_STRING_BUILDER
		do
			create l_builder.make (count + a_other.count)
			l_builder.append_strings (<<a_other, Current>>)
			l_builder.set_built
			Result := l_builder.new_string
		ensure
			concatenated_with_not_void: Result /= Void
			correct_count: Result.count = count + a_other.count
			correct_suffix: Result.substring (1 + a_other.count, Result.count).same_string (Current)
			correct_prefix: Result.substring (1, a_other.count).same_string (a_other)
		end
	
	concatenated_before_strings (a_strings: ARRAY [READABLE_STRING_GENERAL]): ST_STRING is
			-- New string formed by concatenating `Current' before all of `a_strings'.
		require
			a_strings_not_void: a_strings /= Void
			all_strings_not_void: not a_strings.has (Void)
		local
			l_builder: ST_STRING_BUILDER
		do
			create l_builder.make (count + string_sums (a_strings))
			l_builder.append_string (Current)
			l_builder.append_strings (a_strings)
			l_builder.set_built
			Result := l_builder.new_string
		ensure
			concatenated_before_strings_not_void: Result /= Void
			correct_count: Result.count = count + string_sums (a_strings)
			correct_prefix: Result.substring (1, count).same_string (Current)
			correct_suffix: True -- TODO
		end
	
	concatenated_before_code (a_code: NATURAL_32): ST_STRING is
			-- New string formed by concatenating `Current' before `a_code'
		local
			l_builder: ST_STRING_BUILDER
		do
			create l_builder.make (count + 1)
			l_builder.append_string (Current)
			l_builder.append_code (a_code)
			l_builder.set_built
			Result := l_builder.new_string
		ensure
			concatenated_with_not_void: Result /= Void
			correct_count: Result.count = count + 1
			correct_prefix: Result.substring (1, count).same_string (Current)
			correct_suffix: Result.code (Result.count) = a_code
		end

feature -- Iteration

	for_all (a_test: PREDICATE [ANY, TUPLE [NATURAL_32]]): BOOLEAN is
			-- Is `a_test' true for all codepoints?
		require
			a_test_not_void: a_test /= Void
		local
			l_tuple: TUPLE [NATURAL_32]
			i: INTEGER
		do
			Result := True
			if count > 0 then
				from
					create l_tuple
					i := lower
				until
					i > upper or not Result
				loop
					l_tuple.put (area.item (i - 1), 1)
					Result := a_test.item (l_tuple)
					i := i + 1
				end
			end
		end

	for_all_with_index (a_test: PREDICATE [ANY, TUPLE [NATURAL_32, INTEGER]]): BOOLEAN is
			-- Is `a_test' true for all codepoints?
		require
			a_test_not_void: a_test /= Void
		local
			l_tuple: TUPLE [NATURAL_32, INTEGER]
			i: INTEGER
		do
			Result := True
			if count > 0 then
				from
					create l_tuple
					i := lower
				until
					i > upper or not Result
				loop
					l_tuple.put (area.item (i - 1), 1)
					l_tuple.put (i - lower + 1, 2)
					Result := a_test.item (l_tuple)
					i := i + 1
				end
			end
		end

	do_all, do_forward (a_action: PROCEDURE [ANY, TUPLE [NATURAL_32]]) is
			-- Apply `a_action' to all codepoints.
			-- `a_action' must be precondition-free.
		require
			a_action_not_void: a_action /= Void
		local
			l_tuple: TUPLE [NATURAL_32]
			i: INTEGER
		do
			if count > 0 then
				from
					create l_tuple
					i := lower
				until
					i > upper
				loop
					l_tuple.put (area.item (i - 1), 1)
					a_action.call (l_tuple)
					i := i + 1
				end
			end
		end

	do_all_with_index, do_forward_with_index (a_action: PROCEDURE [ANY, TUPLE [NATURAL_32, INTEGER]]) is
			-- Apply `a_action' to all codepoints, passing index relative to `lower'.
			-- `a_action' must be precondition-free.
		require
			a_action_not_void: a_action /= Void		
		local
			l_tuple: TUPLE [NATURAL_32, INTEGER]
			i: INTEGER
		do
			if count > 0 then
				from
					create l_tuple
					i := lower
				until
					i > upper
				loop
					l_tuple.put (area.item (i - 1), 1)
					l_tuple.put (i - lower + 1, 2)
					a_action.call (l_tuple)
					i := i + 1
				end
			end
		end

feature -- Conversion

	to_string: STRING_32 is
			-- `Current' converted to STRING_32
		do
			create Result.make_filled (('%/0/').to_character_32, count)
			do_all_with_index (agent Result.put_code)
		ensure
			to_string_not_void: Result /= Void
			correct_count: count = Result.count
			correct_character_sequence: for_all_with_index (agent same_item_as_string (Result, ?, ?))
		end
	
	to_latin_1_string: STRING_8 is
			-- `Current' converted to ISO-8859-1
		require
			latin_1_string: is_valid_as_string_8
		do
			create Result.make_filled ('%/0/', count)
			do_all_with_index (agent Result.put_code)
		end

	to_utf8_string: UC_UTF8_STRING is
			-- `Current' converted to UTF-8
		do
			create Result.make_from_string_general (Current)
		ensure
			to_utf_8_not_void: Result /= Void
			correct_count: count = Result.count
			correct_character_sequence: for_all_with_index (agent same_item_as_string (Result, ?, ?))
		end
	
	to_natural, to_natural_32: NATURAL_32 is
			-- 32-bit natural value
		require
			is_natural: is_natural_32
		local
			l_convertor: like ctoi_convertor
		do
			l_convertor := ctoi_convertor
			l_convertor.parse_string_with_type (Current, {NUMERIC_INFORMATION}.type_no_limitation)
			Result := l_convertor.parsed_natural_32
		end
	
	to_integer, to_integer_32: INTEGER_32 is
			-- 32-bit signed integer value
		require
			is_integer: is_integer_32
		local
			l_convertor: like ctoi_convertor
		do
			l_convertor := ctoi_convertor
			l_convertor.parse_string_with_type (Current, {NUMERIC_INFORMATION}.type_no_limitation)
			Result := l_convertor.parsed_integer
		end
	
	to_integer_64: INTEGER_64 is
			-- 64-bit integer value
		require
			is_integer_64: is_integer_64
		local
			l_convertor: like ctoi_convertor
		do
			l_convertor := ctoi_convertor
			l_convertor.parse_string_with_type (Current, {NUMERIC_INFORMATION}.type_no_limitation)
			Result := l_convertor.parsed_integer_64
		end
	
	to_double: DOUBLE is
			-- "Double" value;
			-- for example, when applied to "123.0", will yield 123.0 (double)
		require
			represents_a_double: is_double
		local
			l_convertor: like ctor_convertor
		do
			l_convertor := ctor_convertor
			l_convertor.parse_string_with_type (Current, {NUMERIC_INFORMATION}.type_no_limitation)
			Result := l_convertor.parsed_double
		end
	
	to_real: REAL is
			-- Real value;
			-- for example, when applied to "123.0", will yield 123.0
		require
			represents_a_real: is_real
		do
			Result := to_double
		end
	
	to_lower: ST_STRING is
			-- New lower-case version of `Current'
		do
			Result := case_mapping.lower_string (Current)
		ensure
			to_lower_not_void: Result /= Void
		end

	to_upper: ST_STRING is
			-- New upper-case version of `Current'
		do
			Result := case_mapping.upper_string (Current)
		ensure
			to_upper_not_void: Result /= Void
		end
	
	as_nfd: ST_STRING is
			-- Canonical decomposition of `Current';
		do
			Result := normalization.as_nfd_st_string (Current)
		ensure
			as_nfd_not_void: Result /= Void
			is_nfd: Result.is_nfd
		end

	to_nfd: ST_STRING is
			-- Canonical decomposition of `Current';
		do
			Result := normalization.to_nfd_st_string (Current)
		ensure
			as_nfd_not_void: Result /= Void
			is_nfd: Result.is_nfd
			new_object: Result /= Current
		end
	
	as_nfkd: ST_STRING is
			-- Compatibility decomposition of `Current';
		do
			Result := normalization.as_nfkd_st_string (Current)
		ensure
			as_nfkd_not_void: Result /= Void
			is_nfkd: Result.is_nfkd
		end

	to_nfkd: ST_STRING is
			-- Compatibility decomposition of `Current';
		do
			Result := normalization.to_nfkd_st_string (Current)
		ensure
			as_nfkd_not_void: Result /= Void
			is_nfkd: Result.is_nfkd
			new_object: Result /= Current
		end		

	to_nfc: ST_STRING is
			-- Canonical decomposition then canonical composition of `Current';
		do
			Result := normalization.to_nfc_st_string (Current)
		ensure
			as_nfc_not_void: Result /= Void
			is_nfc: Result.is_nfc
			new_object: Result /= Current
		end

	to_nfkc: ST_STRING is
			-- Compatibility decomposition then canonical composition of `Current';
		do
			Result := normalization.to_nfkc_st_string (Current)
		ensure
			as_nfkc_not_void: Result /= Void
			is_nfkc: Result.is_nfkc
			new_object: Result /= Current
		end		
	
feature -- Duplication

	substring (a_start, a_end: INTEGER): ST_STRING is
			-- Shared-memory substring containing all characters at indices between `a_start' and `a_end';
			-- Memory is not shared if `Result' is empty string.
		do
			if a_end < a_start then
				Result := shared_empty_string
			else
				create Result.make_shared (area, a_start + lower - 1, a_end + lower - 1)
			end
		ensure then
			empty_string: a_end < a_start implies Result.is_empty
			lower: not Result.is_empty implies Result.lower = lower + a_start - 1
			upper: not Result.is_empty implies Result.upper = a_end +lower - 1
			same_area: not Result.is_empty implies Result.area = area
			substring_not_void: Result /= Void
		end

feature {NONE} -- Contract support

	same_item_as_string (a_string: READABLE_STRING_GENERAL; a_code: NATURAL_32; a_index: INTEGER): BOOLEAN is
			-- Does `a_code' equal `a_string.code (a_index)'?
		require
			a_string_not_void: a_string /= Void
			a_index_valid: valid_index (a_index)
		do
			Result := a_code = a_string.code (a_index)
		ensure
			definition: Result = (a_code = a_string.code (a_index))
		end

	same_item_as_array (a_array: ARRAY [NATURAL_32]; a_code: NATURAL_32; a_index: INTEGER): BOOLEAN is
			-- Does `a_code' equal `a_array.item (a_index)'?
		require
			a_array_not_void: a_array /= Void
			a_index_valid: valid_index (a_index)
		do
			Result := a_code = a_array.item (a_index)
		ensure
			definition: Result = (a_code = a_array.item (a_index))
		end

feature {NONE} -- Implementation

	leading_string_comparison (a_other: READABLE_STRING_GENERAL; a_count: INTEGER): INTEGER is
			-- Comparison of first `a_count' characters of `Current' with `a_other'
			-- 0 if equal, < 0 if `Current' < `a_other',
			-- Otherwise > 0
		require
			a_other_not_void: a_other /= Void
			valid_count: a_count <= count and a_count <= a_other.count
		local
			i: INTEGER
			l_code, l_other_code: NATURAL_32
		do
			from
				i := 1
			until
				i > a_count
			loop
				l_code := code (i)
				l_other_code := a_other.code (i)
				if l_code /= l_other_code then
					Result := (l_code.to_integer_32 - l_other_code.to_integer_32)
					i := a_count -- Jump out of loop
				end
				i := i + 1
			end
		end
	
	internal_split (a_separators: READABLE_STRING_GENERAL; a_maximal: BOOLEAN): DS_LINKED_LIST [ST_STRING] is
		require
			a_separators_not_void: a_separators /= Void
		local
			i, l_count, l_last: INTEGER
			l_last_separator: INTEGER
			l_code: NATURAL_32 
			l_item: ST_STRING			
		do
			create Result.make
			l_count := count
			if l_count > 0 then
				from
					i := 1
					l_last_separator := 0
					l_last := 1
				invariant
					last_separator: l_last_separator < i
					last_split: l_last <= i
				until
					i > l_count
				loop
					l_code := code (i)
					if a_separators.has_code (l_code) then
						if l_last_separator = i - 1 then
							if a_maximal then
								Result.force_last (shared_empty_string)
							end
						else
							if l_item = Void then
								l_item := substring (l_last, i - 1)
							else
								l_item := l_item.concatenated_before (substring (l_last, i - 1))
							end
							Result.force_last (l_item)
						end
						l_item := Void
						l_last_separator := i
						l_last := i + 1
					end
					i := i + 1
				end
				if l_last_separator = 0 then
					if l_item = Void then
						l_item := Current
					else
						l_item := l_item.concatenated_before (substring (l_last, l_count))
					end
					Result.force_last (l_item)
				elseif l_last_separator < l_count then
					if l_item = Void then
						l_item := substring (l_last, l_count)
					else
						l_item := l_item.concatenated_before (substring (l_last, l_count))
					end
					Result.force_last (l_item)
				elseif a_maximal then
					Result.force_last (shared_empty_string)
				end				
			end
		ensure
			iternal_split_not_void: Result /= Void
			no_void_item: not Result.has (Void)
		end
	
	is_valid_integer_or_natural (type: INTEGER): BOOLEAN is
			-- Is `Current' a valid number according to given `type'?
		local
			l_convertor: like ctoi_convertor
		do
			if is_valid_as_string_8 then
				l_convertor := ctoi_convertor
				l_convertor.reset (type)
				l_convertor.parse_string_with_type (Current, type)
				Result := l_convertor.is_integral_integer
			end
		end

feature {NONE} -- Agents

	is_same_code (a_other: READABLE_STRING_GENERAL; a_code: NATURAL_32; a_index: INTEGER): BOOLEAN is
			-- Does `a_other.code (a_index)' = `a_code'?
		require
			a_other_not_void: a_other /= Void
			a_index_valid: valid_index (a_index)
		do
			Result := a_other.code (a_index) = a_code
		ensure
			definition: Result = (a_other.code (a_index) = a_code)
		end

	is_character_8 (a_code: NATURAL_32): BOOLEAN is
			-- Is `a_code' in range for for CHARACTER_8?
		do
			Result := a_code < 256
		ensure
			definition: Result = (a_code < 256)
		end

invariant

	area_not_void: area /= Void
	all_code_points_valid: for_all (agent valid_code)
	count_is_not_greater_than_area_count: count <= area.count
	lower_large_enough: lower >= 0
	lower_small_enough: lower <= area.count + 1
	upper_large_enough: upper >= 0
	upper_not_larger_than_lower: upper >= lower

end

-- 
Colin Adams
Preston Lancashire

-------------------------------------------------------------------------
This SF.Net email is sponsored by the Moblin Your Move Developer's challenge
Build the coolest Linux based applications with Moblin SDK & win great prizes
Grand prize is a trip for two to an Open Source event anywhere in the world
http://moblin-contest.org/redirect.php?banner_id=100&url=/