-# Mutable `Rope`, optimized for concatenation operations
-#
-# A `RopeBuffer` is an efficient way of building a `String` when
-# concatenating small strings.
-#
-# It does concatenations in an optimized way by having a
-# mutable part and an immutable part built by efficiently
-# concatenating strings in chain.
-#
-# Every concatenation operation is done by copying a string to
-# the mutable part and flushing it when full.
-#
-# However, when a long string is appended to the `Buffer`,
-# the concatenation is done at it would be in a `Rope`.
-class RopeBuffer
- super Rope
- super Buffer
-
- redef var chars: Sequence[Char] is lazy do return new RopeBufferChars(self)
-
- redef var bytes is lazy do return new RopeBufferBytes(self)
-
- # The final string being built on the fly
- private var str: String = ""
-
- # Current concatenation buffer
- private var ns: NativeString is noinit
-
- # Next available (e.g. unset) character in the `Buffer`
- private var rpos = 0
-
- # Length (in chars) of the buffered part
- private var nslen = 0
-
- # Keeps track of the buffer's currently dumped part
- #
- # This might happen if for instance, a String was being
- # built by concatenating small parts of string and suddenly
- # a long string (length > maxlen) is appended.
- private var dumped: Int is noinit
-
- # Length of the complete rope in chars (0)
- redef fun length do
- var st = dumped
- var len = str.length
- while st < rpos do
- st += ns[st].u8len
- len += 1
- end
- return len
- end
-
- # Length of the complete rope in bytes
- redef var bytelen = 0
-
- # Length of the mutable part (in bytes)
- #
- # Is also used as base to compute the size of the next
- # mutable native string (`ns`)
- private var buf_size: Int is noinit
-
- redef fun substrings do return new RopeBufSubstringIterator.from(self)
-
- # Builds an empty `RopeBuffer`
- init do
- ns = new NativeString(maxlen)
- buf_size = maxlen
- dumped = 0
- end
-
- # Builds a new `RopeBuffer` with `str` in it.
- init from(str: String) do
- self.str = str
- ns = new NativeString(maxlen)
- buf_size = maxlen
- _bytelen = str.length
- dumped = 0
- end
-
- # Resets the informations of the `Buffer`
- #
- # This is called when doing in-place modifications
- # on a previously to_s'd `RopeBuffer`
- private fun reset do
- var nns = new NativeString(buf_size)
- var blen = rpos - dumped
- ns.copy_to(nns, blen, dumped, 0)
- ns = nns
- dumped = 0
- rpos = blen
- written = false
- end
-
- redef fun [](i) do
- if i < str.length then
- return str[i]
- else
- var index = ns.char_to_byte_index_cached(i - str.length, 0, dumped)
- return ns.char_at(index)
- end
- end
-
- redef fun []=(i, c) do
- assert i >= 0 and i <= length
- if i == length then add c
- if i < str.length then
- _bytelen += c.u8char_len - str[i].u8char_len
- var s = str
- var l = s.substring(0, i)
- var r = s.substring_from(i + 1)
- str = l + c.to_s + r
- else
- var reali = i - str.length
- var index = ns.char_to_byte_index_cached(reali, 0, dumped)
- var st_nxt = ns.char_to_byte_index_cached(reali + 1, reali, index)
- var loc_c = ns.char_at(index)
- if loc_c.u8char_len != c.u8char_len then
- var delta = c.u8char_len - loc_c.u8char_len
- var remsp = buf_size - rpos
- if remsp < delta then
- buf_size *= 2
- var nns = new NativeString(buf_size)
- ns.copy_to(nns, index - dumped, dumped, 0)
- ns.copy_to(nns, rpos - index - loc_c.u8char_len, index + loc_c.u8char_len, index - dumped + delta)
- ns = nns
- index = index - dumped
- else
- ns.copy_to(ns, rpos - st_nxt, st_nxt, st_nxt + delta)
- end
- _bytelen += delta
- rpos += delta
- end
- ns.set_char_at(index, c)
- end
- end
-
- redef fun empty do return new RopeBuffer
-
- redef fun clear do
- str = ""
- _bytelen = 0
- rpos = 0
- dumped = 0
- if written then
- ns = new NativeString(buf_size)
- written = false
- end
- end
-
- redef fun substring(from, count) do
- var strlen = str.length
-
- if from < 0 then
- count += from
- if count < 0 then count = 0
- from = 0
- end
-
- if count > length then count = length - from
-
- if count == 0 then return empty
-
- if from < strlen then
- var subpos = strlen - from
- if count <= subpos then
- return new RopeBuffer.from(str.substring(from, count))
- else
- var l = str.substring_from(from)
- var rem = count - subpos
- var nns = new NativeString(rem)
- ns.copy_to(nns, rem, dumped, 0)
- return new RopeBuffer.from(l + nns.to_s_unsafe(rem))
- end
- else
- var nns = new NativeString(count)
- ns.copy_to(nns, count, dumped, 0)
- return new RopeBuffer.from(nns.to_s_unsafe(count))
- end
- end
-
- redef fun append(s) do
- var slen = s.bytelen
- if slen >= maxlen then
- persist_buffer
- str += s.to_s
- return
- end
- if s isa FlatText then
- var oits = s._items
- var from = s.first_byte
- var remsp = buf_size - rpos
- if slen <= remsp then
- oits.copy_to(ns, slen, from, rpos)
- rpos += slen
- return
- end
- var brk = oits.find_beginning_of_char_at(from + remsp)
- oits.copy_to(ns, brk, from, rpos)
- rpos += brk
- dump_buffer
- oits.copy_to(ns, slen - remsp, brk, 0)
- rpos = slen - remsp
- else
- for i in s.substrings do append i
- end
- end
-
- redef fun add(c) do
- var rp = rpos
- var remsp = buf_size - rp
- var cln = c.u8char_len
- if cln > remsp then
- dump_buffer
- rp = 0
- end
- ns.set_char_at(rp, c)
- rp += cln
- _bytelen += cln
- rpos = rp
- end
-
- # Converts the Buffer to a FlatString, appends it to
- # the final String and re-allocates a new larger Buffer.
- private fun dump_buffer do
- written = false
- var nstr = new FlatString.with_infos(ns, rpos - dumped, dumped)
- str += nstr
- var bs = buf_size
- bs = bs * 2
- ns = new NativeString(bs)
- buf_size = bs
- dumped = 0
- rpos = 0
- end
-
- # Similar to dump_buffer, but does not reallocate a new NativeString
- private fun persist_buffer do
- if rpos == dumped then return
- var nstr = new FlatString.with_infos(ns, rpos - dumped, dumped)
- str += nstr
- dumped = rpos
- end
-
- redef fun output do
- str.output
- new FlatString.with_infos(ns, rpos - dumped, dumped).output
- end
-
- # Enlarge is useless here since the `Buffer`
- # part is automatically dumped when necessary.
- #
- # Also, since the buffer can not be overused by a
- # single string, there is no need for manual
- # resizing.
- #
- # "You have no power here !"
- redef fun enlarge(i) do end
-
- redef fun to_s do
- persist_buffer
- written = true
- return str
- end
-
- redef fun reverse do
- # Flush the buffer in order to only have to reverse `str`.
- if rpos > 0 and dumped != rpos then
- str += new FlatString.with_infos(ns, rpos - dumped, dumped)
- dumped = rpos
- end
- str = str.reversed
- end
-
- redef fun upper do
- if written then reset
- persist_buffer
- str = str.to_upper
- end
-
- redef fun lower do
- if written then reset
- persist_buffer
- str = str.to_lower
- end
-end
-