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)
if r != null then length += r.length
end
+ redef fun to_leaf
+ do
+ 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
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("")
# 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
assert pos >= 0 and pos <= length
- if pos == length then return append(str).as(RopeString)
-
var path = node_at(pos)
var last_concat: Concat
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: Concat
- if node isa Leaf then
- if s isa FlatString then
- cct = new Concat(node, new StringLeaf(s))
- else
- cct = new Concat(node, s.as(RopeString).root)
- end
- else
- var n = node.as(Concat)
- var right = n.right
- var lft = n.left.as(not null)
- if right == null then
- if s isa FlatString then
- cct = new Concat(lft, new StringLeaf(s))
- else
- cct = new Concat(lft, s.as(RopeString).root)
- end
- else
- cct = new Concat(lft, append_to_path(right, s))
- end
- end
- return cct
- end
-
# O(log(n))
#
# var rope = new RopeString.from("abcd")
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)
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