Merge: Use prefixed chars instead of raw values
[nit.git] / lib / core / text / flat.nit
index 4d35126..06cdcfe 100644 (file)
@@ -36,29 +36,49 @@ end
 
 redef class FlatText
 
-       fun first_byte: Int do return 0
+       # First byte of the CString
+       protected fun first_byte: Int do return 0
 
-       fun last_byte: Int do return _bytelen - 1
+       # Last byte of the CString
+       protected fun last_byte: Int do return first_byte + _byte_length - 1
 
        # Cache of the latest position (char) explored in the string
        var position: Int = 0
 
-       # Cached position (bytes) in the NativeString underlying the String
+       # Cached position (bytes) in the CString underlying the String
        var bytepos: Int = 0
 
        # Index of the character `index` in `_items`
        fun char_to_byte_index(index: Int): Int do
-               var ln = length
-               assert index >= 0
-               assert index < ln
+               var dpos = index - _position
+               var b = _bytepos
+               var its = _items
+
+               if dpos == 1 then
+                       if its[b] & 0x80u8 == 0x00u8 then
+                               b += 1
+                       else
+                               b += its.length_of_char_at(b)
+                       end
+                       _bytepos = b
+                       _position = index
+                       return b
+               end
+               if dpos == -1 then
+                       b = its.find_beginning_of_char_at(b - 1)
+                       _bytepos = b
+                       _position = index
+                       return b
+               end
+               if dpos == 0 then return b
 
+               var ln = _length
                var pos = _position
                # Find best insertion point
                var delta_begin = index
                var delta_end = (ln - 1) - index
                var delta_cache = (pos - index).abs
                var min = delta_begin
-               var its = _items
 
                if delta_cache < min then min = delta_cache
                if delta_end < min then min = delta_end
@@ -66,15 +86,15 @@ redef class FlatText
                var ns_i: Int
                var my_i: Int
 
-               if min == delta_begin then
-                       ns_i = first_byte
-                       my_i = 0
-               else if min == delta_cache then
+               if min == delta_cache then
                        ns_i = _bytepos
                        my_i = pos
+               else if min == delta_begin then
+                       ns_i = first_byte
+                       my_i = 0
                else
                        ns_i = its.find_beginning_of_char_at(last_byte)
-                       my_i = length - 1
+                       my_i = _length - 1
                end
 
                ns_i = its.char_to_byte_index_cached(index, my_i, ns_i)
@@ -93,15 +113,15 @@ redef class FlatText
                var endlen = 0
                while pos <= max do
                        var c = its[pos]
-                       if c == 0x3Cu8 then
+                       if c == b'<' then
                                endlen += 3
-                       else if c == 0x3Eu8 then
+                       else if c == b'>' then
                                endlen += 3
-                       else if c == 0x26u8 then
+                       else if c == b'&' then
                                endlen += 4
-                       else if c == 0x22u8 then
+                       else if c == b'"' then
                                endlen += 4
-                       else if c == 0x27u8 then
+                       else if c == b'\'' then
                                endlen += 4
                        else if c == 0x2Fu8 then
                                endlen += 4
@@ -118,60 +138,53 @@ redef class FlatText
                var its = _items
                var max = last_byte
                var pos = first_byte
-               var nlen = extra + _bytelen
-               var nits = new NativeString(nlen)
+               var nlen = extra + _byte_length
+               var nits = new CString(nlen)
                var outpos = 0
                while pos <= max do
                        var c = its[pos]
                        # Special codes:
                        # Some HTML characters are used as meta-data, they need
                        # to be replaced by an HTML-Escaped equivalent
-                       #
-                       # * 0x3C (<) => &lt;
-                       # * 0x3E (>) => &gt;
-                       # * 0x26 (&) => &amp;
-                       # * 0x22 (") => &#34;
-                       # * 0x27 (') => &#39;
-                       # * 0x2F (/) => &#47;
-                       if c == 0x3Cu8 then
-                               nits[outpos] = 0x26u8
-                               nits[outpos + 1] = 0x6Cu8
-                               nits[outpos + 2] = 0x74u8
-                               nits[outpos + 3] = 0x3Bu8
+                       if c == b'<' then
+                               nits[outpos] = b'&'
+                               nits[outpos + 1] = b'l'
+                               nits[outpos + 2] = b't'
+                               nits[outpos + 3] = b';'
                                outpos += 4
-                       else if c == 0x3Eu8 then
-                               nits[outpos] = 0x26u8
-                               nits[outpos + 1] = 0x67u8
-                               nits[outpos + 2] = 0x74u8
-                               nits[outpos + 3] = 0x3Bu8
+                       else if c == b'>' then
+                               nits[outpos] = b'&'
+                               nits[outpos + 1] = b'g'
+                               nits[outpos + 2] = b't'
+                               nits[outpos + 3] = b';'
                                outpos += 4
-                       else if c == 0x26u8 then
-                               nits[outpos] = 0x26u8
-                               nits[outpos + 1] = 0x61u8
-                               nits[outpos + 2] = 0x6Du8
-                               nits[outpos + 3] = 0x70u8
-                               nits[outpos + 4] = 0x3Bu8
+                       else if c == b'&' then
+                               nits[outpos] = b'&'
+                               nits[outpos + 1] = b'a'
+                               nits[outpos + 2] = b'm'
+                               nits[outpos + 3] = b'p'
+                               nits[outpos + 4] = b';'
                                outpos += 5
-                       else if c == 0x22u8 then
-                               nits[outpos] = 0x26u8
-                               nits[outpos + 1] = 0x23u8
-                               nits[outpos + 2] = 0x33u8
-                               nits[outpos + 3] = 0x34u8
-                               nits[outpos + 4] = 0x3Bu8
+                       else if c == b'"' then
+                               nits[outpos] = b'&'
+                               nits[outpos + 1] = b'#'
+                               nits[outpos + 2] = b'3'
+                               nits[outpos + 3] = b'4'
+                               nits[outpos + 4] = b';'
                                outpos += 5
-                       else if c == 0x27u8 then
-                               nits[outpos] = 0x26u8
-                               nits[outpos + 1] = 0x23u8
-                               nits[outpos + 2] = 0x33u8
-                               nits[outpos + 3] = 0x39u8
-                               nits[outpos + 4] = 0x3Bu8
+                       else if c == b'\'' then
+                               nits[outpos] = b'&'
+                               nits[outpos + 1] = b'#'
+                               nits[outpos + 2] = b'3'
+                               nits[outpos + 3] = b'9'
+                               nits[outpos + 4] = b';'
                                outpos += 5
                        else if c == 0x2Fu8 then
-                               nits[outpos] = 0x26u8
-                               nits[outpos + 1] = 0x23u8
-                               nits[outpos + 2] = 0x34u8
-                               nits[outpos + 3] = 0x37u8
-                               nits[outpos + 4] = 0x3Bu8
+                               nits[outpos] = b'&'
+                               nits[outpos + 1] = b'#'
+                               nits[outpos + 2] = b'4'
+                               nits[outpos + 3] = b'7'
+                               nits[outpos + 4] = b';'
                                outpos += 5
                        else
                                nits[outpos] = c
@@ -179,7 +192,7 @@ redef class FlatText
                        end
                        pos += 1
                end
-               var s = new FlatString.with_infos(nits, nlen, 0, nlen - 1)
+               var s = new FlatString.with_infos(nits, nlen, 0)
                return s
        end
 
@@ -195,16 +208,32 @@ redef class FlatText
                var req_esc = 0
                while pos <= max do
                        var c = its[pos]
-                       if c == 0x0Au8 then
+                       if c == b'\n' then
                                req_esc += 1
-                       else if c == 0x09u8 then
+                       else if c == b'\t' then
                                req_esc += 1
-                       else if c == 0x22u8 then
+                       else if c == b'"' then
                                req_esc += 1
-                       else if c == 0x27u8 then
+                       else if c == b'\'' then
                                req_esc += 1
-                       else if c == 0x5Cu8 then
+                       else if c == b'\\' then
                                req_esc += 1
+                       else if c == 0x3Fu8 then
+                               var j = pos + 1
+                               if j < length then
+                                       var next = its[j]
+                                       # We ignore `??'` because it will be escaped as `??\'`.
+                                       if
+                                               next == 0x21u8 or
+                                               next == 0x28u8 or
+                                               next == 0x29u8 or
+                                               next == 0x2Du8 or
+                                               next == 0x2Fu8 or
+                                               next == 0x3Cu8 or
+                                               next == 0x3Du8 or
+                                               next == 0x3Eu8
+                                       then req_esc += 1
+                               end
                        else if c < 32u8 then
                                req_esc += 3
                        end
@@ -218,8 +247,8 @@ redef class FlatText
                if ln_extra == 0 then return self.to_s
                var its = _items
                var max = last_byte
-               var nlen = _bytelen + ln_extra
-               var nns = new NativeString(nlen)
+               var nlen = _byte_length + ln_extra
+               var nns = new CString(nlen)
                var pos = first_byte
                var opos = 0
                while pos <= max do
@@ -240,31 +269,52 @@ redef class FlatText
                        # * 0x22 => \"
                        # * 0x27 => \'
                        # * 0x5C => \\
-                       if c == 0x09u8 then
-                               nns[opos] = 0x5Cu8
-                               nns[opos + 1] = 0x74u8
+                       if c == b'\t' then
+                               nns[opos] = b'\\'
+                               nns[opos + 1] = b't'
                                opos += 2
-                       else if c == 0x0Au8 then
-                               nns[opos] = 0x5Cu8
-                               nns[opos + 1] = 0x6Eu8
+                       else if c == b'\n' then
+                               nns[opos] = b'\\'
+                               nns[opos + 1] = b'n'
                                opos += 2
-                       else if c == 0x22u8 then
-                               nns[opos] = 0x5Cu8
-                               nns[opos + 1] = 0x22u8
+                       else if c == b'"' then
+                               nns[opos] = b'\\'
+                               nns[opos + 1] = b'"'
                                opos += 2
-                       else if c == 0x27u8 then
-                               nns[opos] = 0x5Cu8
-                               nns[opos + 1] = 0x27u8
+                       else if c == b'\'' then
+                               nns[opos] = b'\\'
+                               nns[opos + 1] = b'\''
                                opos += 2
-                       else if c == 0x5Cu8 then
-                               nns[opos] = 0x5Cu8
-                               nns[opos + 1] = 0x5Cu8
+                       else if c == b'\\' then
+                               nns[opos] = b'\\'
+                               nns[opos + 1] = b'\\'
                                opos += 2
+                       else if c == 0x3Fu8 then
+                               var j = pos + 1
+                               if j < length then
+                                       var next = its[j]
+                                       # We ignore `??'` because it will be escaped as `??\'`.
+                                       if
+                                               next == 0x21u8 or
+                                               next == 0x28u8 or
+                                               next == 0x29u8 or
+                                               next == 0x2Du8 or
+                                               next == 0x2Fu8 or
+                                               next == 0x3Cu8 or
+                                               next == 0x3Du8 or
+                                               next == 0x3Eu8
+                                       then
+                                               nns[opos] = 0x5Cu8
+                                               opos += 1
+                                       end
+                               end
+                               nns[opos] = 0x3Fu8
+                               opos += 1
                        else if c < 32u8 then
-                               nns[opos] = 0x5Cu8
-                               nns[opos + 1] = 0x30u8
-                               nns[opos + 2] = ((c & 0x38u8) >> 3) + 0x30u8
-                               nns[opos + 3] = (c & 0x07u8) + 0x30u8
+                               nns[opos] = b'\\'
+                               nns[opos + 1] = b'0'
+                               nns[opos + 2] = ((c & 0x38u8) >> 3) + b'0'
+                               nns[opos + 3] = (c & 0x07u8) + b'0'
                                opos += 4
                        else
                                nns[opos] = c
@@ -272,48 +322,118 @@ redef class FlatText
                        end
                        pos += 1
                end
-               return nns.to_s_with_length(nlen)
+               return nns.to_s_unsafe(nlen, copy=false, clean=false)
        end
 
-       redef fun [](index) do return _items.char_at(char_to_byte_index(index))
+       redef fun [](index) do
+               var len = _length
+
+               # Statistically:
+               # * ~70% want the next char
+               # * ~23% want the previous
+               # * ~7% want the same char
+               #
+               # So it makes sense to shortcut early. And early is here.
+               var dpos = index - _position
+               var b = _bytepos
+               if dpos == 1 and index < len - 1 then
+                       var its = _items
+                       var c = its[b]
+                       if c & 0x80u8 == 0x00u8 then
+                               # We want the next, and current is easy.
+                               # So next is easy to find!
+                               b += 1
+                               _position = index
+                               _bytepos = b
+                               # The rest will be done by `dpos==0` bellow.
+                               dpos = 0
+                       end
+               else if dpos == -1 and index > 1 then
+                       var its = _items
+                       var c = its[b-1]
+                       if c & 0x80u8 == 0x00u8 then
+                               # We want the previous, and it is easy.
+                               b -= 1
+                               dpos = 0
+                               _position = index
+                               _bytepos = b
+                               return c.ascii
+                       end
+               end
+               if dpos == 0 then
+                       # We know what we want (+0 or +1) just get it now!
+                       var its = _items
+                       var c = its[b]
+                       if c & 0x80u8 == 0x00u8 then return c.ascii
+                       return items.char_at(b)
+               end
+
+               assert index >= 0 and index < len
+               return fetch_char_at(index)
+       end
+
+       # Gets a `Char` at `index` in `self`
+       #
+       # WARNING: Use at your own risks as no bound-checking is done
+       fun fetch_char_at(index: Int): Char do
+               var i = char_to_byte_index(index)
+               var items = _items
+               var b = items[i]
+               if b & 0x80u8 == 0x00u8 then return b.ascii
+               return items.char_at(i)
+       end
+
+       # If `self` contains only digits and alpha <= 'f', return the corresponding integer.
+       #
+       #     assert "ff".to_hex == 255
+       redef fun to_hex(pos, ln) do
+               var res = 0
+               if pos == null then pos = 0
+               if ln == null then ln = length - pos
+               pos = char_to_byte_index(pos)
+               var its = _items
+               var max = pos + ln
+               for i in [pos .. max[ do
+                       res <<= 4
+                       res += its[i].ascii.from_hex
+               end
+               return res
+       end
+
+       redef fun copy_to_native(dst, n, src_off, dst_off) do
+               _items.copy_to(dst, n, first_byte + src_off, dst_off)
+       end
 end
 
 # Immutable strings of characters.
-class FlatString
+abstract class FlatString
        super FlatText
        super String
 
        # Index at which `self` begins in `_items`, inclusively
        redef var first_byte is noinit
 
-       # Index at which `self` ends in `_items`, inclusively
-       redef var last_byte is noinit
-
-       redef var chars = new FlatStringCharView(self) is lazy
+       redef fun chars do return new FlatStringCharView(self)
 
-       redef var bytes = new FlatStringByteView(self) is lazy
+       redef fun bytes do return new FlatStringByteView(self)
 
-       redef var length is lazy do
-               if _bytelen == 0 then return 0
-               return _items.utf8_length(_first_byte, _last_byte)
-       end
-
-       redef var to_cstring is lazy do
-               var blen = _bytelen
-               var new_items = new NativeString(blen + 1)
+       redef fun to_cstring do
+               var blen = _byte_length
+               var new_items = new CString(blen + 1)
                _items.copy_to(new_items, blen, _first_byte, 0)
                new_items[blen] = 0u8
                return new_items
        end
 
-       redef fun reversed
-       do
-               var b = new FlatBuffer.with_capacity(_bytelen + 1)
-               for i in [length - 1 .. 0].step(-1) do
-                       b.add self[i]
+       redef fun reversed do
+               var b = new FlatBuffer.with_capacity(_byte_length + 1)
+               var i = _length - 1
+               while i >= 0 do
+                       b.add self.fetch_char_at(i)
+                       i -= 1
                end
                var s = b.to_s.as(FlatString)
-               s.length = self.length
+               s._length = self._length
                return s
        end
 
@@ -321,24 +441,40 @@ class FlatString
 
        redef fun substring(from, count)
        do
-               assert count >= 0
+               if count <= 0 then return ""
 
                if from < 0 then
                        count += from
-                       if count < 0 then count = 0
+                       if count <= 0 then return ""
                        from = 0
                end
 
-               if (count + from) > length then count = length - from
+               var ln = _length
+               if (count + from) > ln then count = ln - from
                if count <= 0 then return ""
                var end_index = from + count - 1
+               return substring_impl(from, count, end_index)
+       end
+
+       private fun substring_impl(from, count, end_index: Int): String do
+               var cache = _position
+               var dfrom = (cache - from).abs
+               var dend = (end_index - from).abs
+
+               var bytefrom: Int
+               var byteto: Int
+               if dfrom < dend then
+                       bytefrom = char_to_byte_index(from)
+                       byteto = char_to_byte_index(end_index)
+               else
+                       byteto = char_to_byte_index(end_index)
+                       bytefrom = char_to_byte_index(from)
+               end
 
-               var bytefrom = char_to_byte_index(from)
-               var byteto = char_to_byte_index(end_index)
                var its = _items
                byteto += its.length_of_char_at(byteto) - 1
 
-               var s = new FlatString.full(its, byteto - bytefrom + 1, bytefrom, byteto, count)
+               var s = new FlatString.full(its, byteto - bytefrom + 1, bytefrom, count)
                return s
        end
 
@@ -346,9 +482,9 @@ class FlatString
 
        redef fun to_upper
        do
-               var outstr = new FlatBuffer.with_capacity(self._bytelen + 1)
+               var outstr = new FlatBuffer.with_capacity(self._byte_length + 1)
 
-               var mylen = length
+               var mylen = _length
                var pos = 0
 
                while pos < mylen do
@@ -361,9 +497,9 @@ class FlatString
 
        redef fun to_lower
        do
-               var outstr = new FlatBuffer.with_capacity(self._bytelen + 1)
+               var outstr = new FlatBuffer.with_capacity(self._byte_length + 1)
 
-               var mylen = length
+               var mylen = _length
                var pos = 0
 
                while pos < mylen do
@@ -387,27 +523,21 @@ class FlatString
        #
        # `_items` will be used as is, without copy, to retrieve the characters of the string.
        # Aliasing issues is the responsibility of the caller.
-       private init with_infos(items: NativeString, bytelen, from, to: Int)
+       private new with_infos(items: CString, byte_length, from: Int)
        do
-               self._items = items
-               self._bytelen = bytelen
-               _first_byte = from
-               _last_byte = to
-               _bytepos = from
+               var len = items.utf8_length(from, byte_length)
+               if byte_length == len then return new ASCIIFlatString.full_data(items, byte_length, from, len)
+               return new UnicodeFlatString.full_data(items, byte_length, from, len)
        end
 
        # Low-level creation of a new string with all the data.
        #
        # `_items` will be used as is, without copy, to retrieve the characters of the string.
        # Aliasing issues is the responsibility of the caller.
-       private init full(items: NativeString, bytelen, from, to, length: Int)
+       private new full(items: CString, byte_length, from, length: Int)
        do
-               self._items = items
-               self.length = length
-               self._bytelen = bytelen
-               _first_byte = from
-               _last_byte = to
-               _bytepos = from
+               if byte_length == length then return new ASCIIFlatString.full_data(items, byte_length, from, length)
+               return new UnicodeFlatString.full_data(items, byte_length, from, length)
        end
 
        redef fun ==(other)
@@ -416,9 +546,9 @@ class FlatString
 
                if self.object_id == other.object_id then return true
 
-               var my_length = _bytelen
+               var my_length = _byte_length
 
-               if other._bytelen != my_length then return false
+               if other._byte_length != my_length then return false
 
                var my_index = _first_byte
                var its_index = other.first_byte
@@ -446,8 +576,8 @@ class FlatString
                var myits = _items
                var itsits = other._items
 
-               var mbt = _bytelen
-               var obt = other.bytelen
+               var mbt = _byte_length
+               var obt = other.byte_length
 
                var minln = if mbt < obt then mbt else obt
                var mst = _first_byte
@@ -469,42 +599,41 @@ class FlatString
 
        redef fun +(o) do
                var s = o.to_s
-               var slen = s.bytelen
-               var mlen = _bytelen
+               var slen = s.byte_length
+               var mlen = _byte_length
                var nlen = mlen + slen
                var mits = _items
                var mifrom = _first_byte
                if s isa FlatText then
                        var sits = s._items
                        var sifrom = s.first_byte
-                       var ns = new NativeString(nlen + 1)
+                       var ns = new CString(nlen + 1)
                        mits.copy_to(ns, mlen, mifrom, 0)
                        sits.copy_to(ns, slen, sifrom, mlen)
-                       return new FlatString.full(ns, nlen, 0, nlen - 1, length + o.length)
+                       return new FlatString.full(ns, nlen, 0, _length + o.length)
                else
                        abort
                end
        end
 
        redef fun *(i) do
-               var mybtlen = _bytelen
-               var new_bytelen = mybtlen * i
-               var mylen = length
+               var mybtlen = _byte_length
+               var new_byte_length = mybtlen * i
+               var mylen = _length
                var newlen = mylen * i
                var its = _items
                var fb = _first_byte
-               var ns = new NativeString(new_bytelen + 1)
-               ns[new_bytelen] = 0u8
+               var ns = new CString(new_byte_length + 1)
+               ns[new_byte_length] = 0u8
                var offset = 0
                while i > 0 do
                        its.copy_to(ns, mybtlen, fb, offset)
                        offset += mybtlen
                        i -= 1
                end
-               return new FlatString.full(ns, new_bytelen, 0, new_bytelen - 1, newlen)
+               return new FlatString.full(ns, new_byte_length, 0, newlen)
        end
 
-
        redef fun hash
        do
                if hash_cache == null then
@@ -513,7 +642,7 @@ class FlatString
                        var i = _first_byte
 
                        var my_items = _items
-                       var max = _last_byte
+                       var max = last_byte
 
                        while i <= max do
                                h = (h << 5) + h + my_items[i].to_i
@@ -529,6 +658,80 @@ class FlatString
        redef fun substrings do return new FlatSubstringsIter(self)
 end
 
+# Regular Nit UTF-8 strings
+private class UnicodeFlatString
+       super FlatString
+
+       init full_data(items: CString, byte_length, from, length: Int) do
+               self._items = items
+               self._length = length
+               self._byte_length = byte_length
+               _first_byte = from
+               _bytepos = from
+       end
+
+       redef fun substring_from(from) do
+               if from >= self._length then return empty
+               if from <= 0 then return self
+               var c = char_to_byte_index(from)
+               var st = c - _first_byte
+               var fln = byte_length - st
+               return new FlatString.full(items, fln, c, _length - from)
+       end
+end
+
+# Special cases of String where all the characters are ASCII-based
+#
+# Optimizes access operations to O(1) complexity.
+private class ASCIIFlatString
+       super FlatString
+
+       init full_data(items: CString, byte_length, from, length: Int) do
+               self._items = items
+               self._length = length
+               self._byte_length = byte_length
+               _first_byte = from
+               _bytepos = from
+       end
+
+       redef fun [](idx) do
+               assert idx < _byte_length and idx >= 0
+               return _items[idx + _first_byte].ascii
+       end
+
+       redef fun substring(from, count) do
+               var ln = _length
+               if count <= 0 then return ""
+               if (count + from) > ln then count = ln - from
+               if count <= 0 then return ""
+               if from < 0 then
+                       count += from
+                       if count <= 0 then return ""
+                       from = 0
+               end
+               return new ASCIIFlatString.full_data(_items, count, from + _first_byte, count)
+       end
+
+       redef fun reversed do
+               var b = new FlatBuffer.with_capacity(_byte_length + 1)
+               var i = _length - 1
+               while i >= 0 do
+                       b.add self[i]
+                       i -= 1
+               end
+               var s = b.to_s.as(FlatString)
+               return s
+       end
+
+       redef fun char_to_byte_index(index) do return index + _first_byte
+
+       redef fun substring_impl(from, count, end_index) do
+               return new ASCIIFlatString.full_data(_items, count, from + _first_byte, count)
+       end
+
+       redef fun fetch_char_at(i) do return _items[i + _first_byte].ascii
+end
+
 private class FlatStringCharReverseIterator
        super IndexedIterator[Char]
 
@@ -555,7 +758,7 @@ private class FlatStringCharIterator
 
        var curr_pos: Int
 
-       init do max = target.length - 1
+       init do max = target._length - 1
 
        redef fun is_ok do return curr_pos <= max
 
@@ -585,7 +788,7 @@ private class FlatStringByteReverseIterator
 
        var target: FlatString
 
-       var target_items: NativeString is noautoinit
+       var target_items: CString is noautoinit
 
        var curr_pos: Int
 
@@ -611,7 +814,7 @@ private class FlatStringByteIterator
 
        var target: FlatString
 
-       var target_items: NativeString is noautoinit
+       var target_items: CString is noautoinit
 
        var curr_pos: Int
 
@@ -622,7 +825,7 @@ private class FlatStringByteIterator
                curr_pos += tgt._first_byte
        end
 
-       redef fun is_ok do return curr_pos <= target._last_byte
+       redef fun is_ok do return curr_pos <= target.last_byte
 
        redef fun item do return target_items[curr_pos]
 
@@ -639,12 +842,11 @@ private class FlatStringByteView
 
        redef fun [](index)
        do
-               # Check that the index (+ _first_byte) is not larger than _last_byte
+               # Check that the index (+ _first_byte) is not larger than last_byte
                # In other terms, if the index is valid
-               assert index >= 0
-               var target = self.target
+               var target = _target
+               assert index >= 0 and index < target._byte_length
                var ind = index + target._first_byte
-               assert ind <= target._last_byte
                return target._items[ind]
        end
 
@@ -665,46 +867,37 @@ class FlatBuffer
        super FlatText
        super Buffer
 
-       redef var chars: Sequence[Char] = new FlatBufferCharView(self) is lazy
-
-       redef var bytes = new FlatBufferByteView(self) is lazy
-
-       redef var length = 0
-
-       private var char_cache: Int = -1
+       redef fun chars do return new FlatBufferCharView(self)
 
-       private var byte_cache: Int = -1
+       redef fun bytes do return new FlatBufferByteView(self)
 
        private var capacity = 0
 
-       # Real items, used as cache for when to_cstring is called
-       private var real_items: NativeString is noinit
-
        redef fun fast_cstring do return _items.fast_cstring(0)
 
        redef fun substrings do return new FlatSubstringsIter(self)
 
-       # Re-copies the `NativeString` into a new one and sets it as the new `Buffer`
+       # Re-copies the `CString` into a new one and sets it as the new `Buffer`
        #
        # This happens when an operation modifies the current `Buffer` and
        # the Copy-On-Write flag `written` is set at true.
        private fun reset do
-               var nns = new NativeString(capacity)
-               if _bytelen != 0 then _items.copy_to(nns, _bytelen, 0, 0)
+               var nns = new CString(capacity)
+               if _byte_length != 0 then _items.copy_to(nns, _byte_length, 0, 0)
                _items = nns
                written = false
        end
 
        # Shifts the content of the buffer by `len` bytes to the right, starting at byte `from`
        #
-       # Internal only, does not modify _bytelen or length, this is the caller's responsability
+       # Internal only, does not modify _byte_length or length, this is the caller's responsability
        private fun rshift_bytes(from: Int, len: Int) do
                var oit = _items
                var nit = _items
-               var bt = _bytelen
+               var bt = _byte_length
                if bt + len > capacity then
                        capacity = capacity * 2 + 2
-                       nit = new NativeString(capacity)
+                       nit = new CString(capacity)
                        oit.copy_to(nit, 0, 0, from)
                end
                oit.copy_to(nit, bt - from, from, from + len)
@@ -712,18 +905,17 @@ class FlatBuffer
 
        # Shifts the content of the buffer by `len` bytes to the left, starting at `from`
        #
-       # Internal only, does not modify _bytelen or length, this is the caller's responsability
+       # Internal only, does not modify _byte_length or length, this is the caller's responsability
        private fun lshift_bytes(from: Int, len: Int) do
                var it = _items
-               it.copy_to(it, _bytelen - from, from, from - len)
+               it.copy_to(it, _byte_length - from, from, from - len)
        end
 
        redef fun []=(index, item)
        do
-               assert index >= 0 and index <= length
+               assert index >= 0 and index <= _length
                if written then reset
-               is_dirty = true
-               if index == length then
+               if index == _length then
                        add item
                        return
                end
@@ -738,28 +930,60 @@ class FlatBuffer
                else if size_diff < 0 then
                        lshift_bytes(ip + clen, -size_diff)
                end
-               _bytelen += size_diff
-               bytepos += size_diff
+               _byte_length += size_diff
                it.set_char_at(ip, item)
        end
 
+       redef fun insert(s, pos) do
+               assert pos >= 0 and pos <= length
+               if pos == length then
+                       append s
+                       return
+               end
+               var slen = s.byte_length
+               enlarge(byte_length + slen)
+               var it = _items
+               var shpos = it.char_to_byte_index(pos)
+               rshift_bytes(shpos, slen)
+               s.copy_to_native(it, slen, 0, shpos)
+               length += s.length
+               byte_length += slen
+       end
+
+       redef fun insert_char(c, pos) do
+               assert pos >= 0 and pos <= length
+               if pos == length then
+                       add c
+                       return
+               end
+               var clen = c.u8char_len
+               enlarge(byte_length + clen)
+               var it = _items
+               var shpos = it.char_to_byte_index(pos)
+               rshift_bytes(shpos, clen)
+               it.set_char_at(shpos, c)
+               length += 1
+               byte_length += clen
+       end
+
        redef fun add(c)
        do
                if written then reset
-               is_dirty = true
                var clen = c.u8char_len
-               var bt = _bytelen
+               var bt = _byte_length
                enlarge(bt + clen)
                _items.set_char_at(bt, c)
-               _bytelen += clen
-               length += 1
+               _byte_length += clen
+               _length += 1
        end
 
        redef fun clear do
-               is_dirty = true
-               if written then reset
-               _bytelen = 0
-               length = 0
+               _byte_length = 0
+               _length = 0
+               if written then
+                       _capacity = 16
+                       reset
+               end
        end
 
        redef fun empty do return new Buffer
@@ -768,12 +992,13 @@ class FlatBuffer
        do
                var c = capacity
                if cap <= c then return
-               while c <= cap do c = c * 2 + 2
+               if c <= 16 then c = 16
+               while c <= cap do c = c * 2
                # The COW flag can be set at false here, since
                # it does a copy of the current `Buffer`
                written = false
-               var bln = _bytelen
-               var a = new NativeString(c+1)
+               var bln = _byte_length
+               var a = new CString(c)
                if bln > 0 then
                        var it = _items
                        if bln > 0 then it.copy_to(a, bln, 0, 0)
@@ -785,22 +1010,18 @@ class FlatBuffer
        redef fun to_s
        do
                written = true
-               var bln = _bytelen
-               if bln == 0 then _items = new NativeString(1)
-               return new FlatString.full(_items, bln, 0, bln - 1, length)
+               var bln = _byte_length
+               if bln == 0 then _items = new CString(1)
+               return new FlatString.full(_items, bln, 0, _length)
        end
 
        redef fun to_cstring
        do
-               if is_dirty then
-                       var bln = _bytelen
-                       var new_native = new NativeString(bln + 1)
-                       new_native[bln] = 0u8
-                       if length > 0 then _items.copy_to(new_native, bln, 0, 0)
-                       real_items = new_native
-                       is_dirty = false
-               end
-               return real_items
+               var bln = _byte_length
+               var new_native = new CString(bln + 1)
+               new_native[bln] = 0u8
+               if _length > 0 then _items.copy_to(new_native, bln, 0, 0)
+               return new_native
        end
 
        # Create a new empty string.
@@ -813,53 +1034,47 @@ class FlatBuffer
        #
        # If `_items` is shared, `written` should be set to true after the creation
        # so that a modification will do a copy-on-write.
-       private init with_infos(items: NativeString, capacity, bytelen, length: Int)
+       private init with_infos(items: CString, capacity, byte_length, length: Int)
        do
                self._items = items
                self.capacity = capacity
-               self._bytelen = bytelen
-               self.length = length
+               self._byte_length = byte_length
+               self._length = length
        end
 
        # Create a new string copied from `s`.
        init from(s: Text)
        do
-               _items = new NativeString(s.bytelen)
-               if s isa FlatText then
-                       _items = s._items
-               else
-                       for i in substrings do i.as(FlatString)._items.copy_to(_items, i._bytelen, 0, 0)
-               end
-               _bytelen = s.bytelen
-               length = s.length
-               _capacity = _bytelen
-               written = true
+               _items = new CString(s.byte_length)
+               for i in s.substrings do i._items.copy_to(_items, i._byte_length, first_byte, 0)
+               _byte_length = s.byte_length
+               _length = s.length
+               _capacity = _byte_length
        end
 
        # Create a new empty string with a given capacity.
        init with_capacity(cap: Int)
        do
                assert cap >= 0
-               _items = new NativeString(cap + 1)
+               _items = new CString(cap)
                capacity = cap
-               _bytelen = 0
+               _byte_length = 0
        end
 
        redef fun append(s)
        do
                if s.is_empty then return
-               is_dirty = true
-               var sl = s.bytelen
-               var nln = _bytelen + sl
+               var sl = s.byte_length
+               var nln = _byte_length + sl
                enlarge(nln)
                if s isa FlatText then
-                       s._items.copy_to(_items, sl, s.first_byte, _bytelen)
+                       s._items.copy_to(_items, sl, s.first_byte, _byte_length)
                else
                        for i in s.substrings do append i
                        return
                end
-               _bytelen = nln
-               length += s.length
+               _byte_length = nln
+               _length += s.length
        end
 
        # Copies the content of self in `dest`
@@ -876,18 +1091,47 @@ class FlatBuffer
        do
                assert count >= 0
                if from < 0 then from = 0
-               if (from + count) > length then count = length - from
+               if (from + count) > _length then count = _length - from
                if count <= 0 then return new Buffer
                var its = _items
                var bytefrom = its.char_to_byte_index(from)
                var byteto = its.char_to_byte_index(count + from - 1)
                byteto += its.char_at(byteto).u8char_len - 1
                var byte_length = byteto - bytefrom + 1
-               var r_items = new NativeString(byte_length)
+               var r_items = new CString(byte_length)
                its.copy_to(r_items, byte_length, bytefrom, 0)
                return new FlatBuffer.with_infos(r_items, byte_length, byte_length, count)
        end
 
+       redef fun append_substring_impl(s, from, length) do
+               if length <= 0 then return
+               if not s isa FlatText then
+                       super
+                       return
+               end
+               var sits = s._items
+               var bytest = s.char_to_byte_index(from)
+               var bytend = s.char_to_byte_index(from + length - 1)
+               var btln = bytend - bytest + sits.char_at(bytend).u8char_len
+               enlarge(btln + _byte_length)
+               sits.copy_to(_items, btln, bytest, _byte_length)
+               _byte_length += btln
+               _length += length
+       end
+
+       redef fun remove_at(p, len) do
+               if len == null then len = 1
+               if len == 0 then return
+               var its = _items
+               var bst = char_to_byte_index(p)
+               var bend = char_to_byte_index(p + len - 1)
+               bend += its.char_at(bend).u8char_len
+               var blen = bend - bst
+               lshift_bytes(bend, bend - bst)
+               byte_length -= blen
+               length -= len
+       end
+
        redef fun reverse
        do
                written = false
@@ -898,8 +1142,8 @@ class FlatBuffer
 
        redef fun times(repeats)
        do
-               var bln = _bytelen
-               var x = new FlatString.full(_items, bln, 0, bln - 1, length)
+               var bln = _byte_length
+               var x = new FlatString.full(_items, bln, 0, _length)
                for i in [1 .. repeats[ do
                        append(x)
                end
@@ -908,13 +1152,13 @@ class FlatBuffer
        redef fun upper
        do
                if written then reset
-               for i in [0 .. length[ do self[i] = self[i].to_upper
+               for i in [0 .. _length[ do self[i] = self[i].to_upper
        end
 
        redef fun lower
        do
                if written then reset
-               for i in [0 .. length[ do self[i] = self[i].to_lower
+               for i in [0 .. _length[ do self[i] = self[i].to_lower
        end
 end
 
@@ -923,7 +1167,7 @@ private class FlatBufferByteReverseIterator
 
        var target: FlatBuffer
 
-       var target_items: NativeString is noautoinit
+       var target_items: CString is noautoinit
 
        var curr_pos: Int
 
@@ -957,7 +1201,7 @@ private class FlatBufferByteIterator
 
        var target: FlatBuffer
 
-       var target_items: NativeString is noautoinit
+       var target_items: CString is noautoinit
 
        var curr_pos: Int
 
@@ -965,7 +1209,7 @@ private class FlatBufferByteIterator
 
        redef fun index do return curr_pos
 
-       redef fun is_ok do return curr_pos < target._bytelen
+       redef fun is_ok do return curr_pos < target._byte_length
 
        redef fun item do return target_items[curr_pos]
 
@@ -1025,7 +1269,7 @@ private class FlatBufferCharView
        redef fun append(s)
        do
                var s_length = s.length
-               if target.capacity < s.length then enlarge(s_length + target.length)
+               if target.capacity < s.length then enlarge(s_length + target._length)
                for i in s do target.add i
        end
 
@@ -1044,7 +1288,7 @@ private class FlatBufferCharIterator
 
        var curr_pos: Int
 
-       init do max = target.length - 1
+       init do max = target._length - 1
 
        redef fun index do return curr_pos
 
@@ -1056,45 +1300,71 @@ private class FlatBufferCharIterator
 
 end
 
-redef class NativeString
-       redef fun to_s
-       do
-               return to_s_with_length(cstring_length)
-       end
+redef class CString
 
-       # Returns `self` as a String of `length`.
-       redef fun to_s_with_length(length): FlatString
-       do
-               assert length >= 0
-               return clean_utf8(length)
-       end
+       # Get a `String` from the data at `self` copied into Nit memory
+       #
+       # Require: `self` is a null-terminated string.
+       redef fun to_s do return to_s_unsafe
 
-       redef fun to_s_full(bytelen, unilen) do
-               return new FlatString.full(self, bytelen, 0, bytelen - 1, unilen)
-       end
+       # Get a `String` from `byte_length` bytes at `self` copied into Nit memory
+       #
+       # The string is cleaned.
+       fun to_s_with_length(byte_length: Int): String do return to_s_unsafe(byte_length)
 
-       # Returns `self` as a new String.
-       redef fun to_s_with_copy: FlatString
+       redef fun to_s_unsafe(byte_length, char_length, copy, clean)
        do
-               var length = cstring_length
-               var r = clean_utf8(length)
-               if r.items != self then return r
-               var new_self = new NativeString(length + 1)
-               copy_to(new_self, length, 0, 0)
-               var str = new FlatString.with_infos(new_self, length, 0, length - 1)
-               new_self[length] = 0u8
-               str.to_cstring = new_self
+               byte_length = byte_length or else cstring_length
+               clean = clean or else true
+               copy = copy or else true
+
+               # Clean?
+               var str = null
+               if clean then
+                       str = clean_utf8(byte_length)
+                       char_length = str.length
+               else
+                       char_length = char_length or else utf8_length(0, byte_length)
+               end
+
+               # Copy? (if not already copied by `clean_utf8`)
+               if copy and (str == null or str.items == self) then
+                       var new_cstr = new CString(byte_length + 1)
+                       copy_to(new_cstr, byte_length, 0, 0)
+                       new_cstr[byte_length] = 0u8
+                       str = new FlatString.full(new_cstr, byte_length, 0, char_length)
+               end
+
+               if str == null then
+                       str = new FlatString.full(self, byte_length, 0, char_length)
+               end
+
                return str
        end
 
-       # Cleans a NativeString if necessary
+       # Cleans a CString if necessary
        fun clean_utf8(len: Int): FlatString do
                var replacements: nullable Array[Int] = null
                var end_length = len
                var pos = 0
                var chr_ln = 0
-               while pos < len do
+               var rem = len
+               while rem > 0 do
+                       while rem >= 4 do
+                               var i = fetch_4_chars(pos)
+                               if i & 0x80808080u32 != 0u32 then break
+                               pos += 4
+                               chr_ln += 4
+                               rem -= 4
+                       end
+                       if rem == 0 then break
                        var b = self[pos]
+                       if b & 0x80u8 == 0x00u8 then
+                               pos += 1
+                               chr_ln += 1
+                               rem -= 1
+                               continue
+                       end
                        var nxst = length_of_char_at(pos)
                        var ok_st: Bool
                        if nxst == 1 then
@@ -1111,6 +1381,7 @@ redef class NativeString
                                replacements.add pos
                                end_length += 2
                                pos += 1
+                               rem -= 1
                                chr_ln += 1
                                continue
                        end
@@ -1133,14 +1404,17 @@ redef class NativeString
                                end_length += 2
                                pos += 1
                                chr_ln += 1
+                               rem -= 1
                                continue
                        end
-                       pos += c.u8char_len
+                       var clen = c.u8char_len
+                       pos += clen
+                       rem -= clen
                        chr_ln += 1
                end
                var ret = self
                if end_length != len then
-                       ret = new NativeString(end_length)
+                       ret = new CString(end_length)
                        var old_repl = 0
                        var off = 0
                        var repls = replacements.as(not null)
@@ -1159,51 +1433,36 @@ redef class NativeString
                        end
                        copy_to(ret, len - old_repl, old_repl, off)
                end
-               return new FlatString.full(ret, end_length, 0, end_length - 1, chr_ln)
+               return new FlatString.full(ret, end_length, 0, chr_ln)
        end
 
        # Sets the next bytes at position `pos` to the value of `c`, encoded in UTF-8
        #
        # Very unsafe, make sure to have room for this char prior to calling this function.
        private fun set_char_at(pos: Int, c: Char) do
+               var cp = c.code_point
+               if cp < 128 then
+                       self[pos] = cp.to_b
+                       return
+               end
                var ln = c.u8char_len
-               native_set_char(pos, c, ln)
-       end
-
-       private fun native_set_char(pos: Int, c: Char, ln: Int) `{
-               char* dst = self + pos;
-               switch(ln){
-                       case 1:
-                               dst[0] = c;
-                               break;
-                       case 2:
-                               dst[0] = 0xC0 | ((c & 0x7C0) >> 6);
-                               dst[1] = 0x80 | (c & 0x3F);
-                               break;
-                       case 3:
-                               dst[0] = 0xE0 | ((c & 0xF000) >> 12);
-                               dst[1] = 0x80 | ((c & 0xFC0) >> 6);
-                               dst[2] = 0x80 | (c & 0x3F);
-                               break;
-                       case 4:
-                               dst[0] = 0xF0 | ((c & 0x1C0000) >> 18);
-                               dst[1] = 0x80 | ((c & 0x3F000) >> 12);
-                               dst[2] = 0x80 | ((c & 0xFC0) >> 6);
-                               dst[3] = 0x80 | (c & 0x3F);
-                               break;
-               }
-       `}
+               if ln == 2 then
+                       self[pos] = (0xC0 | ((cp & 0x7C0) >> 6)).to_b
+                       self[pos + 1] = (0x80 | (cp & 0x3F)).to_b
+               else if ln == 3 then
+                       self[pos] = (0xE0 | ((cp & 0xF000) >> 12)).to_b
+                       self[pos + 1] = (0x80 | ((cp & 0xFC0) >> 6)).to_b
+                       self[pos + 2] = (0x80 | (cp & 0x3F)).to_b
+               else if ln == 4 then
+                       self[pos] = (0xF0 | ((cp & 0x1C0000) >> 18)).to_b
+                       self[pos + 1] = (0x80 | ((cp & 0x3F000) >> 12)).to_b
+                       self[pos + 2] = (0x80 | ((cp & 0xFC0) >> 6)).to_b
+                       self[pos + 3] = (0x80 | (cp & 0x3F)).to_b
+               end
+       end
 end
 
 redef class Int
-       redef fun to_base(base, signed)
-       do
-               var l = digit_count(base)
-               var s = new FlatBuffer.from(" " * l)
-               fill_buffer(s, base, signed)
-               return s.to_s
-       end
-
        # return displayable int in base 10 and signed
        #
        #     assert 1.to_s            == "1"
@@ -1214,10 +1473,10 @@ redef class Int
                if self == 1 then return "1"
 
                var nslen = int_to_s_len
-               var ns = new NativeString(nslen + 1)
+               var ns = new CString(nslen + 1)
                ns[nslen] = 0u8
                native_int_to_s(ns, nslen + 1)
-               return new FlatString.full(ns, nslen, 0, nslen - 1, nslen)
+               return new FlatString.full(ns, nslen, 0, nslen)
        end
 end
 
@@ -1226,7 +1485,7 @@ redef class Array[E]
        # Fast implementation
        redef fun plain_to_s
        do
-               var l = length
+               var l = _length
                if l == 0 then return ""
                var its = _items.as(not null)
                var first = its[0]
@@ -1242,32 +1501,32 @@ redef class Array[E]
                                continue
                        end
                        var tmp = itsi.to_s
-                       sl += tmp.bytelen
+                       sl += tmp.byte_length
                        na[mypos] = tmp
                        i += 1
                        mypos += 1
                end
-               var ns = new NativeString(sl + 1)
+               var ns = new CString(sl + 1)
                ns[sl] = 0u8
                i = 0
                var off = 0
                while i < mypos do
                        var tmp = na[i]
                        if tmp isa FlatString then
-                               var tpl = tmp._bytelen
+                               var tpl = tmp._byte_length
                                tmp._items.copy_to(ns, tpl, tmp._first_byte, off)
                                off += tpl
                        else
                                for j in tmp.substrings do
                                        var s = j.as(FlatString)
-                                       var slen = s._bytelen
+                                       var slen = s._byte_length
                                        s._items.copy_to(ns, slen, s._first_byte, off)
                                        off += slen
                                end
                        end
                        i += 1
                end
-               return new FlatString.with_infos(ns, sl, 0, sl - 1)
+               return new FlatString.with_infos(ns, sl, 0)
        end
 end
 
@@ -1280,31 +1539,31 @@ redef class NativeArray[E]
                var sl = 0
                var mypos = 0
                while i < l do
-                       sl += na[i].bytelen
+                       sl += na[i].byte_length
                        i += 1
                        mypos += 1
                end
-               var ns = new NativeString(sl + 1)
+               var ns = new CString(sl + 1)
                ns[sl] = 0u8
                i = 0
                var off = 0
                while i < mypos do
                        var tmp = na[i]
                        if tmp isa FlatString then
-                               var tpl = tmp._bytelen
+                               var tpl = tmp._byte_length
                                tmp._items.copy_to(ns, tpl, tmp._first_byte, off)
                                off += tpl
                        else
                                for j in tmp.substrings do
                                        var s = j.as(FlatString)
-                                       var slen = s._bytelen
+                                       var slen = s._byte_length
                                        s._items.copy_to(ns, slen, s._first_byte, off)
                                        off += slen
                                end
                        end
                        i += 1
                end
-               return new FlatString.with_infos(ns, sl, 0, sl - 1)
+               return new FlatString.with_infos(ns, sl, 0)
        end
 end