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