class EL_COMPARABLE_ZSTRING

(source code)

description

Comparison of current EL_READABLE_ZSTRING with other strings

note
	description: "Comparison of current ${EL_READABLE_ZSTRING} with other strings"

	author: "Finnian Reilly"
	copyright: "Copyright (c) 2001-2022 Finnian Reilly"
	contact: "finnian at eiffel hyphen loop dot com"

	license: "MIT license (See: en.wikipedia.org/wiki/MIT_License)"
	date: "2024-08-25 7:44:30 GMT (Sunday 25th August 2024)"
	revision: "48"

deferred class
	EL_COMPARABLE_ZSTRING

inherit
	EL_ZSTRING_IMPLEMENTATION

feature -- Status query

	matches (a_pattern: EL_TEXT_PATTERN_I): BOOLEAN
		do
			Result := a_pattern.matches_string_general (current_readable)
		end

feature -- Start/End comparisons

	begins_with (str: READABLE_STRING_GENERAL): BOOLEAN
		-- True if left-adjusted string begins with `str'
		local
			white_count: INTEGER
		do
			if str.count = 0 then
				Result := True
			else
				white_count := leading_white_space
				if count - white_count >= str.count then
					Result := same_characters_general (str, 1, str.count, white_count + 1)
				end
			end
		end

	enclosed_with (character_pair: READABLE_STRING_GENERAL): BOOLEAN
		require
			is_pair: character_pair.count = 2
		do
			if count >= 2 and then character_pair.count = 2 then
				Result := item (1) = character_pair [1] and then item (count) = character_pair [2]
			end
		end

	ends_with (other: READABLE_STRING_32): BOOLEAN
		local
			other_count: INTEGER
		do
			other_count := other.count
			if other_count = 0 then
				Result := True

			elseif other.count > count then
				do_nothing

			elseif same_type (other) then
				if attached {ZSTRING} other as z_str then
					Result := same_characters_zstring (z_str, 1, other.count, count - other_count + 1)
				end

			else
				Result := same_characters_32 (other, 1, other_count, count - other_count + 1, False)
			end
		end

	ends_with_general (other: READABLE_STRING_GENERAL): BOOLEAN
		local
			other_count: INTEGER
		do
			other_count := other.count
			if other.is_string_8 and then attached {READABLE_STRING_8} other as str_8 then
				inspect other_count
					when 0 then
						Result := True
				else
					Result := same_characters_8 (str_8, 1, other_count, count - other_count + 1, False)
				end

			elseif same_type (other) and then attached {EL_READABLE_ZSTRING} other as z_str then
				Result := same_characters_zstring (z_str, 1, other_count, count - other_count + 1)

			elseif attached {READABLE_STRING_32} other as str_32 then

				Result := same_characters_32 (str_32, 1, other_count, count - other_count + 1, False)
			end
		end

	matches_wildcard (wildcard: READABLE_STRING_GENERAL): BOOLEAN
		local
			any_ending, any_start: BOOLEAN; start_index, end_index: INTEGER
			search_string: READABLE_STRING_GENERAL
		do
			start_index := 1; end_index := wildcard.count
			inspect wildcard.count
				when 0 then
				when 1 then
					if wildcard [1].code = {ASCII}.Star then
						any_ending := True; any_start := True
					end
			else
				if wildcard.count > 0 and then wildcard [end_index] = '*' then
					end_index := end_index - 1
					any_ending := True
				end
				if wildcard.count > 0 and then wildcard [1] = '*' then
					start_index := start_index + 1
					any_start := True
				end
			end
			if start_index - end_index + 1 = wildcard.count then
				search_string := wildcard
			else
				search_string := wildcard.substring (start_index, end_index)
			end

			if any_ending and any_start then
				if wildcard.count = 1 then
					Result := True
				else
					Result := has_substring (search_string)
				end

			elseif any_ending then
				Result := starts_with_general (search_string)

			elseif any_start then
				Result := ends_with_general (search_string)
			else
				Result := count = end_index and then same_characters_general (wildcard, 1, end_index, 1)
			end
		end

	starts_with (other: READABLE_STRING_32): BOOLEAN
		local
			other_count: INTEGER
		do
			other_count := other.count
			if other_count = 0 then
				Result := True

			elseif other_count > count then
				do_nothing

			elseif same_type (other) and then attached {ZSTRING} other as z_str then
				Result := same_characters_zstring (z_str, 1, other.count, 1)

			else
				Result := same_characters_32 (other, 1, other_count, 1, False)
			end
		end

	starts_with_general (other: READABLE_STRING_GENERAL): BOOLEAN
		do
			if other.is_string_8 and then attached {READABLE_STRING_8} other as str_8 then
				inspect other.count
					when 0 then
						Result := True
				else
					Result := same_characters_8 (str_8, 1, other.count, 1, False)
				end

			elseif same_type (other) then
				if attached {ZSTRING} other as z_str then
					Result := same_characters_zstring (z_str, 1, other.count, 1)
				end

			elseif attached {READABLE_STRING_32} other as str_32 then

				Result := same_characters_32 (str_32, 1, other.count, 1, False)
			end
		end

feature -- Comparison

	is_less alias "<" (other: like Current): BOOLEAN
			-- Is string lexicographically lower than `other'?
		local
			o_count, l_count: INTEGER
		do
			if other /= Current then
				o_count := other.count; l_count := count
				if o_count = l_count then
					Result := order_comparison (other, 1, 1, o_count) > 0
				else
					if l_count < o_count then
						Result := order_comparison (other, 1, 1, l_count) >= 0
					else
						Result := order_comparison (other, 1, 1, o_count) > 0
					end
				end
			end
		end

	same_caseless_characters (other: READABLE_STRING_32; start_pos, end_pos, start_index: INTEGER): BOOLEAN
		-- Are characters of `other' within bounds `start_pos' and `end_pos'
		-- caseless identical to characters of current string starting at index `start_index'.
		do
			if same_type (other) and then attached {ZSTRING} other as z_str then
				Result := same_caseless_characters_zstring (z_str, start_pos, end_pos, start_index)
			else
				Result := same_characters_32 (other, start_pos, end_pos, start_index, True)
			end
		end

	same_caseless_characters_general (other: READABLE_STRING_GENERAL; start_pos, end_pos, start_index: INTEGER): BOOLEAN
		do
			inspect string_storage_type (other)
				when '1' then
					if attached {READABLE_STRING_8} other as str_8 then
						Result := same_characters_8 (str_8, start_pos, end_pos, start_index, True)
					end
				when '4' then
					if attached {READABLE_STRING_32} other as str_32 then
						Result := same_characters_32 (str_32, start_pos, end_pos, start_index, True)
					end
				when 'X' then
					if attached {ZSTRING} other as z_str then
						Result := same_caseless_characters_zstring (z_str, start_pos, end_pos, start_index)
					end
			end
		end

	same_characters (other: READABLE_STRING_32; start_pos, end_pos, start_index: INTEGER): BOOLEAN
			-- Are characters of `other' within bounds `start_pos' and `end_pos'
			-- identical to characters of current string starting at index `start_index'.
		do
			if same_type (other) and then attached {ZSTRING} other as z_str then
				Result := same_characters_zstring (z_str, start_pos, end_pos, start_index)
			else
				Result := same_characters_32 (other, start_pos, end_pos, start_index, False)
			end
		end

	same_characters_general (other: READABLE_STRING_GENERAL; start_pos, end_pos, start_index: INTEGER): BOOLEAN
			-- Are characters of `other' within bounds `start_pos' and `end_pos'
			-- identical to characters of current string starting at index `start_index'.
		do
			inspect string_storage_type (other)
				when '1' then
					if attached {READABLE_STRING_8} other as str_8 then
						Result := same_characters_8 (str_8, start_pos, end_pos, start_index, False)
					end
				when '4' then
					if attached {READABLE_STRING_32} other as str_32 then
						Result := same_characters_32 (str_32, start_pos, end_pos, start_index, False)
					end
				when 'X' then
					if attached {ZSTRING} other as z_str then
						Result := same_characters_zstring (z_str, start_pos, end_pos, start_index)
					end
			end
		end

	same_substring (str: READABLE_STRING_GENERAL; i: INTEGER; case_insensitive: BOOLEAN): BOOLEAN
		-- `True' if `str' occurs at position `i' with optional `case_insensitive' match
		do
			if str.count = 0 then
				Result := True
			else
				if case_insensitive then
					Result := same_caseless_characters_general (str, 1, str.count, i)
				else
					Result := same_characters_general (str, 1, str.count, i)
				end
			end
		end

feature {NONE} -- Implementation

	order_comparison (other: EL_COMPARABLE_ZSTRING; start_index, other_start_index, n: INTEGER): INTEGER
			-- Compare `n' characters from `other' starting at `other_start_index' with
			-- `n' characters from `Current' starting as `start_index'
			-- 0 if equal, < 0 if `Current' < `other', > 0 if `Current' > `other'
		require
			n_non_negative: n >= 0
			valid_start_index: valid_index (start_index) and valid_index (other_start_index)
			valid_n_count: valid_index (start_index + n - 1) and valid_index (other_start_index + n - 1)
		local
			i, j, i_final, block_index, other_block_index: INTEGER; found: BOOLEAN
			uc_i, o_uc_i: CHARACTER_32; l_code, o_code: NATURAL; c_i, o_i: CHARACTER
			o_unencoded, unencoded: like unencoded_area; o_area, l_area: like area
			iter: EL_COMPACT_SUBSTRINGS_32_ITERATION
		do
			l_area := area; o_area := other.area
			unencoded := unencoded_area; o_unencoded := other.unencoded_area
			i_final := area_lower + other_start_index + n - 1
			j := other.area_lower + other_start_index - 1
			from i := area_lower + start_index - 1 until found or else i = i_final loop
				c_i := l_area [i]; o_i := o_area [j]
				inspect current_other_bitmap (c_i = Substitute, o_i = Substitute)
					when Both_have_mixed_encoding then
						uc_i := iter.item ($block_index, unencoded, i + 1)
						o_uc_i := iter.item ($other_block_index, o_unencoded, j + 1)
						if uc_i /= o_uc_i then
							l_code := unicode_to_z_code (uc_i.natural_32_code)
							o_code := unicode_to_z_code (o_uc_i.natural_32_code)
							found := True
						end
					when Only_current then
						uc_i := iter.item ($block_index, unencoded, i + 1)
						l_code := unicode_to_z_code (uc_i.natural_32_code); o_code := o_i.natural_32_code
						found := True

					when Only_other then
						o_uc_i := iter.item ($other_block_index, o_unencoded, j + 1)
						l_code := c_i.natural_32_code; o_code := unicode_to_z_code (o_uc_i.natural_32_code)
						found := True

					when Neither then
						if c_i /= o_i then
							l_code := c_i.natural_32_code; o_code := o_i.natural_32_code
							found := True
						end
				else
				end
				i := i + 1; j := j + 1
			end
			if found then
				Result := Codec.order_comparison (l_code, o_code)
			end
		end

	same_caseless_characters_zstring (other: EL_READABLE_ZSTRING; start_pos, end_pos, start_index: INTEGER): BOOLEAN
		-- Are characters of `other' within bounds `start_pos' and `end_pos'
		-- caselessly matching characters of current string starting at index `start_index'
		local
			l_count: INTEGER
		do
			l_count := end_pos - start_pos + 1
			if start_index + l_count - 1 <= count
				and then codec.same_caseless_characters (area, other.area, start_pos - start_index, start_index - 1, l_count)
			then
				Result := same_unencoded_characters (
					other.unencoded_area, start_index, start_index + l_count - 1, start_pos - start_index, True
				)
			end
		end

	same_characters_8 (
		other: READABLE_STRING_8; start_pos, end_pos, start_index: INTEGER; case_insensitive: BOOLEAN
	): BOOLEAN
			-- Are characters of `other' within bounds `start_pos' and `end_pos'
			-- identical to characters of current string starting at index `start_index'.
		local
			end_index: INTEGER
		do
			end_index := start_index + end_pos - start_pos
			if end_index <= count and then
				attached shared_comparator_string_8 (start_index, end_index, case_insensitive) as list
			then
				list.set_other_area (cursor_8 (other))
				Result := list.same_characters (area, start_pos - start_index)
			end
		end

	same_characters_32 (
		other: READABLE_STRING_32; start_pos, end_pos, start_index: INTEGER; case_insensitive: BOOLEAN
	): BOOLEAN
			-- Are characters of `other' within bounds `start_pos' and `end_pos'
			-- identical to characters of current string starting at index `start_index'.
		local
			end_index: INTEGER
		do
			end_index := start_index + end_pos - start_pos
			if end_index <= count and then
				attached shared_comparator_string_32 (start_index, end_index, case_insensitive) as list
			then
				list.set_other_area (cursor_32 (other))
				Result := list.same_characters (area, start_pos - start_index)
			end
		end

	same_characters_zstring (other: EL_READABLE_ZSTRING; start_pos, end_pos, start_index: INTEGER): BOOLEAN
		-- Are characters of `other' within bounds `start_pos' and `end_pos'
		-- the same characters of current string starting at index `start_index'
		local
			end_index: INTEGER
		do
			end_index := start_index + end_pos - start_pos
			if end_index <= count and then internal_same_characters (other, start_pos, end_pos, start_index) then
				Result := same_unencoded_characters (
					other.unencoded_area, start_index, end_index, start_pos - start_index, False
				)
			end
		end

	shared_comparator_string_8 (
		start_index, end_index: INTEGER; case_insensitive: BOOLEAN
	): EL_COMPARE_ZSTRING_TO_STRING_8
		do
			if case_insensitive then
				Result := Caseless_comparator_string_8
			else
				Result := Comparator_string_8
			end
			Result.set (unencoded_area, start_index, end_index)
		end

	shared_comparator_string_32 (
		start_index, end_index: INTEGER; case_insensitive: BOOLEAN
	): EL_COMPARE_ZSTRING_TO_STRING_32
		do
			if case_insensitive then
				Result := Caseless_comparator_string_32
			else
				Result := Comparator_string_32
			end
			Result.set (unencoded_area, start_index, end_index)
		end

feature {NONE} -- Constants

	Comparator_string_32: EL_COMPARE_ZSTRING_TO_STRING_32
		once
			create Result.make
		end

	Comparator_string_8: EL_COMPARE_ZSTRING_TO_STRING_8
		once
			create Result.make
		end

	Caseless_comparator_string_32: EL_CASELESS_COMPARE_ZSTRING_TO_STRING_32
		once
			create Result.make
		end

	Caseless_comparator_string_8: EL_CASELESS_COMPARE_ZSTRING_TO_STRING_8
		once
			create Result.make
		end

end