lib: prepare for new constructors
[nit.git] / lib / standard / ropes.nit
index 54e6050..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,25 +56,26 @@ 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
 
@@ -90,6 +96,15 @@ private class StringLeaf
                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.
@@ -104,8 +119,10 @@ abstract class Rope
        # 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
@@ -179,15 +196,49 @@ abstract class Rope
        # 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
@@ -233,9 +284,9 @@ class RopeString
 
        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
@@ -244,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
@@ -253,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
@@ -277,74 +339,47 @@ class RopeString
 
                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 StringLeaf(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 StringLeaf(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 StringLeaf(r_half.as(FlatString))
-                       last_concat.left = new StringLeaf(l_half.as(FlatString))
-                       if str isa FlatString then last_concat.right = new StringLeaf(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
-       redef fun prepend(s) do return insert_at(s, 0)
-
-       # Adds `s` at the end of self
-       redef fun append(s)
-       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 StringLeaf(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 StringLeaf(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")
@@ -375,11 +410,7 @@ class RopeString
 
                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
@@ -392,11 +423,7 @@ class RopeString
 
                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
@@ -407,14 +434,17 @@ end
 
 redef class FlatString
 
-       redef fun append(s) do return (new RopeString.from(self)) + s
-
-       redef fun prepend(s) do return (new RopeString.from(self)).prepend(s)
-
        redef fun insert_at(s, pos)
        do
-               if pos == 0 then return prepend(s)
-               if pos == length then return append(s)
+
+               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)
@@ -433,6 +463,7 @@ private class RopeStringChars
        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