--- /dev/null
+exception Out_of_bounds of string
+
+type rope =
+ | Leaf of string * int * int
+ | Node of rope * rope * int
+
+type t = rope
+
+let empty = Leaf ("", 0, 0)
+
+let length = function
+ | Leaf (_,_,l) -> l
+ | Node (_,_,l) -> l
+
+let from_string s =
+ Leaf (s, 0, (String.length s))
+
+let check_index rope i =
+ if i < 0 || i >= (length rope) then
+ raise (Out_of_bounds "Rope.check_index")
+
+let limit_index rope i =
+ if i < 0 then 0
+ else if i > 0 && i >= (length rope) then
+ ((length rope) - 1)
+ else i
+
+let join left right =
+ let left_len = (length left) in
+ let right_len = (length right) in
+ if left_len == 0 then right
+ else if right_len == 0 then left
+ else Node (left, right, (length left) + (length right))
+
+let rec split rope i =
+ if i < 0 || i > (length rope) then
+ raise (Out_of_bounds "Rope.split");
+ match rope with
+ | Leaf (s,off,len) ->
+ (Leaf (s, off, i), Leaf (s, (off + i), len - (i)))
+ | Node (l,r,len) ->
+ let left_len = (length l) in
+ if i < left_len then
+ let (sl,sr) = (split l i) in
+ (sl, (join sr r))
+ else
+ let (sl,sr) = (split r i) in
+ ((join l sl), sr)
+
+let rec getc rope i =
+ check_index rope i;
+ match rope with
+ | Leaf (s,off,_) -> (Char.code s.[off + i])
+ | Node (l,r,len) ->
+ let left_len = (length l) in
+ if i < left_len then
+ getc l i
+ else
+ getc r (i - left_len)
+
+let last rope =
+ limit_index rope ((length rope) - 1)
+
+let is_bol rope pos =
+ if pos == 0 then true
+ else ((getc rope (pos-1)) == 0x0A)
+
+let is_eol rope pos =
+ if pos >= (last rope) then true
+ else let c = (getc rope (pos+1)) in
+ (c == 0x0A || c == 0x0D)
+
+let is_bow rope pos = false
+
+let is_eow rope pos = false
+
+let rec move_till step testfn rope pos =
+ if (testfn rope pos) then pos
+ else (move_till step testfn rope (pos + step))
+
+let to_bol rope pos =
+ move_till (-1) is_bol rope pos
+
+let to_eol rope pos =
+ move_till (+1) is_eol rope pos
+
+let to_bow rope pos =
+ move_till (-1) is_bow rope pos
+
+let to_eow rope pos =
+ move_till (+1) is_eow rope pos
+
+let rec puts rope s i =
+ let (left,right) = split rope i in
+ let middle = from_string s in
+ (join (join left middle) right)
+
+let del rope i j =
+ let (l_left,l_right) = split rope i in
+ let (r_left,r_right) = split l_right (j - i) in
+ (join l_left r_right)
+
+let rec iter_from fn rope pos =
+ if pos < (length rope) && (fn (getc rope pos)) then
+ iter_from fn rope (pos + 1)
+
+let rec iteri_from fn rope pos =
+ if pos < (length rope) && (fn pos (getc rope pos)) then
+ iteri_from fn rope (pos + 1)
+
+let iteri fn rope =
+ iteri_from (fun i c -> (fn i c); true) rope 0
+
+let iter fn rope =
+ iter_from (fun c -> (fn c); true) rope 0
+
+let map fn rope =
+ let buf = Bytes.create (length rope) in
+ iteri (fun i c -> Bytes.set buf i (fn c)) rope;
+ from_string (Bytes.unsafe_to_string buf)
+
+let mapi fn rope =
+ let buf = Bytes.create (length rope) in
+ iteri (fun i c -> Bytes.set buf i (fn i c)) rope;
+ from_string (Bytes.unsafe_to_string buf)
+
+let gets rope i j =
+ let buf = Bytes.create (j - i) in
+ iteri_from
+ (fun n c ->
+ Bytes.set buf (n - i) (Char.chr (getc rope i));
+ (n <= j))
+ rope i;
+ Bytes.unsafe_to_string buf
+
+let to_string rope =
+ gets rope 0 (length rope)
+
+(* Unit Tests *****************************************************************)
+
+let run_unit_tests () =
+ let open Test in
+ (* length() tests *)
+ test "length : 0 for empty string" (fun () ->
+ let rope = Leaf("", 0, 0) in
+ assert( length rope == 0 )
+ );
+ test "length : equal to length of leaf" (fun () ->
+ let rope = Leaf("a", 0, 1) in
+ assert( length rope == 1 )
+ );
+ test "length : equal to sum of leaf lengths" (fun () ->
+ let rope = (join (Leaf("a", 0, 1)) (Leaf("a", 0, 1))) in
+ assert( length rope == 2 )
+ );
+
+ (* join() tests *)
+ test "join : join two leaves into rope" (fun () ->
+ let left = Leaf("a", 0, 1) in
+ let right = Leaf("a", 0, 1) in
+ let rope = (join left right) in
+ assert( match rope with
+ | Node (l,r,2) -> (l == left && r == right)
+ | _ -> false)
+ );
+ test "join : join a rope with a leaf (l to r)" (fun () ->
+ let left = join (Leaf("a", 0, 1)) (Leaf("a", 0, 1)) in
+ let right = Leaf("a", 0, 1) in
+ let rope = (join left right) in
+ assert( match rope with
+ | Node (l,r,3) -> (l == left && r == right)
+ | _ -> false)
+ );
+ test "join : join a rope with a leaf (r to l)" (fun () ->
+ let left = Leaf("a", 0, 1) in
+ let right = join (Leaf("a", 0, 1)) (Leaf("a", 0, 1)) in
+ let rope = (join left right) in
+ assert( match rope with
+ | Node (l,r,3) -> (l == left && r == right)
+ | _ -> false)
+ );
+
+ (* getc() tests *)
+ test "getc : raise Out_of_bounds on negative index" (fun () ->
+ let rope = Leaf("a", 0, 1) in
+ try getc rope (-1); assert false
+ with Out_of_bounds _ -> assert true
+ );
+ test "getc : raise Out_of_bounds on out of bounds index" (fun () ->
+ let rope = Leaf("a", 0, 1) in
+ try getc rope (2); assert false
+ with Out_of_bounds _ -> assert true
+ );
+ test "getc : return index 0 of leaf" (fun () ->
+ let rope = Leaf("abc", 0, 3) in
+ assert( (getc rope (0)) == Char.code 'a' );
+ );
+ test "getc : return index 1 of leaf" (fun () ->
+ let rope = Leaf("abc", 0, 3) in
+ assert( (getc rope (1)) == Char.code 'b' );
+ );
+ test "getc : return index 2 of leaf" (fun () ->
+ let rope = Leaf("abc", 0, 3) in
+ assert( (getc rope (2)) == Char.code 'c' );
+ );
+ test "getc : return index 0 of rope" (fun () ->
+ let rope = Node((Leaf("a", 0, 1)), (Leaf("b", 0, 1)), 2) in
+ assert( (getc rope (0)) == Char.code 'a' );
+ );
+ test "getc : return index 1 of rope" (fun () ->
+ let rope = Node((Leaf("a", 0, 1)), (Leaf("b", 0, 1)), 2) in
+ assert( (getc rope (1)) == Char.code 'b' );
+ );
+
+ (* puts() tests *)
+ test "puts : insert at index 0" (fun () ->
+ let rope = Leaf("bc", 0, 2) in
+ let rope = (puts rope "a" 0) in
+ assert( (length rope) == 3 );
+ assert( (getc rope (0)) == Char.code 'a' );
+ assert( (getc rope (1)) == Char.code 'b' );
+ assert( (getc rope (2)) == Char.code 'c' );
+ );
+ test "puts : insert at index 1" (fun () ->
+ let rope = Leaf("ac", 0, 2) in
+ let rope = (puts rope "b" 1) in
+ assert( (length rope) == 3 );
+ assert( (getc rope (0)) == Char.code 'a' );
+ assert( (getc rope (1)) == Char.code 'b' );
+ assert( (getc rope (2)) == Char.code 'c' );
+ );
+ test "puts : insert index at 2" (fun () ->
+ let rope = Leaf("ab", 0, 2) in
+ let rope = (puts rope "c" 2) in
+ assert( (length rope) == 3 );
+ assert( (getc rope (0)) == Char.code 'a' );
+ assert( (getc rope (1)) == Char.code 'b' );
+ assert( (getc rope (2)) == Char.code 'c' );
+ );
+
+ (* is_bol() tests *)
+ test "is_bol : should return true if pos is 0" (fun () ->
+ let rope = Leaf("abc", 0, 3) in
+ assert( is_bol rope 0 );
+ );
+ test "is_bol : should return true if pos is first char of line" (fun () ->
+ let rope = Leaf("\nabc", 0, 3) in
+ assert( is_bol rope 1 );
+ );
+ test "is_bol : should return false if pos is not first char of line" (fun () ->
+ let rope = Leaf("\nabc", 0, 3) in
+ assert( (is_bol rope 2) == false );
+ );
+ test "is_bol : should return false if previous char is not \n" (fun () ->
+ let rope = Leaf("\rabc", 0, 3) in
+ assert( (is_bol rope 1) == false );
+ );
+
+ (* is_eol() tests *)
+ test "is_eol : should return true if pos is Rope.last" (fun () ->
+ let rope = Leaf("abc", 0, 3) in
+ assert( is_eol rope 2 );
+ );
+ test "is_eol : should return true if pos is last char of line with \n ending" (fun () ->
+ let rope = Leaf("abc\n", 0, 4) in
+ assert( is_eol rope 2 );
+ );
+ test "is_eol : should return true if pos is last char of line with \r\n ending" (fun () ->
+ let rope = Leaf("abc\r\n", 0, 5) in
+ assert( is_eol rope 2 );
+ );
+ test "is_eol : should return false if pos is not last char of line" (fun () ->
+ let rope = Leaf("abcd\n", 0, 5) in
+ assert( (is_eol rope 2) == false );
+ );
+ ()
+
+(*
+
+size_t buf_lscan(Buf* buf, size_t pos, Rune r);
+size_t buf_rscan(Buf* buf, size_t pos, Rune r);
+void buf_getword(Buf* buf, bool ( *isword)(Rune), Sel* sel);
+void buf_getblock(Buf* buf, Rune beg, Rune end, Sel* sel);
+size_t buf_byrune(Buf* buf, size_t pos, int count);
+size_t buf_byword(Buf* buf, size_t pos, int count);
+size_t buf_byline(Buf* buf, size_t pos, int count);
+void buf_find(Buf* buf, int dir, size_t* beg, size_t* end);
+void buf_findstr(Buf* buf, int dir, char* str, size_t* beg, size_t* end);
+void buf_lastins(Buf* buf, size_t* beg, size_t* end);
+size_t buf_setln(Buf* buf, size_t line);
+size_t buf_getcol(Buf* buf, size_t pos);
+size_t buf_setcol(Buf* buf, size_t pos, size_t col);
+
+*)