lib: prepare for new constructors
[nit.git] / lib / standard / ropes.nit
index f69b547..843096f 100644 (file)
@@ -44,6 +44,11 @@ end
 private abstract class RopeNode
        # Length of the node
        var length = 0
+
+       # Transforms the current node to a Leaf.
+       # This might be costly to invoke since this produces a FlatString concatenation.
+       # Can be used internally to limit the growth of the Rope when working with small leaves.
+       fun to_leaf: Leaf is abstract
 end
 
 # Node that represents a concatenation between two nodes (of any RopeNode type)
@@ -51,40 +56,55 @@ private class Concat
        super RopeNode
 
        # Left child of the node
-       var _left: nullable RopeNode = null
+       var left: nullable RopeNode
        # Right child of the node
-       var _right: nullable RopeNode = null
-
-       fun left: nullable RopeNode do return _left
-       fun right: nullable RopeNode do return _right
+       var right: nullable RopeNode
 
-       fun left=(l: RopeNode)
+       init(l: nullable RopeNode, r: nullable RopeNode)
        do
-               _left = l
-               length = l.length
-               if _right != null then length += _right.length
+               left = l
+               right = r
+               if l != null then length += l.length
+               if r != null then length += r.length
        end
 
-       fun right=(r: RopeNode)
+       redef fun to_leaf
        do
-               _right = r
-               length = r.length
-               if _left != null then length += _left.length
+               if left == null then
+                       if right == null then return new StringLeaf("".as(FlatString))
+                       return right.to_leaf
+               end
+               if right == null then return left.as(not null).to_leaf
+               return new StringLeaf((left.to_leaf.str.as(FlatString) + right.to_leaf.str.as(FlatString)).as(FlatString))
        end
 end
 
 # Leaf of a Rope, contains a FlatString
-private class Leaf
+private abstract class Leaf
        super RopeNode
 
        # Encapsulated FlatString in the leaf node
-       var str: FlatString
+       var str: FlatText
+
+end
+
+private class StringLeaf
+       super Leaf
 
        init(val: FlatString) do
                self.str = val
                length = str.length
        end
 
+       redef fun to_leaf do return self
+end
+
+# Used as a cache when using indexed access to a substring in the Rope
+private class LeafCache
+       # Cached leaf
+       var leaf: Leaf
+       # Position in Rope
+       var pos: Int
 end
 
 # Basic structure, binary tree with a root node.
@@ -96,12 +116,17 @@ abstract class Rope
        # Root node, entry point of a Rope.
        private var root: RopeNode
 
+       # Cached version of self as a flat String
+       private var str_representation: nullable NativeString = null
+
+       private var leaf_cache: nullable LeafCache = null
+
        # Empty Rope
-       init do from("")
+       init do root = new StringLeaf("".as(FlatString))
 
        # Creates a new Rope with `s` as root
        init from(s: String) do
-               if s isa RopeString then root = s.root else root = new Leaf(s.as(FlatString))
+               if s isa RopeString then root = s.root else root = new StringLeaf(s.as(FlatString))
        end
 
        private init from_root(r: RopeNode)
@@ -118,23 +143,102 @@ abstract class Rope
        private fun leaves(from: Int): LeavesIterator do return new LeavesIterator(self, from)
 
        # Iterator on the substrings from 0, in forward order
-       fun substrings: IndexedIterator[Text] do return new SubstringsIterator(self, 0)
+       redef fun substrings do return new SubstringsIterator(self, 0)
 
        # Iterator on the substrings, starting at position `from`, in forward order
        fun substrings_from(from: Int): IndexedIterator[Text] do return new SubstringsIterator(self, from)
 
+       # Iterator on the nodes of the rope, in backwards postfix order
+       private fun reverse_postfix(from: Int): ReversePostfix do return new ReversePostfix.from(self, from)
+
+       # Iterator on the leaves of the rope, backwards order
+       private fun reverse_leaves(from: Int): ReverseLeavesIterator do return new ReverseLeavesIterator(self,from)
+
+       # Iterator on the substrings, in reverse order
+       fun reverse_substrings: IndexedIterator[Text] do return new ReverseSubstringsIterator(self, length-1)
+
+       # Iterator on the substrings, in reverse order, starting iteration at position `from`
+       fun reverse_substrings_from(from: Int): IndexedIterator[Text] do return new ReverseSubstringsIterator(self, from)
+
+       redef fun output
+       do
+               for i in substrings do
+                       i.output
+               end
+       end
+
+       redef fun to_cstring
+       do
+               if str_representation != null then return str_representation.as(not null)
+
+               var native_final_str = calloc_string(length + 1)
+
+               native_final_str[length] = '\0'
+
+               if self.length == 0 then
+                       str_representation = native_final_str
+                       return native_final_str
+               end
+
+               var offset = 0
+
+               for i in substrings do
+                       var str = i.flatten
+                       if str isa FlatString then str.items.copy_to(native_final_str, str.length, str.index_from, offset)
+                       offset += i.length
+               end
+
+               str_representation = native_final_str
+
+               return native_final_str
+       end
+
        # Path to the Leaf for `position`
        private fun node_at(position: Int): Path
        do
-               assert position >= 0 and position < length
-               return get_node_from(root.as(not null), 0, position, new List[PathElement])
+               assert position >= 0 and position <= length
+               if position == length then
+                       var st = new List[PathElement]
+                       stack_to_end(root,st)
+                       if not st.is_empty then
+                               var lst = st.last
+                               var lf = lst.node.right
+                               if lf != null then
+                                       return new Path(lf.as(Leaf), lf.length, st)
+                               else
+                                       lf = lst.node.left
+                                       return new Path(lf.as(Leaf), lf.length, st)
+                               end
+                       else
+                               return new Path(root.as(Leaf), length, st)
+                       end
+               end
+               return get_node_from(root, 0, position, new List[PathElement])
+       end
+
+       # Special case for when the required pos is length
+       private fun stack_to_end(nod: RopeNode, st: List[PathElement])
+       do
+               if nod isa Leaf then return
+               var n = nod.as(Concat)
+               var r = n.right
+               var ele = new PathElement(n)
+               ele.right = true
+               st.push(ele)
+               if r != null then
+                       stack_to_end(r, st)
+               end
+               return
        end
 
        # Builds the path to Leaf at position `seek_pos`
        private fun get_node_from(node: RopeNode, curr_pos: Int, seek_pos: Int, stack: List[PathElement]): Path
        do
                assert curr_pos >= 0
-               if node isa Leaf then return new Path(node, seek_pos - curr_pos, stack)
+               if node isa Leaf then
+                       self.leaf_cache = new LeafCache(node, curr_pos)
+                       return new Path(node, seek_pos - curr_pos, stack)
+               end
                node = node.as(Concat)
 
                if node.left != null then
@@ -176,11 +280,13 @@ class RopeString
 
        redef fun empty do return once new RopeString.from("")
 
+       redef var chars: SequenceRead[Char] = new RopeStringChars(self)
+
        redef fun reversed
        do
-               var ret = empty.as(RopeString)
+               var ret = empty
                for i in substrings do
-                       ret = ret.prepend(i.reversed.to_s).as(RopeString)
+                       ret = i.as(String).reversed + ret
                end
                return ret
        end
@@ -189,7 +295,7 @@ class RopeString
        do
                var ret = empty
                for i in substrings do
-                       ret += i.to_upper
+                       ret += i.as(String).to_upper
                end
                return ret
        end
@@ -198,12 +304,23 @@ class RopeString
        do
                var ret = empty
                for i in substrings do
-                       ret += i.to_lower
+                       ret += i.as(String).to_lower
                end
                return ret
        end
 
-       redef fun +(o) do return insert_at(o.to_s, length)
+       redef fun +(o) do
+               if self.length == 0 then return o.to_s
+               if o.length == 0 then return self
+               var str = o.to_s
+               if str isa FlatString then
+                       return new RopeString.from_root(new Concat(root, new StringLeaf(str)))
+               else if str isa RopeString then
+                       return new RopeString.from_root(new Concat(root, str.root))
+               else
+                       abort
+               end
+       end
 
        redef fun *(n)
        do
@@ -215,81 +332,54 @@ class RopeString
        end
 
        # Inserts a String `str` at position `pos`
-       fun insert_at(str: String, pos: Int): RopeString
+       redef fun insert_at(str, pos)
        do
                if str.length == 0 then return self
                if self.length == 0 then return new RopeString.from(str)
 
                assert pos >= 0 and pos <= length
 
-               if pos == length then return append(str).as(RopeString)
-
                var path = node_at(pos)
 
-               var last_concat = new Concat
+               var last_concat: Concat
 
                if path.offset == 0 then
-                       last_concat.right = path.leaf
-                       if str isa FlatString then last_concat.left = new Leaf(str) else last_concat.left = str.as(RopeString).root
+                       if str isa FlatString then
+                               last_concat = new Concat(new StringLeaf(str), path.leaf)
+                       else
+                               last_concat = new Concat(str.as(RopeString).root, path.leaf)
+                       end
                else if path.offset == path.leaf.length then
-                       if str isa FlatString then last_concat.right = new Leaf(str) else last_concat.right = str.as(RopeString).root
-                       last_concat.left = path.leaf
+                       if str isa FlatString then
+                               last_concat = new Concat(path.leaf, new StringLeaf(str))
+                       else
+                               last_concat = new Concat(path.leaf, str.as(RopeString).root)
+                       end
                else
                        var s = path.leaf.str
                        var l_half = s.substring(0, s.length - path.offset)
                        var r_half = s.substring_from(s.length - path.offset)
-                       var cct = new Concat
-                       cct.right = new Leaf(r_half)
-                       last_concat.left = new Leaf(l_half)
-                       if str isa FlatString then last_concat.right = new Leaf(str) else last_concat.right = str.as(RopeString).root
-                       cct.left = last_concat
-                       last_concat = cct
+                       var cct: Concat
+                       var ll = new StringLeaf(l_half.as(FlatString))
+                       if str isa FlatString then
+                               cct = new Concat(ll, new StringLeaf(str))
+                       else
+                               cct = new Concat(ll, str.as(RopeString).root)
+                       end
+                       last_concat = new Concat(cct, new StringLeaf(r_half.as(FlatString)))
                end
 
                for i in path.stack.reverse_iterator do
-                       var nod = new Concat
                        if i.left then
-                               nod.right = i.node.right.as(not null)
-                               nod.left = last_concat
+                               last_concat = new Concat(last_concat, i.node.right)
                        else
-                               nod.left = i.node.left.as(not null)
-                               nod.right = last_concat
+                               last_concat = new Concat(i.node.left, last_concat)
                        end
-                       last_concat = nod
                end
 
                return new RopeString.from_root(last_concat)
        end
 
-       # Adds `s` at the beginning of self
-       fun prepend(s: String): String do return insert_at(s, 0)
-
-       # Adds `s` at the end of self
-       fun append(s: String): String
-       do
-               if self.is_empty then return s
-               return new RopeString.from_root(append_to_path(root,s))
-       end
-
-       # Builds a new path from root to the rightmost node with s appended
-       private fun append_to_path(node: RopeNode, s: String): RopeNode
-       do
-               var cct = new Concat
-               if node isa Leaf then
-                       cct.left = node
-                       if s isa FlatString then cct.right = new Leaf(s) else cct.right = s.as(RopeString).root
-               else if node isa Concat then
-                       var right = node.right
-                       if node.left != null then cct.left = node.left.as(not null)
-                       if right == null then
-                               if s isa FlatString then cct.right = new Leaf(s) else cct.right = s.as(RopeString).root
-                       else
-                               cct.right = append_to_path(right, s)
-                       end
-               end
-               return cct
-       end
-
        # O(log(n))
        #
        #     var rope = new RopeString.from("abcd")
@@ -314,17 +404,13 @@ class RopeString
                var lf = path.leaf
                var offset = path.offset
 
-               if path.leaf.str.length - offset > len then lf = new Leaf(lf.str.substring(offset,len)) else lf = new Leaf(lf.str.substring_from(offset))
+               if path.leaf.str.length - offset > len then lf = new StringLeaf(lf.str.substring(offset,len).as(FlatString)) else lf = new StringLeaf(lf.str.substring_from(offset).as(FlatString))
 
                var nod: RopeNode = lf
 
                for i in path.stack.reverse_iterator do
                        if i.right then continue
-                       var tmp = new Concat
-                       tmp.left = nod
-                       var r = i.node.right
-                       if r != null then tmp.right = r
-                       nod = tmp
+                       nod = new Concat(nod, i.node.right)
                end
 
                var ret = new RopeString
@@ -333,15 +419,11 @@ class RopeString
                path = ret.node_at(len-1)
 
                offset = path.offset
-               nod = new Leaf(path.leaf.str.substring(0, offset+1))
+               nod = new StringLeaf(path.leaf.str.substring(0, offset+1).as(FlatString))
 
                for i in path.stack.reverse_iterator do
                        if i.left then continue
-                       var tmp = new Concat
-                       tmp.right = nod
-                       var l = i.node.left
-                       if l != null then tmp.left = l
-                       nod = tmp
+                       nod = new Concat(i.node.left, nod)
                end
 
                ret.root = nod
@@ -350,6 +432,51 @@ class RopeString
        end
 end
 
+redef class FlatString
+
+       redef fun insert_at(s, pos)
+       do
+
+               if pos == 0 then
+                       var r = new RopeString.from(s)
+                       return r + self
+               end
+               if pos == length then
+                       var r = new RopeString.from(self)
+                       return r + s
+               end
+
+               var l = substring(0,pos)
+               var r = substring_from(pos)
+               var ret: String = new RopeString.from(l)
+               ret += s
+               return ret + r
+       end
+
+end
+
+private class RopeStringChars
+       super SequenceRead[Char]
+
+       var tgt: Rope
+
+       redef fun [](pos)
+       do
+               assert pos < tgt.length
+               if tgt.leaf_cache != null and pos >= tgt.leaf_cache.pos and (tgt.leaf_cache.pos + tgt.leaf_cache.leaf.length) > pos then return tgt.leaf_cache.leaf.str.chars[pos - tgt.leaf_cache.pos]
+               var path = tgt.node_at(pos)
+               return path.leaf.str.chars[path.offset]
+       end
+
+       redef fun iterator do return iterator_from(0)
+
+       redef fun iterator_from(pos) do return new RopeCharIterator(tgt, pos)
+
+       redef fun reverse_iterator do return reverse_iterator_from(tgt.length-1)
+
+       redef fun reverse_iterator_from(pos) do return new ReverseRopeCharIterator(tgt, pos)
+end
+
 # Used to iterate on a Rope
 private class IteratorElement
 
@@ -575,3 +702,189 @@ class RopeCharIterator
        end
 end
 
+private class ReversePostfix
+       super IndexedIterator[RopeNode]
+
+       var target: Rope
+
+       var pos: Int
+
+       var min = 0
+
+       var stack = new List[IteratorElement]
+
+       init from(tgt: Rope, pos: Int)
+       do
+               self.pos = pos
+               target = tgt
+               if pos < 0 or pos >= tgt.length then return
+               var path = tgt.node_at(pos)
+               self.pos -= path.offset
+               for i in path.stack do
+                       var elemt = new IteratorElement(i.node)
+                       elemt.right = true
+                       if i.left then
+                               elemt.left = true
+                       end
+                       stack.push elemt
+               end
+               stack.push(new IteratorElement(path.leaf))
+               stack.last.done = true
+       end
+
+       redef fun item do
+               assert is_ok
+               return stack.last.node
+       end
+
+       redef fun is_ok do return not stack.is_empty
+
+       redef fun index do return pos
+
+       redef fun next
+       do
+               if stack.is_empty then return
+               if pos < min then
+                       stack.clear
+                       return
+               end
+               var lst = stack.last
+               if lst.done then
+                       stack.pop
+                       next
+                       return
+               end
+               if not lst.right then
+                       var nod = lst.node.as(Concat)
+                       var rgt = nod.right
+                       lst.right = true
+                       if rgt != null then
+                               stack.push(new IteratorElement(rgt))
+                               next
+                               return
+                       end
+               end
+               if not lst.left then
+                       var nod = lst.node.as(Concat)
+                       var lft = nod.left
+                       lst.left = true
+                       if lft != null then
+                               stack.push(new IteratorElement(lft))
+                               next
+                               return
+                       end
+               end
+               if lst.node isa Leaf then pos -= lst.node.length
+               lst.done = true
+       end
+end
+
+private class ReverseLeavesIterator
+       super IndexedIterator[Leaf]
+
+       var nodes: ReversePostfix
+
+       init(tgt: Rope, from: Int)
+       do
+               nodes = tgt.reverse_postfix(from)
+       end
+
+       redef fun is_ok do return nodes.is_ok
+
+       redef fun item do
+               assert is_ok
+               return nodes.item.as(Leaf)
+       end
+
+       redef fun next do
+               while nodes.is_ok do
+                       nodes.next
+                       if nodes.is_ok then if nodes.item isa Leaf then break
+               end
+       end
+
+       redef fun index do return nodes.index
+
+end
+
+private class ReverseSubstringsIterator
+       super IndexedIterator[Text]
+
+       var leaves: ReverseLeavesIterator
+
+       var pos: Int
+
+       var str: Text
+
+       init(tgt: Rope, from: Int)
+       do
+               leaves = tgt.reverse_leaves(from)
+               pos = from
+               if not leaves.is_ok then return
+               str = leaves.item.str
+               make_substring
+       end
+
+       fun make_substring
+       do
+               if pos >= (leaves.index + str.length - 1) then return
+               str = str.substring(0, (pos - leaves.index + 1))
+       end
+
+       redef fun is_ok do return leaves.is_ok
+
+       redef fun item do
+               assert is_ok
+               return str
+       end
+
+       redef fun index do return pos
+
+       redef fun next do
+               pos -= str.length
+               leaves.next
+               if not leaves.is_ok then return
+               str = leaves.item.str
+               make_substring
+       end
+end
+
+private class ReverseRopeCharIterator
+       super IndexedIterator[Char]
+
+       var substrs: IndexedIterator[Text]
+
+       var pos: Int
+
+       var subiter: IndexedIterator[Char]
+
+       init(tgt: Rope, from: Int)
+       do
+               substrs = tgt.reverse_substrings_from(from)
+               if not substrs.is_ok then
+                       pos = -1
+                       return
+               end
+               subiter = substrs.item.chars.reverse_iterator
+               pos = from
+       end
+
+       redef fun is_ok do return pos >= 0
+
+       redef fun item do
+               assert is_ok
+               return subiter.item
+       end
+
+       redef fun index do return pos
+
+       redef fun next do
+               pos -= 1
+               if subiter.is_ok then subiter.next
+               if not subiter.is_ok then
+                       if substrs.is_ok then substrs.next
+                       if substrs.is_ok then subiter = substrs.item.chars.reverse_iterator
+               end
+       end
+end
+