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 }