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)
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.
# 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("")
# 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)
# 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
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
do
var ret = empty
for i in substrings do
- ret += i.to_upper
+ ret += i.as(String).to_upper
end
return ret
end
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
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.as(FlatString))
- last_concat.left = new Leaf(l_half.as(FlatString))
- 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")
var lf = path.leaf
var offset = path.offset
- if path.leaf.str.length - offset > len then lf = new Leaf(lf.str.substring(offset,len).as(FlatString)) else lf = new Leaf(lf.str.substring_from(offset).as(FlatString))
+ 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
path = ret.node_at(len-1)
offset = path.offset
- nod = new Leaf(path.leaf.str.substring(0, offset+1).as(FlatString))
+ 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
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]
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