about summary refs log tree commit diff
diff options
context:
space:
mode:
authorDavid Rajchenbach-Teller <dteller@mozilla.com>2011-11-04 18:38:42 +0100
committerBrian Anderson <banderson@mozilla.com>2011-11-05 13:04:34 -0700
commita5dcf66ad3760961244477c610e4478853e13abb (patch)
tree34360bca18a85f8705af6f5a067a2de767ffc5f0
parent07574363efc8fa046d197c71a53ac9987c064902 (diff)
stdlib: Added a small rope library
-rw-r--r--src/lib/rope.rs1131
-rw-r--r--src/lib/std.rc2
-rw-r--r--src/test/stdtest/stdtest.rc1
3 files changed, 1134 insertions, 0 deletions
diff --git a/src/lib/rope.rs b/src/lib/rope.rs
new file mode 100644
index 00000000000..0e6560d3c3b
--- /dev/null
+++ b/src/lib/rope.rs
@@ -0,0 +1,1131 @@
+/*
+Module: rope
+
+
+High-level text containers.
+
+Ropes are a high-level representation of text that offers
+much better performance than strings for common operations,
+and generally reduce memory allocations and copies, while only
+entailing a small degradation of less common operations.
+
+More precisely, where a string is represented as a memory buffer,
+a rope is a tree structure whose leaves are slices of immutable
+strings. Therefore, concatenation, appending, prepending, substrings,
+etc. are operations that require only trivial tree manipulation,
+generally without having to copy memory. In addition, the tree
+structure of ropes makes them suitable as a form of index to speed-up
+access to Unicode characters by index in long chunks of text.
+
+The following operations are algorithmically faster in ropes:
+- extracting a subrope is logarithmic (linear in strings);
+- appending/prepending is near-constant time (linear in strings);
+- concatenation is near-constant time (linear in strings);
+- char length is constant-time (linear in strings);
+- access to a character by index is logarithmic (linear in strings);
+ */
+
+
+
+
+/*
+ Type: rope
+
+ The type of ropes.
+ */
+type rope = node::root;
+
+/*
+ Section: Creating a rope
+ */
+
+/*
+ Function:empty
+
+ Create an empty rope
+ */
+fn empty() -> rope {
+   ret node::empty;
+}
+
+/*
+ Function: of_str
+
+ Adopt a string as a rope.
+
+ Parameters:
+
+str - A valid string.
+
+ Returns:
+
+A rope representing the same string as `str`. Depending of the length
+of `str`, this rope may be empty, flat or complex.
+
+Performance notes:
+- this operation does not copy the string;
+- the function runs in linear time.
+ */
+fn of_str(str: @str) -> rope {
+    ret of_substr(str, 0u, str::byte_len(*str));
+}
+
+/*
+Function: of_substr
+
+As `of_str` but for a substring.
+
+Performance note:
+- this operation does not copy the substring.
+
+Parameters:
+
+byte_offset - The offset of `str` at which the rope starts.
+byte_len    - The number of bytes of `str` to use.
+
+Returns:
+
+A rope representing the same string as
+`str::substr(str, byte_offset, byte_len)`.
+Depending on `byte_len`, this rope may be empty, flat or complex.
+
+Safety notes:
+- this function does _not_ check the validity of the substring;
+- this function fails if `byte_offset` or `byte_len` do not match `str`.
+ */
+fn of_substr(str: @str, byte_offset: uint, byte_len: uint) -> rope {
+    if byte_len == 0u { ret node::empty; }
+    ret node::content(node::of_substr(str, byte_offset, byte_len));
+}
+
+/*
+Section: Adding things to a rope
+ */
+
+/*
+Function: append_char
+
+Add one char to the end of the rope
+
+Performance note:
+- this function executes in near-constant time
+ */
+fn append_char(rope: rope, char: char) -> rope {
+    ret append_str(rope, @str::from_chars([char]));
+}
+
+/*
+Function: append_str
+
+Add one string to the end of the rope
+
+Performance note:
+- this function executes in near-linear time
+ */
+fn append_str(rope: rope, str: @str) -> rope {
+    ret append_rope(rope, of_str(str))
+}
+
+/*
+Function: prepend_char
+
+Add one char to the beginning of the rope
+
+Performance note:
+- this function executes in near-constant time
+ */
+fn prepend_char(rope: rope, char: char) -> rope {
+    ret prepend_str(rope, @str::from_chars([char]));
+}
+
+/*
+Function: prepend_str
+
+Add one string to the beginning of the rope
+
+Performance note:
+- this function executes in near-linear time
+ */
+fn prepend_str(rope: rope, str: @str) -> rope {
+    ret append_rope(of_str(str), rope)
+}
+
+/*
+Function: append_rope
+
+Concatenate two ropes
+ */
+fn append_rope(left: rope, right: rope) -> rope {
+   alt(left) {
+     node::empty. { ret right; }
+     node::content(left_content) {
+       alt(right) {
+         node::empty. { ret left; }
+     node::content(right_content) {
+           ret node::content(node::concat2(left_content, right_content));
+     }
+       }
+     }
+   }
+}
+
+/*
+Function: concat
+
+Concatenate many ropes
+ */
+fn concat(v: [rope]) -> rope {
+   let acc = node::empty;
+   for r: rope in v {
+       acc = append_rope(acc, r);
+   }
+   ret bal(acc);
+}
+
+
+/*
+Section: Keeping ropes healthy
+ */
+
+
+
+/*
+Function: bal
+
+Balance a rope.
+
+Returns:
+
+A copy of the rope in which small nodes have been grouped in memory,
+and with a reduced height.
+
+If you perform numerous rope concatenations, it is generally a good idea
+to rebalance your rope at some point, before using it for other purposes.
+ */
+fn bal(rope:rope) -> rope {
+    alt(rope) {
+      node::empty. { ret rope }
+      node::content(x) {
+        alt(node::bal(x)) {
+          option::none.   { rope }
+          option::some(y) { node::content(y) }
+        }
+      }
+    }
+}
+
+/*
+Section: Transforming ropes
+ */
+
+
+/*
+Function: sub_chars
+
+Extract a subrope from a rope.
+
+Performance note:
+- on a balanced rope, this operation takes algorithmic time;
+- this operation does not involve any copying
+
+Safety note:
+- this function fails if char_offset/char_len do not represent
+valid positions in rope
+ */
+fn sub_chars(rope: rope, char_offset: uint, char_len: uint) -> rope {
+    if char_len == 0u { ret node::empty; }
+    alt(rope) {
+      node::empty. { fail }
+      node::content(node) {
+        if char_len > node::char_len(node) { fail }
+        else {
+            ret node::content(node::sub_chars(node, char_offset, char_len))
+        }
+      }
+    }
+}
+
+/*
+Function:sub_bytes
+
+Extract a subrope from a rope.
+
+Performance note:
+- on a balanced rope, this operation takes algorithmic time;
+- this operation does not involve any copying
+
+Safety note:
+- this function fails if byte_offset/byte_len do not represent
+valid positions in rope
+ */
+fn sub_bytes(rope: rope, byte_offset: uint, byte_len: uint) -> rope {
+    if byte_len == 0u { ret node::empty; }
+    alt(rope) {
+      node::empty. { fail }
+      node::content(node) {
+        if byte_len > node::byte_len(node) { fail }
+        else {
+            ret node::content(node::sub_bytes(node, byte_offset, byte_len))
+        }
+      }
+    }
+}
+
+/*
+Section: Comparing ropes
+ */
+
+/*
+Function: cmp
+
+Compare two ropes by Unicode lexicographical order.
+
+This function compares only the contents of the rope, not their structure.
+
+Returns:
+
+A negative value if `left < right`, 0 if eq(left, right) or a positive
+value if `left > right`
+ */
+fn cmp(left: rope, right: rope) -> int {
+    alt((left, right)) {
+      (node::empty., node::empty.) { ret 0; }
+      (node::empty., _)     { ret -1;}
+      (_, node::empty.)     { ret  1;}
+      (node::content(a), node::content(b)) {
+        ret node::cmp(a, b);
+      }
+    }
+}
+
+fn eq(left: rope, right: rope) -> bool {
+    ret cmp(left, right) == 0;
+}
+
+fn leq(left: rope, right: rope) -> bool {
+    ret cmp(left, right) <= 0;
+}
+
+fn lt(left: rope, right: rope) -> bool {
+    ret cmp(left, right) < 0;
+}
+
+fn geq(left: rope, right: rope) -> bool {
+    ret cmp(left, right) >= 0;
+}
+
+fn gt(left: rope, right: rope) -> bool {
+    ret cmp(left, right) > 0;
+}
+
+/*
+Section: Iterating
+ */
+
+/*
+Function: loop_chars
+
+Loop through a rope, char by char
+
+While other mechanisms are available, this is generally the best manner
+of looping through the contents of a rope char by char. If you prefer a
+loop that iterates through the contents string by string (e.g. to print
+the contents of the rope or output it to the system), however,
+you should rather use `traverse_components`.
+
+Parameters:
+rope - A rope to traverse. It may be empty.
+it - A block to execute with each consecutive character of the rope.
+Return `true` to continue, `false` to stop.
+
+Returns:
+
+`true` If execution proceeded correctly, `false` if it was interrupted,
+that is if `it` returned `false` at any point.
+ */
+fn loop_chars(rope: rope, it: block(char) -> bool) -> bool {
+   alt(rope) {
+      node::empty. { ret true }
+      node::content(x) { ret node::loop_chars(x, it) }
+   }
+}
+fn iter_chars(rope: rope, it: block(char)) {
+    loop_chars(rope) {|x|
+        it(x);
+        ret true
+    }
+}
+
+/*
+Function: loop_leaves
+
+Loop through a rope, string by string
+
+While other mechanisms are available, this is generally the best manner of
+looping through the contents of a rope string by string, which may be useful
+e.g. to print strings as you see them (without having to copy their
+contents into a new string), to send them to then network, to write them to
+a file, etc.. If you prefer a loop that iterates through the contents
+char by char (e.g. to search for a char), however, you should rather
+use `traverse`.
+
+Parameters:
+
+rope - A rope to traverse. It may be empty.
+it - A block to execute with each consecutive string component of the rope.
+Return `true` to continue, `false` to stop.
+
+Returns:
+
+`true` If execution proceeded correctly, `false` if it was interrupted,
+that is if `it` returned `false` at any point.
+ */
+fn loop_leaves(rope: rope, it: block(node::leaf) -> bool) -> bool{
+   alt(rope) {
+      node::empty. { ret true }
+      node::content(x) {ret node::loop_leaves(x, it)}
+   }
+}
+
+mod iterator {
+    mod leaf {
+        fn start(rope: rope) -> node::leaf_iterator::t {
+            alt(rope) {
+              node::empty.     { ret node::leaf_iterator::empty() }
+              node::content(x) { ret node::leaf_iterator::start(x) }
+            }
+        }
+        fn next(it: node::leaf_iterator::t) -> option::t<node::leaf> {
+            ret node::leaf_iterator::next(it);
+        }
+    }
+    mod char {
+        fn start(rope: rope) -> node::char_iterator::t {
+            alt(rope) {
+              node::empty.   { ret node::char_iterator::empty() }
+              node::content(x) { ret node::char_iterator::start(x) }
+            }
+        }
+        fn next(it: node::char_iterator::t) -> option::t<char> {
+            ret node::char_iterator::next(it)
+        }
+    }
+}
+
+/*
+ Section: Rope properties
+ */
+
+/*
+ Function: height
+
+ Returns: The height of the rope, i.e. a bound on the number of
+operations which must be performed during a character access before
+finding the leaf in which a character is contained.
+
+ Performance note: Constant time.
+*/
+fn height(rope: rope) -> uint {
+   alt(rope) {
+      node::empty.    { ret 0u; }
+      node::content(x) { ret node::height(x); }
+   }
+}
+
+
+
+/*
+ Function: char_len
+
+ Returns: The number of character in the rope
+
+ Performance note: Constant time.
+ */
+pure fn char_len(rope: rope) -> uint {
+   alt(rope) {
+     node::empty.           { ret 0u; }
+     node::content(x)       { ret node::char_len(x) }
+   }
+}
+
+/*
+ Function: char_len
+
+ Returns: The number of bytes in the rope
+
+ Performance note: Constant time.
+ */
+pure fn byte_len(rope: rope) -> uint {
+   alt(rope) {
+     node::empty.           { ret 0u; }
+     node::content(x)       { ret node::byte_len(x) }
+   }
+}
+
+/*
+ Function: char_at
+
+ Parameters:
+  pos - A position in the rope
+
+ Returns: The character at position `pos`
+
+ Safety notes: The function will fail if `pos`
+ is not a valid position in the rope.
+
+ Performance note: This function executes in a time
+ proportional to the height of the rope + the (bounded)
+ length of the largest leaf.
+ */
+fn char_at(rope: rope, pos: uint) -> char {
+   alt(rope) {
+      node::empty. { fail }
+      node::content(x) { ret node::char_at(x, pos) }
+   }
+}
+
+
+/*
+ Section: Implementation
+*/
+mod node {
+
+    /*
+     Enum: node::root
+
+     Implementation of type `rope`
+
+     Constants:
+       empty   - An empty rope
+       content - A non-empty rope
+    */
+    tag root {
+        empty;
+        content(@node);
+    }
+
+    /*
+     Struct: node::leaf
+
+     A text component in a rope.
+
+     This is actually a slice in a rope, so as to ensure maximal sharing.
+    */
+    type leaf = {
+
+    /*
+     Field: byte_offset
+
+     The number of bytes skipped in `content`
+    */
+    byte_offset: uint,
+
+    /*
+     Field: byte_len
+
+     The number of bytes of `content` to use
+    */
+    byte_len:    uint,
+
+    /*
+     Field: char_len
+
+
+     The number of chars in the leaf.
+    */
+    char_len:   uint,
+
+    /*
+    Field: content
+
+    Contents of the leaf.
+
+    Note that we can have `char_len < str::char_len(content)`, if this
+    leaf is only a subset of the string. Also note that the string
+    can be shared between several ropes, e.g. for indexing purposes.
+    */
+    content:    @str
+    };
+
+
+    /*
+     Struct node::concat
+
+     A node obtained from the concatenation of two other nodes
+    */
+    type concat = {
+
+        /*
+        Field: left
+
+        The node containing the beginning of the text.
+        */
+        left:     @node,//TODO: Perhaps a `vec` instead of `left`/`right`
+
+        /*
+        Field: right
+
+        The node containing the end of the text.
+        */
+        right:    @node,
+
+        /*
+        Field: char_len
+
+        The number of chars contained in all leaves of this node.
+        */
+        char_len: uint,
+
+        /*
+        Field: byte_len
+
+        The number of bytes in the subrope.
+
+        Used to pre-allocate the correct amount of storage for serialization.
+        */
+        byte_len: uint,
+
+        /*
+        Field: height
+
+        Height of the subrope.
+
+        Used for rebalancing and to allocate stacks for
+        traversals.
+        */
+        height:   uint
+    };
+
+    /*
+    Enum: node::node
+
+    leaf - A leaf consisting in a `str`
+    concat - The concatenation of two ropes
+    */
+    tag node {
+        leaf(leaf);
+        concat(concat);
+    }
+
+    /*
+    The maximal number of chars that _should_ be permitted in a single node.
+
+    This is not a strict value
+     */
+    const hint_max_leaf_char_len: uint = 256u;
+
+    /*
+    The maximal height that _should_ be permitted in a tree.
+
+    This is not a strict value
+     */
+    const hint_max_node_height:   uint = 16u;
+
+    /*
+    Adopt a string as a node.
+
+    If the string is longer than `max_leaf_char_len`, it is
+    logically split between as many leaves as necessary. Regardless,
+    the string itself is not copied.
+
+    Performance note: The complexity of this function is linear in
+    the length of `str`.
+     */
+    fn of_str(str: @str) -> @node {
+        ret of_substr(str, 0u, str::byte_len(*str));
+    }
+
+    /*
+    Adopt a slice of a string as a node.
+
+    If the slice is longer than `max_leaf_char_len`, it is logically split
+    between as many leaves as necessary. Regardless, the string itself
+    is not copied.
+
+    @param byte_start The byte offset where the slice of `str` starts.
+    @param byte_len   The number of bytes from `str` to use.
+     */
+    fn of_substr(str: @str, byte_start: uint, byte_len: uint) -> @node {
+        assert (byte_len > 0u);
+        let char_len = str::char_len_range(*str, byte_start, byte_len);
+        let candidate = @leaf({
+                byte_offset: byte_start,
+                byte_len:    byte_len,
+                char_len:    char_len,
+                content:     str});
+        if char_len <= hint_max_leaf_char_len {
+            ret candidate;
+        } else {
+            //Firstly, split `str` in slices of hint_max_leaf_char_len
+            let leaves = uint::div_ceil(char_len, hint_max_leaf_char_len);
+            //Number of leaves
+            let nodes  = vec::init_elt_mut(candidate, leaves);
+
+            let i = 0u;
+            let offset = byte_start;
+            let first_leaf_char_len =
+                if char_len%hint_max_leaf_char_len == 0u {
+                  hint_max_leaf_char_len
+                } else {
+                char_len%hint_max_leaf_char_len
+               };
+            while i < leaves {
+                let chunk_char_len: uint =
+                    if i == 0u  { first_leaf_char_len }
+                    else { hint_max_leaf_char_len };
+                let chunk_byte_len =
+                    str::byte_len_range(*str, offset, chunk_char_len);
+                nodes[i] = @leaf({
+                    byte_offset: offset,
+                    byte_len:    chunk_byte_len,
+                    char_len:    chunk_char_len,
+                    content:     str
+                });
+
+                offset += chunk_byte_len;
+                i      += 1u;
+            }
+
+            //Then, build a tree from these slices by collapsing them
+            while leaves > 1u {
+                i = 0u;
+                while i < leaves - 1u {//Concat nodes 0 with 1, 2 with 3 etc.
+                    nodes[i/2u] = concat2(nodes[i], nodes[i + 1u]);
+                    i += 2u;
+                }
+                if i == leaves - 1u {
+                    //And don't forget the last node if it is in even position
+                    nodes[i/2u] = nodes[i];
+                }
+                leaves = uint::div_ceil(leaves, 2u);
+            }
+            ret nodes[0u];
+        }
+    }
+
+    pure fn byte_len(node: @node) -> uint {
+        alt(*node) {//TODO: Could we do this without the pattern-matching?
+          leaf(y)  { ret y.byte_len; }
+          concat(y){ ret y.byte_len; }
+        }
+    }
+
+    pure fn char_len(node: @node) -> uint {
+        alt(*node) {
+          leaf(y)   { ret y.char_len; }
+          concat(y) { ret y.char_len; }
+        }
+    }
+
+
+    fn tree_from_forest_destructive(forest: [mutable @node]) -> @node {
+        let i = 0u;
+        let len = vec::len(forest);
+        while len > 1u {
+            i = 0u;
+            while i < len - 1u {//Concat nodes 0 with 1, 2 with 3 etc.
+                let left  = forest[i];
+                let right = forest[i+1u];
+                let left_len = char_len(left);
+                let right_len= char_len(right);
+                let left_height= height(left);
+                let right_height=height(right);
+                if left_len + right_len > hint_max_leaf_char_len
+                    //TODO: Improve strategy
+                    || left_height  >= hint_max_node_height
+                    || right_height >= hint_max_node_height {
+                    if left_len <= hint_max_leaf_char_len {
+                        left = flatten(left);
+                    }
+                    if right_len <= hint_max_leaf_char_len {
+                        right = flatten(right);
+                    }
+                }
+                forest[i/2u] = concat2(left, right);
+                i += 2u;
+            }
+            if i == len - 1u {
+                //And don't forget the last node if it is in even position
+                forest[i/2u] = forest[i];
+            }
+            len = uint::div_ceil(len, 2u);
+        }
+        ret forest[0];
+    }
+
+    fn serialize_node(node: @node) -> str unsafe {
+        let buf = vec::init_elt_mut(0u8, byte_len(node));
+        let offset = 0u;//Current position in the buffer
+        let it = leaf_iterator::start(node);
+        while true {
+            alt(leaf_iterator::next(it)) {
+              option::none. { break; }
+              option::some(x) {
+                //TODO: Replace with memcpy or something similar
+                let local_buf: [u8] = unsafe::reinterpret_cast(*x.content);
+                let i = x.byte_offset;
+                while i < x.byte_len {
+                    buf[offset] = local_buf[i];
+                    offset += 1u;
+                    i      += 1u;
+                }
+                unsafe::leak(local_buf);
+              }
+            }
+        }
+        let str : str = unsafe::reinterpret_cast(buf);
+        unsafe::leak(buf);//TODO: Check if this is correct
+        ret str;
+    }
+
+    /*
+    Replace a subtree by a single leaf with the same contents.
+     */
+    fn flatten(node: @node) -> @node unsafe {
+        alt(*node) {
+          leaf(_) { ret node }
+          concat(x) {
+            ret @leaf({
+                byte_offset: 0u,
+                byte_len:    x.byte_len,
+                char_len:    x.char_len,
+                content:     @serialize_node(node)
+            })
+          }
+        }
+    }
+
+    fn bal(node: @node) -> option::t<@node> {
+        if height(node) < hint_max_node_height { ret option::none }
+        else {
+            //1. Gather all leaves as a forest
+            let forest = [mutable];
+            let it = leaf_iterator::start(node);
+            while true {
+                alt (leaf_iterator::next(it)) {
+                  option::none.   { break; }
+                  option::some(x) { forest += [mutable @leaf(x)]; }
+                }
+            }
+            //2. Rebuild tree from forest
+            let root = @*tree_from_forest_destructive(forest);
+            ret option::some(root);
+        }
+    }
+
+    fn sub_bytes(node: @node, byte_offset: uint, byte_len: uint) -> @node {
+        let node        = node;
+        let result      = node;//Arbitrary value
+        let byte_offset = byte_offset;
+        let byte_len    = byte_len;
+        while true {
+            if byte_offset == 0u && byte_len == node::byte_len(node) {
+                result = node;
+                break;
+            }
+            alt(*node) {
+              node::leaf(x) {
+                let char_len =
+                    str::char_len_range(*x.content, byte_offset, byte_len);
+                result = @leaf({byte_offset: byte_offset,
+                                byte_len:    byte_len,
+                                char_len:    char_len,
+                                content:     x.content});
+                break;
+              }
+              node::concat(x) {
+                let left_len: uint = node::byte_len(x.left);
+                if byte_offset <= left_len {
+                    if byte_offset + byte_len <= left_len {
+                   //Case 1: Everything fits in x.left, tail-call
+                        node = x.left;
+                    } else {
+                        //Case 2: A (non-empty, possibly full) suffix
+                        //of x.left and a (non-empty, possibly full) prefix
+                        //of x.right
+                        let left_result  =
+                            sub_bytes(x.left, byte_offset, left_len);
+                        let right_result =
+                            sub_bytes(x.right, 0u, left_len - byte_offset);
+                        result = concat2(left_result, right_result);
+                        break;
+                    }
+                } else {
+                    //Case 3: Everything fits in x.right
+                    byte_offset -= left_len;
+                    node = x.right;
+                }
+              }
+            }
+        }
+        ret result;
+    }
+
+    fn sub_chars(node: @node, char_offset: uint, char_len: uint) -> @node {
+        alt(*node) {
+          node::leaf(x) {
+            if char_offset == 0u && char_len == x.char_len {
+                ret node;
+            }
+            let byte_offset =
+                str::byte_len_range(*x.content, 0u, char_offset);
+            let byte_len    =
+                str::byte_len_range(*x.content, byte_offset, char_len);
+            ret @leaf({byte_offset: byte_offset,
+                 byte_len:    byte_len,
+                 char_len:    char_len,
+                 content:     x.content});
+          }
+          node::concat(x) {
+            if char_offset == 0u && char_len == x.char_len {ret node;}
+            let left_len : uint = node::char_len(x.left);
+            if char_offset <= left_len {
+                if char_offset + char_len <= left_len {
+                    //Case 1: Everything fits in x.left
+                    ret sub_chars(x.left, char_offset, char_len);
+                    //TODO: Optimize manually this tail call?
+                } else {
+                    //Case 2: A (non-empty, possibly full) suffix
+                    //of x.left and a (non-empty, possibly full) prefix
+                    //of x.right
+                    let left_result  =
+                        sub_chars(x.left, char_offset, left_len);
+                    let right_result =
+                        sub_chars(x.right, 0u, left_len - char_offset);
+                    ret concat2(left_result, right_result)
+                }
+            } else {
+                //Case 3: Everything fits in x.right
+                ret sub_chars(x.right, char_offset - left_len, char_len);
+                //TODO: Optimize manually this tail call?
+            }
+          }
+        }
+    }
+
+    fn concat2(left: @node, right: @node) -> @node {
+        ret @concat({left    : left,
+                     right   : right,
+             char_len: char_len(left) + char_len(right),
+                     byte_len: byte_len(left) + byte_len(right),
+             height: math::max(height(left), height(right)) + 1u
+                    })
+    }
+
+    fn height(node: @node) -> uint {
+        alt(*node) {
+          leaf(_)   { ret 0u; }
+          concat(x) { ret x.height; }
+        }
+    }
+
+    fn cmp(a: @node, b: @node) -> int {
+        let ita = char_iterator::start(a);
+        let itb = char_iterator::start(b);
+        let result = 0;
+        let pos = 0u;
+        while result == 0 {
+            alt((char_iterator::next(ita), char_iterator::next(itb))) {
+              (option::none., option::none.) {
+                break;
+              }
+              (option::some(chara), option::some(charb)) {
+                result = char::cmp(chara, charb);
+              }
+              (option::some(_), _)         {
+                result = 1;
+              }
+              (_, option::some(_))         {
+                result = -1;
+              }
+            }
+            pos += 1u;
+        }
+        ret result;
+    }
+
+    fn loop_chars(node: @node, it: block(char) -> bool) -> bool {
+        ret loop_leaves(node, {|leaf|
+            ret str::loop_chars_sub(*leaf.content,
+                                    leaf.byte_offset,
+                                    leaf.byte_len, it)
+        })
+    }
+
+    fn loop_leaves(node: @node, it: block(leaf) -> bool) -> bool{
+        let result  = true;
+        let current = node;
+        while true {
+            alt(*current) {
+              leaf(x) {
+                result = it(x);
+                break;
+              }
+              concat(x) {
+                if loop_leaves(x.left, it) { //non tail call
+                    current = x.right;       //tail call
+                } else {
+                    result = false;
+                    break;
+                }
+              }
+            }
+        }
+        ret result;
+    }
+
+   fn char_at(node: @node, pos: uint) -> char {
+       let node    = node;
+       let pos     = pos;
+       let result  = '0';
+       while true {
+           alt(*node) {
+             leaf(x) {
+               result = str::char_at(*x.content, pos);
+               break;
+             }
+             concat({left: left,
+                     right: right,
+                     char_len: _,
+                     byte_len: _,
+                     height: _}) {
+               let left_len = char_len(left);
+           if left_len > pos {
+                   node = left;
+           } else {
+                   node = right;
+                   pos  = pos - left_len;
+           }
+             }
+           }
+       };
+       ret result;
+   }
+
+    mod leaf_iterator {
+        type t = {
+            stack:            [mutable @node],
+            mutable stackpos: int
+        };
+
+        fn empty() -> t {
+            let stack : [mutable @node] = [mutable];
+            ret {stack: stack, mutable stackpos: -1}
+        }
+
+        fn start(node: @node) -> t {
+            let stack = vec::init_elt_mut(node, height(node)+1u);
+            ret {
+                stack:             stack,
+                mutable stackpos:  0
+            }
+        }
+
+        fn next(it: t) -> option::t<leaf> {
+            if it.stackpos < 0 { ret option::none; }
+            let result  = option::none;
+            while true {
+                let current = it.stack[it.stackpos];
+                it.stackpos -= 1;
+                alt(*current) {
+                  concat(x) {
+                    it.stackpos += 1;
+                    it.stack[it.stackpos] = x.right;
+                    it.stackpos += 1;
+                    it.stack[it.stackpos] = x.left;
+                  }
+                  leaf(x) {
+                    result    = option::some(x);
+                    break;
+                  }
+                }
+            }
+            ret result;
+        }
+    }
+
+    mod char_iterator {
+        type t = {
+            leaf_iterator: leaf_iterator::t,
+            mutable leaf:  option::t<leaf>,
+            mutable leaf_byte_pos: uint
+        };
+
+        fn start(node: @node) -> t {
+            ret {
+                leaf_iterator: leaf_iterator::start(node),
+                mutable leaf:          option::none,
+                mutable leaf_byte_pos: 0u
+            }
+        }
+
+        fn empty() -> t {
+            ret {
+                leaf_iterator: leaf_iterator::empty(),
+                mutable leaf:  option::none,
+                mutable leaf_byte_pos: 0u
+            }
+        }
+
+        fn next(it: t) -> option::t<char> {
+            let result = option::none;
+            while true {
+                alt(get_current_or_next_leaf(it)) {
+                  option::none. {
+                    break;
+                  }
+                  option::some(leaf) {
+                    let next_char = get_next_char_in_leaf(it);
+                    alt(next_char) {
+                      option::none. {
+                        cont;
+                      }
+                      option::some(_) {
+                        result = next_char;
+                        break;
+                      }
+                    }
+                  }
+                }
+            }
+            ret result;
+        }
+
+        fn get_current_or_next_leaf(it: t) -> option::t<leaf> {
+            alt(it.leaf) {
+              option::some(_) { ret it.leaf }
+              option::none. {
+                let next = leaf_iterator::next(it.leaf_iterator);
+                alt(next) {
+                  option::none. { ret option::none }
+                  option::some(leaf) {
+                    it.leaf          = next;
+                    it.leaf_byte_pos = 0u;
+                    ret next;
+                  }
+                }
+              }
+            }
+        }
+
+        fn get_next_char_in_leaf(it: t) -> option::t<char> {
+            alt(it.leaf) {
+              option::none. { ret option::none }
+              option::some(leaf) {
+                if it.leaf_byte_pos >= leaf.byte_len {
+                    //We are actually past the end of the leaf
+                    it.leaf = option::none;
+                    ret option::none
+                } else {
+                    let {ch, next} =
+                        str::char_range_at(*leaf.content,
+                                     it.leaf_byte_pos + leaf.byte_offset);
+                    it.leaf_byte_pos = next - leaf.byte_offset;
+                    ret option::some(ch)
+                }
+              }
+            }
+        }
+    }
+}
+
diff --git a/src/lib/std.rc b/src/lib/std.rc
index 75b3798ae6c..6906b7f7f83 100644
--- a/src/lib/std.rc
+++ b/src/lib/std.rc
@@ -11,6 +11,7 @@ export box, char, float, int, str, ptr, uint, u8, u32, u64, vec;
 export aio, comm, fs, io, net, run, sio, sys, task;
 export ctypes, either, option, result, util;
 export bitv, deque, fun_treemap, list, map, smallintmap, sort, treemap, ufind;
+export rope;
 export ebml, dbg, getopts, math, rand, sha1, term, time, unsafe;
 export extfmt, test;
 // FIXME: generic_os and os_fs shouldn't be exported
@@ -61,6 +62,7 @@ mod deque;
 mod fun_treemap;
 mod list;
 mod map;
+mod rope;
 mod smallintmap;
 mod sort;
 mod treemap;
diff --git a/src/test/stdtest/stdtest.rc b/src/test/stdtest/stdtest.rc
index 501879c540f..18ba19d0151 100644
--- a/src/test/stdtest/stdtest.rc
+++ b/src/test/stdtest/stdtest.rc
@@ -33,6 +33,7 @@ mod uint;
 mod float;
 mod math;
 mod result;
+mod rope;
 
 // Local Variables:
 // mode: rust