+ # Equality of text
+ # Two pieces of text are equals if thez have the same characters in the same order.
+ #
+ # assert "hello" == "hello"
+ # assert "hello" != "HELLO"
+ # assert "hello" == "hel"+"lo"
+ #
+ # Things that are not Text are not equal.
+ #
+ # assert "9" != '9'
+ # assert "9" != ['9']
+ # assert "9" != 9
+ #
+ # assert "9".chars.first == '9' # equality of Char
+ # assert "9".chars == ['9'] # equality of Sequence
+ # assert "9".to_i == 9 # equality of Int
+ redef fun ==(o)
+ do
+ if o == null then return false
+ if not o isa Text then return false
+ if self.is_same_instance(o) then return true
+ if self.length != o.length then return false
+ return self.chars == o.chars
+ end
+
+ # Lexicographical comparaison
+ #
+ # assert "abc" < "xy"
+ # assert "ABC" < "abc"
+ redef fun <(other)
+ do
+ var self_chars = self.chars.iterator
+ var other_chars = other.chars.iterator
+
+ while self_chars.is_ok and other_chars.is_ok do
+ if self_chars.item < other_chars.item then return true
+ if self_chars.item > other_chars.item then return false
+ self_chars.next
+ other_chars.next
+ end
+
+ if self_chars.is_ok then
+ return false
+ else
+ return true
+ end
+ end
+
+ # Flat representation of self
+ fun flatten: FlatText is abstract
+
+ private var hash_cache: nullable Int = null
+
+ redef fun hash
+ do
+ if hash_cache == null then
+ # djb2 hash algorithm
+ var h = 5381
+
+ for char in self.chars do
+ h = h.lshift(5) + h + char.ascii
+ end
+
+ hash_cache = h
+ end
+ return hash_cache.as(not null)
+ end
+
+end
+
+# All kinds of array-based text representations.
+abstract class FlatText
+ super Text
+
+ private var items: NativeString
+
+ # Real items, used as cache for to_cstring is called
+ private var real_items: nullable NativeString = null
+
+ redef var length: Int = 0
+
+ init do end
+
+ redef fun output
+ do
+ var i = 0
+ while i < length do
+ items[i].output
+ i += 1
+ end
+ end
+
+ redef fun flatten do return self
+end
+
+# Abstract class for the SequenceRead compatible
+# views on String and Buffer objects
+private abstract class StringCharView
+ super SequenceRead[Char]
+
+ type SELFTYPE: Text
+
+ private var target: SELFTYPE
+
+ private init(tgt: SELFTYPE)
+ do
+ target = tgt
+ end
+
+ redef fun is_empty do return target.is_empty
+
+ redef fun length do return target.length
+
+ redef fun iterator: IndexedIterator[Char] do return self.iterator_from(0)
+
+ redef fun reverse_iterator do return self.reverse_iterator_from(self.length - 1)
+end
+
+# View on Buffer objects, extends Sequence
+# for mutation operations
+private abstract class BufferCharView
+ super StringCharView
+ super Sequence[Char]
+
+ redef type SELFTYPE: Buffer
+
+end
+
+abstract class String
+ super Text
+
+ redef type SELFTYPE: String
+
+ redef fun to_s do return self
+end
+
+private class FlatSubstringsIter
+ super Iterator[FlatText]
+
+ var tgt: nullable FlatText
+
+ init(tgt: FlatText) do self.tgt = tgt
+
+ redef fun item do
+ assert is_ok
+ return tgt.as(not null)
+ end
+
+ redef fun is_ok do return tgt != null
+
+ redef fun next do tgt = null
+end
+
+# Immutable strings of characters.
+class FlatString
+ super FlatText
+ super String
+
+ # Index in _items of the start of the string
+ private var index_from: Int
+
+ # Indes in _items of the last item of the string
+ private var index_to: Int
+
+ redef var chars: SequenceRead[Char] = new FlatStringCharView(self)
+
+ ################################################
+ # AbstractString specific methods #
+ ################################################
+
+ redef fun reversed
+ do
+ var native = calloc_string(self.length + 1)
+ var length = self.length
+ var items = self.items
+ var pos = 0
+ var ipos = length-1
+ while pos < length do
+ native[pos] = items[ipos]
+ pos += 1
+ ipos -= 1
+ end
+ return native.to_s_with_length(self.length)
+ end
+
+ redef fun substring(from, count)
+ do
+ assert count >= 0
+
+ if from < 0 then
+ count += from
+ if count < 0 then count = 0
+ from = 0
+ end
+
+ var realFrom = index_from + from
+
+ if (realFrom + count) > index_to then return new FlatString.with_infos(items, index_to - realFrom + 1, realFrom, index_to)
+
+ if count == 0 then return empty
+
+ var to = realFrom + count - 1
+
+ return new FlatString.with_infos(items, to - realFrom + 1, realFrom, to)
+ end
+
+ redef fun empty do return "".as(FlatString)
+
+ redef fun to_upper
+ do
+ var outstr = calloc_string(self.length + 1)
+ var out_index = 0
+
+ var myitems = self.items
+ var index_from = self.index_from
+ var max = self.index_to
+
+ while index_from <= max do
+ outstr[out_index] = myitems[index_from].to_upper
+ out_index += 1
+ index_from += 1
+ end
+
+ outstr[self.length] = '\0'
+
+ return outstr.to_s_with_length(self.length)
+ end
+
+ redef fun to_lower
+ do
+ var outstr = calloc_string(self.length + 1)
+ var out_index = 0
+
+ var myitems = self.items
+ var index_from = self.index_from
+ var max = self.index_to
+
+ while index_from <= max do
+ outstr[out_index] = myitems[index_from].to_lower
+ out_index += 1
+ index_from += 1
+ end
+
+ outstr[self.length] = '\0'
+
+ return outstr.to_s_with_length(self.length)
+ end
+
+ redef fun output
+ do
+ var i = self.index_from
+ var imax = self.index_to
+ while i <= imax do
+ items[i].output
+ i += 1
+ end
+ end
+
+ ##################################################
+ # String Specific Methods #
+ ##################################################
+
+ private init with_infos(items: NativeString, len: Int, from: Int, to: Int)
+ do
+ self.items = items
+ length = len
+ index_from = from
+ index_to = to
+ end
+
+ redef fun to_cstring: NativeString
+ do
+ if real_items != null then return real_items.as(not null)
+ if index_from > 0 or index_to != items.cstring_length - 1 then
+ var newItems = calloc_string(length + 1)
+ self.items.copy_to(newItems, length, index_from, 0)
+ newItems[length] = '\0'
+ self.real_items = newItems
+ return newItems
+ end
+ return items
+ end
+
+ redef fun ==(other)
+ do
+ if not other isa FlatString then return super
+
+ if self.object_id == other.object_id then return true
+
+ var my_length = length
+
+ if other.length != my_length then return false
+
+ var my_index = index_from
+ var its_index = other.index_from
+
+ var last_iteration = my_index + my_length
+
+ var itsitems = other.items
+ var myitems = self.items
+
+ while my_index < last_iteration do
+ if myitems[my_index] != itsitems[its_index] then return false
+ my_index += 1
+ its_index += 1
+ end
+
+ return true
+ end
+
+ redef fun <(other)
+ do
+ if not other isa FlatString then return super
+
+ if self.object_id == other.object_id then return false
+
+ var my_curr_char : Char
+ var its_curr_char : Char
+
+ var curr_id_self = self.index_from
+ var curr_id_other = other.index_from
+
+ var my_items = self.items
+ var its_items = other.items
+
+ var my_length = self.length
+ var its_length = other.length
+
+ var max_iterations = curr_id_self + my_length
+
+ while curr_id_self < max_iterations do
+ my_curr_char = my_items[curr_id_self]
+ its_curr_char = its_items[curr_id_other]
+
+ if my_curr_char != its_curr_char then
+ if my_curr_char < its_curr_char then return true
+ return false
+ end
+
+ curr_id_self += 1
+ curr_id_other += 1
+ end
+
+ return my_length < its_length
+ end
+
+ redef fun +(s)
+ do
+ var my_length = self.length
+ var its_length = s.length
+
+ var total_length = my_length + its_length
+
+ var target_string = calloc_string(my_length + its_length + 1)
+
+ self.items.copy_to(target_string, my_length, index_from, 0)
+ if s isa FlatString then
+ s.items.copy_to(target_string, its_length, s.index_from, my_length)
+ else if s isa FlatBuffer then
+ s.items.copy_to(target_string, its_length, 0, my_length)
+ else
+ var curr_pos = my_length
+ for i in s.chars do
+ target_string[curr_pos] = i
+ curr_pos += 1
+ end
+ end
+
+ target_string[total_length] = '\0'
+
+ return target_string.to_s_with_length(total_length)
+ end
+
+ redef fun *(i)
+ do
+ assert i >= 0
+
+ var my_length = self.length
+
+ var final_length = my_length * i
+
+ var my_items = self.items
+
+ var target_string = calloc_string((final_length) + 1)
+
+ target_string[final_length] = '\0'
+
+ var current_last = 0
+
+ for iteration in [1 .. i] do
+ my_items.copy_to(target_string, my_length, 0, current_last)
+ current_last += my_length
+ end
+
+ return target_string.to_s_with_length(final_length)
+ end
+
+ redef fun hash
+ do
+ if hash_cache == null then
+ # djb2 hash algorithm
+ var h = 5381
+ var i = index_from
+
+ var myitems = items
+
+ while i <= index_to do
+ h = h.lshift(5) + h + myitems[i].ascii
+ i += 1
+ end
+
+ hash_cache = h
+ end
+
+ return hash_cache.as(not null)
+ end
+
+ redef fun substrings do return new FlatSubstringsIter(self)
+end
+
+private class FlatStringReverseIterator
+ super IndexedIterator[Char]
+
+ var target: FlatString
+
+ var target_items: NativeString
+
+ var curr_pos: Int
+
+ init with_pos(tgt: FlatString, pos: Int)
+ do
+ target = tgt
+ target_items = tgt.items
+ curr_pos = pos + tgt.index_from
+ end
+
+ redef fun is_ok do return curr_pos >= 0
+
+ redef fun item do return target_items[curr_pos]
+
+ redef fun next do curr_pos -= 1
+
+ redef fun index do return curr_pos - target.index_from
+
+end
+
+private class FlatStringIterator
+ super IndexedIterator[Char]
+
+ var target: FlatString
+
+ var target_items: NativeString
+
+ var curr_pos: Int
+
+ init with_pos(tgt: FlatString, pos: Int)
+ do
+ target = tgt
+ target_items = tgt.items
+ curr_pos = pos + target.index_from
+ end
+
+ redef fun is_ok do return curr_pos <= target.index_to
+
+ redef fun item do return target_items[curr_pos]
+
+ redef fun next do curr_pos += 1
+
+ redef fun index do return curr_pos - target.index_from
+
+end
+
+private class FlatStringCharView
+ super StringCharView
+
+ redef type SELFTYPE: FlatString
+
+ redef fun [](index)