1 module fluid.rope; 2 3 import std.range; 4 import std.string; 5 import std.algorithm; 6 import std.exception; 7 8 9 @safe: 10 11 12 /// Rope implementation, providing more efficient modification if there's lots of text. 13 /// 14 /// The `Rope` structure acts as a slice, a view into the rope's contents. If additional text is added to a node stored 15 /// inside, the change will not be reflected by the rope. 16 /// 17 /// `rope.init` is guaranteed to be valid and empty. 18 /// 19 /// See_Also: https://en.wikipedia.org/wiki/Rope_(data_structure) 20 struct Rope { 21 22 static assert(isInputRange!Rope); 23 static assert(isBidirectionalRange!Rope); 24 static assert(isForwardRange!Rope); 25 static assert(hasSlicing!Rope); 26 27 /// Content of the rope. 28 const(RopeNode)* node; 29 30 /// Start and length of the rope, in UTF-8 bytes. 31 size_t start, length; 32 33 /// Depth of the node. 34 int depth = 1; 35 36 /// Create a rope holding given text. 37 this(inout const(char)[] text) inout pure nothrow { 38 39 // No text, stay with Rope.init 40 if (text == "") 41 this(null, 0, 0, 1); 42 else 43 this(new inout RopeNode(text)); 44 45 } 46 47 /// Create a rope concatenating two other ropes. 48 /// 49 /// Avoids gaps: If either node is empty, copies and becomes the other node. 50 this(inout Rope left, inout Rope right) inout pure nothrow { 51 52 // No need to create a new node there's empty space 53 if (left.length == 0) { 54 55 // Both empty, return .init 56 if (right.length == 0) 57 this(null, 0, 0, 1); 58 59 // Right node only, clone it 60 else 61 this(right); 62 63 } 64 65 // Only the left side is present 66 else if (right.length == 0) 67 this(left); 68 69 // Neither is empty, create a new node 70 else 71 this(new inout RopeNode(left, right)); 72 73 } 74 75 unittest { 76 77 import std.stdio; 78 79 auto bumpy = Rope( 80 new RopeNode( 81 Rope(""), 82 Rope("foo"), 83 ), 84 ); 85 auto flattened = Rope( 86 Rope(""), 87 Rope("foo"), 88 ); 89 90 assert(bumpy.byNode.equal(["", "foo"])); 91 assert(flattened.byNode.equal(["foo"])); 92 93 auto c = Rope( 94 Rope("AAA"), 95 Rope( 96 Rope(""), 97 Rope("BBB"), 98 ), 99 ); 100 assert(c.byNode.equal(["AAA", "BBB"])); 101 102 } 103 104 /// Create a rope from given node. 105 this(const inout(RopeNode)* node) inout pure nothrow { 106 107 // No node given, set all to init 108 if (node is null) return; 109 110 this.node = node; 111 this.start = 0; 112 this.length = node.length; 113 this.depth = node.isLeaf 114 ? 1 115 : max(node.left.depth, node.right.depth) + 1; 116 117 } 118 119 /// Copy a `Rope`. 120 this(inout const Rope rope) inout pure nothrow { 121 122 this(rope.node, rope.start, rope.length, rope.depth); 123 124 } 125 126 private this(inout(RopeNode)* node, size_t start, size_t length, int depth) inout pure nothrow { 127 128 this.node = node; 129 this.start = start; 130 this.length = length; 131 this.depth = depth; 132 133 } 134 135 unittest { 136 137 auto rope = Rope(Rope.init, Rope("foo")); 138 139 assert(rope == "foo"); 140 141 } 142 143 /// Get a hash of the rope. 144 size_t toHash() const nothrow { 145 146 import std.digest.murmurhash; 147 148 auto hash = MurmurHash3!32(); 149 150 // Hash each item 151 foreach (item; byNode) { 152 153 assert(item.isLeaf); 154 155 hash.put(cast(const ubyte[]) item.value); 156 157 } 158 159 return hash.get(); 160 161 } 162 163 unittest { 164 165 auto rope = Rope("This is Fluid!"); 166 auto rope2 = Rope( 167 Rope("This is "), 168 Rope("Fluid!"), 169 ); 170 171 assert(rope.toHash == rope.toHash); 172 assert(rope.toHash == rope2.toHash); 173 assert(rope2.toHash == rope2.toHash); 174 assert(rope2.left.toHash == rope2.left.toHash); 175 assert(rope2.right.toHash == rope2.right.toHash); 176 177 assert(rope2.left.toHash != rope2.right.toHash); 178 assert(rope2.left.toHash != rope2.toHash); 179 assert(rope2.left.toHash != rope.toHash); 180 assert(rope2.right.toHash != rope2.toHash); 181 assert(rope2.right.toHash != rope.toHash); 182 183 } 184 185 /// Compare the text to a string. 186 bool opEquals(const char[] str) const nothrow { 187 188 return equal(this[], str[]); 189 190 } 191 192 /// Compare two ropes. 193 bool opEquals(const Rope str) const nothrow { 194 195 return equal(this[], str[]); 196 197 } 198 199 /// Assign a new value to the rope. 200 ref opAssign(const char[] str) return nothrow { 201 202 return opAssign(new RopeNode(str)); 203 204 } 205 206 /// ditto 207 ref opAssign(RopeNode* node) return nothrow { 208 209 this.node = node; 210 this.start = 0; 211 this.length = node ? node.length : 0; 212 return this; 213 214 } 215 216 /// Concatenate two ropes together. 217 Rope opBinary(string op : "~")(const Rope that) const nothrow { 218 219 return Rope(this, that).rebalance(); 220 221 } 222 223 /// Concatenate with a string. 224 Rope opBinary(string op : "~")(const(char)[] text) const nothrow { 225 226 return Rope(this, Rope(text)).rebalance(); 227 228 } 229 230 /// ditto 231 Rope opBinaryRight(string op : "~")(const(char)[] text) const nothrow { 232 233 return Rope(Rope(text), this).rebalance(); 234 235 } 236 237 /// True if the node is a leaf. 238 bool isLeaf() const nothrow pure { 239 240 return node is null 241 || node.isLeaf; 242 243 } 244 245 Rope save() const nothrow { 246 247 return this; 248 249 } 250 251 /// If true, the rope is empty. 252 bool empty() const nothrow { 253 254 return node is null 255 || length == 0; 256 257 } 258 259 /// Get the first *byte* from the rope. 260 char front() const nothrow { 261 262 assert(!empty, "Cannot access `.front` in an empty rope"); 263 264 // Load nth character of the leaf 265 if (node.isLeaf) 266 return node.value[start]; 267 268 // Accessing the left node 269 else if (!left.empty) 270 return left.front; 271 272 // Accessing the right node 273 else 274 return right.front; 275 276 } 277 278 /// Remove the first byte from the rope. 279 void popFront() nothrow { 280 281 assert(!empty, "Cannot `.popFront` in an empty rope"); 282 283 start++; 284 length--; 285 286 } 287 288 /// Get the last *byte* from the rope. 289 char back() const nothrow { 290 291 assert(!empty, "Cannot access `.back` in an empty rope"); 292 293 // Decode the nth character of the leaf 294 if (node.isLeaf) 295 return node.value[start + length - 1]; 296 297 // Accessing the right node 298 else if (!right.empty) 299 return right.back; 300 301 // Accessing the left node 302 else 303 return left.back; 304 305 } 306 307 /// Remove the last byte from the rope. 308 void popBack() nothrow { 309 310 length--; 311 312 } 313 314 /// Return a mutable slice. 315 Rope opIndex() const nothrow { 316 317 return this; 318 319 } 320 321 /// Get character at given index. 322 char opIndex(size_t index) const nothrow { 323 324 assert(index < length, format!"Given index [%s] exceeds rope length %s"(index, length).assumeWontThrow); 325 326 // Access the nth byte of the leaf 327 if (node.isLeaf) 328 return node.value[start + index]; 329 330 // Accessing the left node 331 else if (index < left.length) 332 return left[index]; 333 334 // Accessing the right node 335 else 336 return right[index - left.length]; 337 338 } 339 340 /// Get the rope's length. 341 size_t opDollar() const nothrow { 342 343 return length; 344 345 } 346 347 /// Slice the rope. 348 Rope opIndex(size_t[2] slice) const nothrow { 349 350 assert(slice[0] <= length, 351 format!"Left boundary of slice [%s .. %s] exceeds rope length %s"(slice[0], slice[1], length) 352 .assumeWontThrow); 353 assert(slice[1] <= length, 354 format!"Right boundary of slice [%s .. %s] exceeds rope length %s"(slice[0], slice[1], length) 355 .assumeWontThrow); 356 assert(slice[0] <= slice[1], 357 format!"Right boundary of slice [%s .. %s] is greater than left boundary"(slice[0], slice[1]) 358 .assumeWontThrow); 359 360 // .init stays the same 361 if (node is null) return Rope.init; 362 363 slice[] += start; 364 365 // Flatten if slicing into a specific side of the slice 366 // e.g. Rope(Rope("foo"), Rope("bar"))[0..3] returns Rope("foo") 367 if (!isLeaf) { 368 369 const divide = node.left.length; 370 371 // Slicing into the left node 372 if (slice[1] <= divide) 373 return node.left[slice]; 374 375 // Slicing into the right node 376 else if (slice[0] >= divide) 377 return node.right[slice[0] - divide .. slice[1] - divide]; 378 379 } 380 381 // Overlap or a leaf: return both as they are 382 return Rope(node, slice[0], slice[1] - slice[0], depth); 383 384 } 385 386 unittest { 387 388 auto myRope = Rope( 389 Rope("foo"), 390 Rope("bar"), 391 ); 392 393 assert(myRope[0..3].isLeaf); 394 assert(myRope[0..3] == "foo"); 395 assert(myRope[3..6].isLeaf); 396 assert(myRope[3..6] == "bar"); 397 assert(myRope[1..2].isLeaf); 398 assert(myRope[1..2] == "o"); 399 assert(myRope[4..5].isLeaf); 400 assert(myRope[4..5] == "a"); 401 402 auto secondRope = Rope( 403 myRope, 404 myRope, 405 ); 406 407 assert(secondRope[1..$][0..2].isLeaf); 408 assert(secondRope[1..$][0..2] == "oo"); 409 410 } 411 412 size_t[2] opSlice(size_t dim : 0)(size_t left, size_t right) const nothrow { 413 414 return [left, right]; 415 416 } 417 418 /// Returns: 419 /// True if the rope is fairly balanced. 420 /// Params: 421 /// maxDistance = Maximum allowed `depth` difference 422 bool isBalanced(int maxDistance = 3) const nothrow { 423 424 // Leaves are always balanced 425 if (isLeaf) return true; 426 427 const depthDifference = node.left.depth - node.right.depth; 428 429 return depthDifference >= -maxDistance 430 && depthDifference <= +maxDistance; 431 432 } 433 434 /// Returns: 435 /// If the rope is unbalanced, returns a copy of the rope, optimized to improve reading performance. 436 /// If the rope is already balanced, returns the original rope unmodified. 437 /// Params: 438 /// maxDistance = Maximum allowed `depth` difference before rebalancing happens. 439 Rope rebalance() const nothrow 440 out (r) { 441 assert(r.isBalanced, 442 format("rebalance(%s) failed. Depth %s (left %s, right %s)", this, depth, left.depth, right.depth) 443 .assumeWontThrow); 444 } 445 do { 446 447 import std.array; 448 449 if (isBalanced) return this; 450 451 return merge(byNode.array); 452 453 } 454 455 /// Returns: A rope created by concatenating an array of leaves together. 456 static Rope merge(Rope[] leaves) nothrow { 457 458 if (leaves.length == 0) 459 return Rope.init; 460 else if (leaves.length == 1) 461 return leaves[0]; 462 else if (leaves.length == 2) 463 return Rope(leaves[0], leaves[1]); 464 else 465 return Rope(merge(leaves[0 .. $/2]), merge(leaves[$/2 .. $])); 466 467 } 468 469 470 unittest { 471 472 auto a = Rope(); 473 assert(a.depth == 1); 474 475 auto b = Rope("foo"); 476 assert(b.depth == 1); 477 478 auto c = Rope( 479 Rope("foo"), 480 Rope("bar"), 481 ); 482 assert(c.depth == 2); 483 484 auto d = Rope(b, c); 485 assert(d.depth == 3); 486 487 auto e = Rope(d, d); 488 assert(e.depth == 4); 489 490 } 491 492 /// Get a leaf node that is a subrope starting with the given index. The length of the node may vary, and does not 493 /// have to reach the end of the rope. 494 Rope leafFrom(size_t start) const nothrow 495 out (r; r.isLeaf) 496 do { 497 498 auto slice = this[start..$]; 499 500 // The slice is a leaf node, return it 501 if (slice.isLeaf) 502 return slice; 503 504 // Not a leaf, get the chunk containing the node 505 if (slice.left.empty) 506 return slice.right.leafFrom(0); 507 else 508 return slice.left.leafFrom(0); 509 510 } 511 512 /// 513 unittest { 514 515 auto myRope = Rope( 516 Rope("Hello, "), 517 Rope( 518 Rope("Flu"), 519 Rope("id"), 520 ), 521 ); 522 523 assert(myRope.leafFrom(0) == Rope("Hello, ")); 524 assert(myRope.leafFrom(7) == Rope("Flu")); 525 assert(myRope.leafFrom(10) == Rope("id")); 526 527 assert(myRope.leafFrom(2) == Rope("llo, ")); 528 assert(myRope.leafFrom(7) == Rope("Flu")); 529 assert(myRope.leafFrom(8) == Rope("lu")); 530 assert(myRope.leafFrom(9) == Rope("u")); 531 532 assert(myRope.leafFrom(myRope.length) == Rope.init); 533 534 } 535 536 /// Get the left side of the rope 537 Rope left() const nothrow { 538 539 if (node is null) return Rope.init; 540 541 const start = min(node.left.length, start); 542 const end = min(node.left.length, start + length); 543 544 return node.left[start .. end]; 545 546 } 547 548 unittest { 549 550 auto a = Rope("ABC"); 551 auto b = Rope("DEF"); 552 auto ab = Rope(a, b); 553 554 assert(ab.left.equal("ABC")); 555 assert(ab[1..$].left.equal("BC")); 556 assert(ab[3..$].left.equal("")); 557 assert(ab[4..$].left.equal("")); 558 assert(ab[0..4].left.equal("ABC")); 559 assert(Rope(ab.node, 0, 3, 1).left.equal("ABC")); 560 assert(Rope(ab.node, 0, 2, 1).left.equal("AB")); 561 assert(Rope(ab.node, 0, 1, 1).left.equal("A")); 562 assert(ab[0..0].left.equal("")); 563 assert(ab[1..1].left.equal("")); 564 assert(ab[4..4].left.equal("")); 565 566 assert(ab[0..3].equal("ABC")); 567 assert(ab[0..2].equal("AB")); 568 assert(ab[0..1].equal("A")); 569 570 } 571 572 unittest { 573 574 auto a = Rope("ABC")[1..$]; 575 auto b = Rope("DEF")[1..$]; 576 auto ab = Rope(a, b); 577 578 assert(ab.left.equal("BC")); 579 assert(ab[1..$].left.equal("C")); 580 assert(ab[3..$].left.equal("")); 581 assert(ab[4..$].left.equal("")); 582 assert(ab[0..4].left.equal("BC")); 583 assert(ab[0..3].left.equal("BC")); 584 assert(Rope(ab.node, 0, 2, 1).left.equal("BC")); 585 assert(Rope(ab.node, 0, 1, 1).left.equal("B")); 586 assert(ab[0..0].left.equal("")); 587 assert(ab[1..1].left.equal("")); 588 assert(ab[4..4].left.equal("")); 589 590 assert(ab[0..2].equal("BC")); 591 assert(ab[0..1].equal("B")); 592 593 } 594 595 /// Get the right side of the rope 596 Rope right() const nothrow { 597 598 if (node is null) return Rope.init; 599 600 const leftStart = min(node.left.length, start); 601 const leftEnd = min(node.left.length, start + length); 602 603 const start = min(node.right.length, this.start - leftStart); 604 const end = min(node.right.length, this.start + length - leftEnd); 605 606 return node.right[start .. end]; 607 608 } 609 610 unittest { 611 612 auto a = Rope("ABC"); 613 auto b = Rope("DEF"); 614 auto ab = Rope(a, b); 615 616 assert(ab.right.equal("DEF")); 617 assert(ab[1..$].right.equal("DEF")); 618 assert(Rope(ab.node, 3, 3, 1).right.equal("DEF")); 619 assert(Rope(ab.node, 4, 2, 1).right.equal("EF")); 620 assert(Rope(ab.node, 4, 1, 1).right.equal("E")); 621 assert(Rope(ab.node, 3, 2, 1).right.equal("DE")); 622 assert(ab[2..$-1].right.equal("DE")); 623 assert(ab[1..1].right.equal("")); 624 assert(ab[4..4].right.equal("")); 625 626 assert(ab[3..$].equal("DEF")); 627 assert(ab[4..$].equal("EF")); 628 assert(ab[4..$-1].equal("E")); 629 assert(ab[3..$-1].equal("DE")); 630 631 } 632 633 unittest { 634 635 auto a = Rope("ABC")[1..$]; // BC 636 auto b = Rope("DEF")[1..$]; // EF 637 auto ab = Rope(a, b); 638 639 assert(ab.right.equal("EF")); 640 assert(ab[1..$].right.equal("EF")); 641 assert(Rope(ab.node, 3, 1, 1).right.equal("F")); 642 assert(Rope(ab.node, 4, 0, 1).right.equal("")); 643 assert(Rope(ab.node, 1, 2, 1).right.equal("E")); 644 assert(Rope(ab.node, 2, 1, 1).right.equal("E")); 645 assert(Rope(ab.node, 3, 0, 1).right.equal("")); 646 assert(ab[1..1].right.equal("")); 647 assert(ab[4..4].right.equal("")); 648 649 assert(ab[3..$].equal("F")); 650 assert(ab[4..$].equal("")); 651 assert(ab[1..$-1].right.equal("E")); 652 assert(ab[2..$-1].equal("E")); 653 assert(ab[3..$-1].equal("")); 654 655 } 656 657 /// Get the value of this rope. Only works for leaf nodes. 658 const(char)[] value() const nothrow { 659 660 // Null node → null string 661 if (node is null) return null; 662 663 return node.value[start .. start + length]; 664 665 } 666 667 /// Split the rope, creating a new root node that connects the left and right side of the split. 668 /// 669 /// This functions never returns leaf nodes, but either side of the node may be empty. 670 Rope split(size_t index) const nothrow 671 out (r; !r.node.isLeaf) 672 do { 673 674 assert(index <= length, format!"Split index (%s) exceeds rope length %s"(index, length).assumeWontThrow); 675 676 auto left = this.left; 677 auto right = this.right; 678 679 const(RopeNode)* result; 680 681 // Leaf node, split by slicing 682 if (node.isLeaf) 683 result = new RopeNode(this[0..index], this[index..$]); 684 685 // Already split 686 else if (index == left.length) 687 return this; 688 689 // Splitting inside left node 690 else if (index < left.length) { 691 692 auto div = left.split(index); 693 694 result = new RopeNode( 695 div.left, 696 Rope(div.right, right), 697 ); 698 699 } 700 701 // Splitting inside right node 702 else { 703 704 auto div = right.split(index - left.length); 705 706 result = new RopeNode( 707 Rope(left, div.left), 708 div.right, 709 ); 710 711 } 712 713 return Rope(result); 714 715 } 716 717 unittest { 718 719 auto a = Rope("Hello, World!"); 720 auto b = a.split(7); 721 722 assert(b.node.left == "Hello, "); 723 assert(b.node.right == "World!"); 724 725 auto startSplit = a.split(0); 726 727 assert(startSplit.node.left == ""); 728 assert(startSplit.node.right == a); 729 730 auto endSplit = a.split(a.length); 731 732 assert(endSplit.node.left == a); 733 assert(endSplit.node.right == ""); 734 735 auto c = a[1..$-4].split(6); 736 737 assert(c.node.left == "ello, "); 738 assert(c.node.right == "Wo"); 739 740 } 741 742 unittest { 743 744 auto myRope = Rope( 745 Rope( 746 Rope("HËl"), 747 Rope("lo"), 748 ), 749 Rope( 750 Rope(", "), 751 Rope( 752 Rope("Wor"), 753 Rope("ld!"), 754 ), 755 ) 756 ); 757 758 assert(myRope.equal("HËllo, World!")); 759 assert(myRope[1..$].equal("Ëllo, World!")); 760 761 { 762 auto split = myRope[1..$].split(6); 763 764 assert(split.equal("Ëllo, World!")); 765 assert(split.left.equal("Ëllo,")); 766 assert(split.right.equal(" World!")); 767 } 768 { 769 auto split = myRope[5..$-3].split(4); 770 771 assert(split.equal("o, Wor")); 772 assert(split.left.equal("o, W")); 773 assert(split.right.equal("or")); 774 } 775 { 776 auto split = myRope.split(6); 777 assert(split == myRope); 778 assert(split.left.equal("HËllo")); 779 } 780 { 781 auto split = myRope[1..$].split(5); 782 assert(split == myRope[1..$]); 783 assert(split.left.equal("Ëllo")); 784 } 785 { 786 auto split = myRope.split(0); 787 assert(split.left.equal("")); 788 assert(split.right.equal(myRope)); 789 } 790 { 791 auto split = myRope.split(myRope.length); 792 assert(split.left.equal(myRope)); 793 assert(split.right.equal("")); 794 } 795 { 796 auto split = myRope[1..$].split(0); 797 798 assert(split.left.equal("")); 799 assert(split.right.equal("Ëllo, World!")); 800 } 801 { 802 auto split = myRope[1..$-1].split(myRope.length-2); 803 assert(split.left.equal("Ëllo, World")); 804 assert(split.right.equal("")); 805 } 806 807 } 808 809 /// Insert a new node into the rope. 810 Rope insert(size_t index, Rope value) const nothrow { 811 812 // Perform a split 813 auto split = split(index); 814 815 // Insert the element 816 return Rope(split.left, Rope(value, split.right)).rebalance(); 817 818 } 819 820 unittest { 821 822 auto a = Rope("Hello, !"); 823 auto b = a.insert(7, Rope("World")); 824 825 assert(a.equal("Hello, !")); 826 assert(b.equal("Hello, World!")); 827 828 assert(Rope("World!") 829 .insert(0, Rope("Hello, ")) 830 .equal("Hello, World!")); 831 assert(Rope("Hellø, ") 832 .insert(8, Rope("Fluid!")) 833 .equal("Hellø, Fluid!")); 834 835 } 836 837 unittest { 838 839 auto a = Rope("Hello, !"); 840 auto b = a.insert(7, Rope("rl")); 841 842 assert(b.equal("Hello, rl!")); 843 844 auto c = b.insert(7, Rope("Wo")); 845 846 assert(c.equal("Hello, Worl!")); 847 848 auto d = c.insert(11, Rope("d")); 849 850 assert(d.equal("Hello, World!")); 851 852 } 853 854 /// Replace value between two indexes with a new one. 855 /// 856 /// Params: 857 /// low = Low index, inclusive; First index to delete. 858 /// high = High index, exclusive; First index after the newly inserted fragment to keep. 859 /// value = Value to insert. 860 Rope replace(size_t low, size_t high, Rope value) const nothrow { 861 862 assert(low <= high, 863 format!"Low boundary of replace slice [%s .. %s] is greater than the high boundary"(low, high) 864 .assumeWontThrow); 865 assert(high <= length, 866 format!"Replace slice [%s .. %s] exceeds Rope length %s"(low, high, length) 867 .assumeWontThrow); 868 869 // Return the value as-is if the node is empty 870 if (length == 0) 871 return value; 872 873 // Split the rope in both points 874 const leftSplit = split(low); 875 const rightSplit = split(high); 876 877 return Rope( 878 leftSplit.left, 879 Rope( 880 value, 881 rightSplit.right, 882 ), 883 ).rebalance(); 884 885 } 886 887 unittest { 888 889 auto a = Rope("Hello, World!"); 890 auto b = a.replace(7, 12, Rope("Fluid")); 891 892 assert(b == "Hello, Fluid!"); 893 894 auto c = Rope("Foo Bar Baz Ban"); 895 auto d = c.replace(4, 12, Rope.init); 896 897 assert(d == "Foo Ban"); 898 899 } 900 901 unittest { 902 903 auto a = Rope( 904 Rope("Hello"), 905 Rope("Fluid"), 906 ); 907 auto b = a.replace(3, 6, Rope("LOf")); 908 909 assert(b == "HelLOfluid"); 910 assert(b.byNode.equal(["Hel", "LOf", "luid"])); 911 912 auto c = b.replace(3, 6, Rope("~~~")); 913 914 assert(c.byNode.equal(["Hel", "~~~", "luid"])); 915 916 auto d = c.replace(0, 3, Rope("***")); 917 918 assert(d.byNode.equal(["***", "~~~", "luid"])); 919 920 auto e = d.replace(3, 10, Rope("###")); 921 922 assert(e.byNode.equal(["***", "###"])); 923 924 } 925 926 unittest { 927 928 auto a = Rope("Hello, World!"); 929 930 // Replacing with an empty node should effectively split 931 a = a.replace(7, 12, Rope("")); 932 assert(a.byNode.equal(["Hello, ", "!"])); 933 934 // Insert characters into the text by replacing a whole node 935 a = a.replace(7, 7, Rope("a")); 936 assert(a.byNode.equal(["Hello, ", "a", "!"])); 937 938 a = a.replace(7, 8, Rope("ab")); 939 assert(a.byNode.equal(["Hello, ", "ab", "!"])); 940 941 a = a.replace(7, 9, Rope("abc")); 942 assert(a.byNode.equal(["Hello, ", "abc", "!"])); 943 944 // Now, reuse the same node 945 auto node = new RopeNode("abcd"); 946 947 a = a.replace(7, 10, Rope(node)); 948 assert(a.byNode.equal(["Hello, ", "abcd", "!"])); 949 assert(a == "Hello, abcd!"); 950 951 // Adding the node should have no effect since slices weren't updated 952 node.value = "abcde"; 953 assert(a == "Hello, abcd!"); 954 955 // Update the slices 956 a = a.replace(7, 11, Rope(node)); 957 assert(a.byNode.equal(["Hello, ", "abcde", "!"])); 958 assert(a == "Hello, abcde!"); 959 960 } 961 962 unittest { 963 964 auto a = Rope("Rope"); 965 966 a = a.replace(0, 2, Rope("Car")); 967 968 assert(a.byNode.equal(["Car", "pe"])); 969 970 a = a.replace(5, 5, Rope(" diem")); 971 972 assert(a.byNode.equal(["Car", "pe", " diem"])); 973 974 } 975 976 unittest { 977 978 auto a = Rope(); 979 980 a = a.replace(0, 0, Rope("foo")); 981 982 assert(a.byNode.equal(["foo"])); 983 assert(a.isLeaf); 984 985 } 986 987 /// Replace given substring with a new value 988 Rope replace(String)(String oldValue, Rope value) const nothrow { 989 990 const start = this[].indexOf(oldValue); 991 const end = start + oldValue.length; 992 993 // Substring not found, do nothing 994 if (start == -1) return this; 995 996 return replace(start, end, value); 997 998 } 999 1000 unittest { 1001 1002 assert(Rope("foo baz baz").replace("baz", Rope("bar")) == "foo bar baz"); 1003 1004 } 1005 1006 /// Append text to the rope. 1007 ref Rope opOpAssign(string op : "~")(const(char)[] value) return nothrow { 1008 1009 auto left = this; 1010 1011 return this = Rope(left, Rope(value)).rebalance; 1012 1013 } 1014 1015 /// Append another rope to the rope. 1016 ref Rope opOpAssign(string op : "~")(const Rope value) return nothrow { 1017 1018 auto left = this; 1019 1020 return this = Rope(left, value).rebalance(); 1021 1022 } 1023 1024 /// Iterate the rope by characters. 1025 auto byDchar() const { 1026 1027 import std.utf : byDchar; 1028 1029 return byDchar(this[]); 1030 1031 } 1032 1033 /// Count characters in the string. Iterates the whole rope. 1034 size_t countCharacters() const { 1035 1036 return byDchar.walkLength; 1037 1038 } 1039 1040 /// Perform deep-first search through leaf nodes of the rope. 1041 auto byNode() inout { 1042 1043 import std.container.dlist; 1044 1045 struct ByNode { 1046 1047 DList!Rope ancestors; 1048 Rope front; 1049 bool empty; 1050 1051 /// Switch to the next sibling or ascend. 1052 void popFront() @safe nothrow { 1053 1054 assert(!empty); 1055 1056 // No ancestors remain, stop 1057 if (ancestors.empty) { 1058 empty = true; 1059 return; 1060 } 1061 1062 // Get the last ancestor; remove it so we don't return to it 1063 auto parent = ancestors.back; 1064 ancestors.removeBack; 1065 1066 // Switch to its right sibling 1067 descend(parent.right); 1068 1069 } 1070 1071 /// Descend into given node. 1072 void descend(Rope node) @safe nothrow { 1073 1074 // Leaf node, set as front 1075 if (node.isLeaf) { 1076 front = node; 1077 return; 1078 } 1079 1080 // Descend 1081 ancestors.insertBack(node); 1082 1083 // Start from the left side 1084 descend(node.left); 1085 1086 } 1087 1088 } 1089 1090 auto result = ByNode(); 1091 result.descend(this); 1092 return result; 1093 1094 } 1095 1096 unittest { 1097 1098 auto mr = Rope( 1099 Rope("a"), 1100 Rope( 1101 Rope( 1102 Rope( 1103 Rope("b"), 1104 Rope( 1105 Rope("c"), 1106 Rope("d"), 1107 ), 1108 ), 1109 Rope( 1110 Rope("e"), 1111 Rope("f"), 1112 ), 1113 ), 1114 Rope("g") 1115 ), 1116 ); 1117 1118 assert(mr.byNode.equal([ 1119 Rope("a"), 1120 Rope("b"), 1121 Rope("c"), 1122 Rope("d"), 1123 Rope("e"), 1124 Rope("f"), 1125 Rope("g"), 1126 ])); 1127 1128 } 1129 1130 /// Get line in the rope by a byte index. 1131 /// Returns: A rope slice with the line containing the given index. 1132 Rope lineByIndex(KeepTerminator keepTerminator = No.keepTerminator)(size_t index) const 1133 in (index >= 0 && index <= length, format!"Index %s is out of bounds of Rope of length %s"(index, length)) 1134 do { 1135 1136 import fluid.typeface : Typeface; 1137 1138 auto back = Typeface.lineSplitter(this[0..index].retro).front; 1139 auto front = Typeface.lineSplitter!keepTerminator(this[index..$]).front; 1140 1141 static assert(is(ElementType!(typeof(back)) == char)); 1142 static assert(is(ElementType!(typeof(front)) == char)); 1143 1144 const backLength = back.walkLength; 1145 const frontLength = front.walkLength; 1146 1147 // Combine everything on the same line, before and after the cursor 1148 return this[index - backLength .. index + frontLength]; 1149 1150 } 1151 1152 unittest { 1153 1154 Rope root; 1155 assert(root.lineByIndex(0) == ""); 1156 1157 root = root ~ "aaą\nbbČb\n c \r\n\n **Ą\n"; 1158 assert(root.lineByIndex(0) == "aaą"); 1159 assert(root.lineByIndex(1) == "aaą"); 1160 assert(root.lineByIndex(4) == "aaą"); 1161 assert(root.lineByIndex(5) == "bbČb"); 1162 assert(root.lineByIndex(7) == "bbČb"); 1163 assert(root.lineByIndex(10) == "bbČb"); 1164 assert(root.lineByIndex(11) == " c "); 1165 assert(root.lineByIndex!(Yes.keepTerminator)(11) == " c \r\n"); 1166 assert(root.lineByIndex(16) == ""); 1167 assert(root.lineByIndex!(Yes.keepTerminator)(16) == "\n"); 1168 assert(root.lineByIndex(17) == " **Ą"); 1169 assert(root.lineByIndex(root.value.length) == ""); 1170 assert(root.lineByIndex!(Yes.keepTerminator)(root.value.length) == ""); 1171 1172 } 1173 1174 /// Get the column the given index is on. 1175 /// Returns: 1176 /// Return value depends on the type fed into the function. `column!dchar` will use characters and `column!char` 1177 /// will use bytes. The type does not have effect on the input index. 1178 ptrdiff_t column(Chartype)(size_t index) const { 1179 1180 import std.utf : byUTF; 1181 import fluid.typeface : Typeface; 1182 1183 // Get last line 1184 return Typeface.lineSplitter(this[0..index].retro).front 1185 1186 // Count characters 1187 .byUTF!Chartype.walkLength; 1188 1189 } 1190 1191 unittest { 1192 1193 Rope root; 1194 assert(root.column!dchar(0) == 0); 1195 1196 root = Rope(" aąąą"); 1197 assert(root.column!dchar(8) == 5); 1198 assert(root.column!char(8) == 8); 1199 1200 root = Rope(" aąąąO\n"); 1201 assert(root.column!dchar(10) == 0); 1202 assert(root.column!char(10) == 0); 1203 1204 root = Rope(" aąąąO\n "); 1205 assert(root.column!dchar(11) == 1); 1206 1207 root = Rope(" aąąąO\n Ω = 1+2"); 1208 assert(root.column!dchar(14) == 3); 1209 assert(root.column!char(14) == 4); 1210 1211 } 1212 1213 /// Get the index of the start or end of the line — from index of any character on the same line. 1214 size_t lineStartByIndex(size_t index) { 1215 1216 return index - column!char(index); 1217 1218 } 1219 1220 /// ditto 1221 size_t lineEndByIndex(size_t index) { 1222 1223 return lineStartByIndex(index) + lineByIndex(index).length; 1224 1225 } 1226 1227 /// 1228 unittest { 1229 1230 auto rope = Rope("Hello, World!\nHello, Fluid!"); 1231 1232 assert(rope.lineStartByIndex(5) == 0); 1233 assert(rope.lineEndByIndex(5) == 13); 1234 assert(rope.lineStartByIndex(18) == 14); 1235 assert(rope.lineEndByIndex(18) == rope.length); 1236 1237 } 1238 1239 struct DiffRegion { 1240 1241 size_t start; 1242 Rope first; 1243 Rope second; 1244 1245 alias asArray this; 1246 1247 /// Returns true if the two compared ropes are identical. 1248 bool isSame() const { 1249 1250 return first.empty && second.empty; 1251 1252 } 1253 1254 inout(Rope)[2] asArray() inout { 1255 1256 return [first, second]; 1257 1258 } 1259 1260 } 1261 1262 /// Find the difference between the two ropes, in terms of a single contiguous region. 1263 /// Params: 1264 /// other = Rope to compare this rope to. 1265 /// Returns: 1266 /// The region containing the change, in terms of two subropes. The first rope is a subrope exclusive to this 1267 /// rope, and the second is a subrope exclusive to the other. 1268 /// 1269 /// The return value includes a `start` field which indicates the exact index the resulting range starts with. 1270 DiffRegion diff(const Rope other) const { 1271 1272 if (this is other) { 1273 return DiffRegion.init; 1274 } 1275 1276 if (!isLeaf) { 1277 1278 // Left side is identical, compare right side only 1279 if (left is other.left) { 1280 1281 auto result = right.diff(other.right); 1282 1283 return DiffRegion(left.length + result.start, result.first, result.second); 1284 1285 } 1286 1287 // Or, right side is identical 1288 else if (right is other.right) { 1289 1290 return left.diff(other.left); 1291 1292 } 1293 1294 } 1295 1296 // Perform string comparison 1297 const prefix = commonPrefix( 1298 BasicRopeRange(this[]), 1299 BasicRopeRange(other[])).length; 1300 const suffix = commonPrefix(this[prefix..$].retro, other[prefix..$].retro).length; 1301 1302 const start = prefix; 1303 const thisEnd = this.length - suffix; 1304 const otherEnd = other.length - suffix; 1305 1306 return DiffRegion(start, 1307 thisEnd == 0 1308 ? Rope() 1309 : this[start .. thisEnd], 1310 otherEnd == 0 1311 ? Rope() 1312 : other[start .. otherEnd]); 1313 1314 } 1315 1316 unittest { 1317 1318 auto rope1 = Rope("Hello, World!"); 1319 auto rope2 = Rope("Hello, Fluid!"); 1320 auto diff = rope1.diff(rope2); 1321 1322 assert(diff.start == 7); 1323 assert(diff[].equal(["Worl", "Flui"])); 1324 1325 } 1326 1327 unittest { 1328 1329 auto rope1 = Rope("Hello!"); 1330 auto rope2 = Rope("Hello!"); 1331 1332 assert(rope1.diff(rope2)[] == [Rope(), Rope()]); 1333 1334 } 1335 1336 unittest { 1337 1338 auto rope1 = Rope( 1339 Rope("Hello, "), 1340 Rope("World!")); 1341 auto rope2 = Rope("Hello, Fluid!"); 1342 auto diff = rope1.diff(rope2); 1343 1344 assert(diff.start == 7); 1345 assert(diff[].equal([ 1346 "Worl", 1347 "Flui", 1348 ])); 1349 1350 } 1351 1352 unittest { 1353 1354 auto rope = Rope( 1355 Rope( 1356 Rope("auto rope"), 1357 Rope( 1358 Rope("1"), 1359 Rope(" = Rope();"), 1360 ) 1361 ), 1362 Rope( 1363 Rope( 1364 Rope("auto rope2 = Rope(\""), 1365 Rope("Hello, Fluid!") 1366 ), 1367 Rope( 1368 Rope("\");"), 1369 Rope("auto diff = rope1.diff(rope2);"), 1370 ), 1371 ) 1372 ); 1373 1374 assert( 1375 rope.replace(0, 4, Rope("Rope")) 1376 .diff(rope) 1377 == DiffRegion(0, Rope("Rope"), Rope("auto")) 1378 ); 1379 1380 auto tmp = rope; 1381 1382 auto findRope1() { 1383 1384 return tmp.indexOf("rope1"); 1385 1386 } 1387 1388 assert( 1389 rope 1390 .replace("rope1", Rope("rope")) 1391 .replace("rope1", Rope("rope")) 1392 .diff(rope) 1393 == DiffRegion( 1394 9, 1395 Rope(` = Rope();auto rope2 = Rope("Hello, Fluid!");auto diff = rope`), 1396 Rope(`1 = Rope();auto rope2 = Rope("Hello, Fluid!");auto diff = rope1`)) 1397 ); 1398 1399 } 1400 1401 unittest { 1402 1403 auto core = Rope("foobar"); 1404 auto rope1 = core[0..5]; 1405 auto rope2 = rope1.replace(0, 5, core); 1406 1407 assert(rope1 == "fooba"); 1408 assert(rope2 == "foobar"); 1409 assert(rope1.diff(rope2) == DiffRegion(5, Rope(""), Rope("r"))); 1410 1411 } 1412 1413 unittest { 1414 1415 auto core = Rope("foobar"); 1416 auto rope1 = Rope(core[0..5], Rope(";")); 1417 auto rope2 = rope1.replace(0, 5, core); 1418 1419 assert(rope1 == "fooba;"); 1420 assert(rope2 == "foobar;"); 1421 assert(rope1.diff(rope2) == DiffRegion(5, Rope(""), Rope("r"))); 1422 1423 } 1424 1425 unittest { 1426 1427 auto core = Rope("foobar"); 1428 auto rope1 = Rope( 1429 Rope("return "), 1430 Rope( 1431 core[0..5], 1432 Rope(`("Hello, World!");`) 1433 ), 1434 ); 1435 auto rope2 = rope1.replace(7, 7+5, core); 1436 1437 assert(rope1 == `return fooba("Hello, World!");`); 1438 assert(rope2 == `return foobar("Hello, World!");`); 1439 assert(rope1.diff(rope2) == DiffRegion(12, Rope(""), Rope("r"))); 1440 1441 } 1442 1443 /// Put the rope's contents inside given output range. 1444 void toString(Writer)(ref Writer w) const { 1445 1446 foreach (node; byNode) { 1447 1448 put(w, node.value); 1449 1450 } 1451 1452 } 1453 1454 unittest { 1455 1456 auto rope1 = Rope("bar"); 1457 const rope2 = Rope( 1458 Rope("b"), 1459 Rope( 1460 Rope("a"), 1461 Rope("r"), 1462 ), 1463 ); 1464 1465 assert(format!"Foo, %s, baz"(rope1) == "Foo, bar, baz"); 1466 assert(format!"Foo, %s, baz"(rope2) == "Foo, bar, baz"); 1467 1468 } 1469 1470 string toString() const @trusted { 1471 1472 // Allocate space for the string 1473 auto buffer = new char[length]; 1474 1475 // Write all characters 1476 foreach (i, char ch; this[].enumerate) { 1477 1478 buffer[i] = ch; 1479 1480 } 1481 1482 return cast(string) buffer; 1483 1484 } 1485 1486 unittest { 1487 1488 auto rope1 = Rope("bar"); 1489 const rope2 = Rope( 1490 Rope("b"), 1491 Rope( 1492 Rope("a"), 1493 Rope("r"), 1494 ), 1495 ); 1496 1497 assert(rope1.toString == "bar"); 1498 assert(rope2.toString == "bar"); 1499 1500 } 1501 1502 immutable(char)* toStringz() const @trusted { 1503 1504 return cast(immutable) toStringzMutable; 1505 1506 } 1507 1508 char* toStringzMutable() const { 1509 1510 // Allocate space for the whole string and a null terminator 1511 auto buffer = new char[length + 1]; 1512 1513 // Write all characters 1514 foreach (i, char ch; this[].enumerate) { 1515 1516 buffer[i] = ch; 1517 1518 } 1519 1520 // Add a terminator null byte 1521 buffer[$-1] = '\0'; 1522 1523 return &buffer[0]; 1524 1525 } 1526 1527 @system 1528 unittest { 1529 1530 import core.stdc.string; 1531 1532 auto input = Rope("Hello, World!"); 1533 1534 assert(strlen(input.toStringzMutable) == input.length); 1535 1536 } 1537 1538 1539 } 1540 1541 struct RopeNode { 1542 1543 public { 1544 1545 /// Left child of this node. 1546 Rope left; 1547 1548 /// Right child of this node. 1549 Rope right; 1550 1551 /// Direct content of the node, if a leaf node. 1552 /// 1553 /// This must be a fully valid string. Content may not be split in the middle of a codepoint. 1554 const(char)[] value; 1555 1556 } 1557 1558 /// Create a leaf node from a slice 1559 this(inout const(char)[] text) inout pure nothrow { 1560 1561 this.value = text; 1562 1563 } 1564 1565 /// Create a node from two other node; Concatenate the two other nodes. Both must not be null. 1566 this(inout Rope left, inout Rope right) inout pure nothrow { 1567 1568 this.left = left; 1569 this.right = right; 1570 1571 } 1572 1573 /// Get length of this node. 1574 size_t length() const pure nothrow { 1575 1576 return isLeaf 1577 ? value.length 1578 : left.length + right.length; 1579 1580 } 1581 1582 /// True if this is a leaf node and contains text rather than child nodes. 1583 bool isLeaf() const pure nothrow { 1584 1585 return left.node is null 1586 && right.node is null; 1587 1588 } 1589 1590 } 1591 1592 unittest { 1593 1594 auto a = Rope("Hello, "); 1595 auto b = Rope("World! "); 1596 1597 auto combined = Rope(a, b); 1598 1599 assert(combined.equal("Hello, World! ")); 1600 assert(combined[1..$].equal("ello, World! ")); 1601 assert(combined[1..5].equal("ello")); 1602 1603 } 1604 1605 unittest { 1606 1607 assert(Rope.init.empty); 1608 assert(Rope(" Hello, World! ").strip == "Hello, World!"); 1609 assert(Rope(" Hello, World! ").stripLeft == "Hello, World! "); 1610 assert(Rope(" Hello, World! ").stripRight == " Hello, World!"); 1611 1612 } 1613 1614 /// `std.utf.codeLength` implementation for Rope. 1615 alias codeLength(T : Rope) = imported!"std.utf".codeLength!char; 1616 1617 /// A wrapper over Range which disables slicing. Some algorithms assume slicing is faster than regular range access, 1618 /// but it's not the case for `Rope`. 1619 struct BasicRopeRange { 1620 1621 Rope rope; 1622 1623 size_t length() const { 1624 return rope.length; 1625 } 1626 1627 bool empty() const { 1628 return rope.empty; 1629 } 1630 1631 void popFront() { 1632 rope.popFront; 1633 } 1634 1635 char front() const { 1636 return rope.front; 1637 } 1638 1639 BasicRopeRange save() { 1640 return this; 1641 } 1642 1643 }