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