Merge: Do not compile dead modules
[nit.git] / lib / standard / text / ropes.nit
1 # This file is part of NIT (http://www.nitlanguage.org).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Tree-based representation of a String.
16 #
17 # Ropes are a data structure introduced in a 1995 paper from
18 # Hans J. Boehm, Russ Atkinson and Michael Plass.
19 #
20 # See:
21 #
22 # > Ropes: an Alternative to Strings,
23 # > *Software - Practice and Experience*,
24 # > Vol. 25(12), 1315-1330 (December 1995).
25 #
26 # The implementation developed here provides an automatic change
27 # of data structure depending on the length of the leaves.
28 #
29 # The length from which a `Rope` is built from a `flat` string
30 # if defined at top-level (see `maxlen`) and can be redefined by clients
31 # depending on their needs (e.g. if you need to bench the speed of
32 # the creation of concat nodes, lower the size of maxlen).
33 #
34 # A Rope as defined in the original paper is a Tree made of concatenation
35 # nodes and containing `Flat` (that is Array-based) strings as Leaves.
36 #
37 # Example :
38 #
39 # ~~~raw
40 # Concat
41 # / \
42 # Concat Concat
43 # / \ / \
44 # "My" " Name" " is" " Simon."
45 # ~~~
46 #
47 # Note that the above example is not representative of the actual implementation
48 # of `Ropes`, since short leaves are merged to keep the rope at an acceptable
49 # height (hence, this rope here might actually be a `FlatString` !).
50 module ropes
51
52 intrude import flat
53
54 # Maxlen is the maximum length of a Leaf node
55 #
56 # When concatenating two leaves, if `new_length` > `maxlen`,
57 # A `Concat` node is created instead
58 #
59 # Its purpose is to limit the depth of the `Rope` (this
60 # improves performance when accessing/iterating).
61 fun maxlen: Int do return 64
62
63 # String using a tree-based representation with leaves as `FlatStrings`
64 private abstract class Rope
65 super Text
66 end
67
68 # Node that represents a concatenation between two `String`
69 private class Concat
70 super Rope
71 super String
72
73 redef var chars is lazy do return new RopeChars(self)
74
75 redef var length is noinit
76
77 redef fun substrings do return new RopeSubstrings(self)
78
79 redef fun empty do return ""
80
81 redef var to_cstring is lazy do
82 var len = length
83 var ns = new NativeString(len + 1)
84 ns[len] = '\0'
85 var off = 0
86 for i in substrings do
87 var ilen = i.length
88 i.as(FlatString).items.copy_to(ns, ilen, i.as(FlatString).index_from, off)
89 off += ilen
90 end
91 return ns
92 end
93
94 # Left child of the node
95 var left: String
96 # Right child of the node
97 var right: String
98
99 init do
100 length = left.length + right.length
101 end
102
103 redef fun output do
104 left.output
105 right.output
106 end
107
108 redef fun iterator do return new RopeIter(self)
109
110 redef fun *(i) do
111 var x: String = self
112 for j in [1 .. i[ do x += self
113 return x
114 end
115
116 redef fun [](i) do
117 var llen = left.length
118 if i >= llen then return right[i - llen]
119 return left[i]
120 end
121
122 redef fun substring(from, len) do
123 var llen = left.length
124 if from < llen then
125 if from + len < llen then return left.substring(from,len)
126 var lsublen = llen - from
127 return left.substring_from(from) + right.substring(0, len - lsublen)
128 else
129 return right.substring(from - llen, len)
130 end
131 end
132
133 redef fun reversed do return new Concat(right.reversed, left.reversed)
134
135 redef fun insert_at(s, pos) do
136 if pos > left.length then
137 return left + right.insert_at(s, pos - left.length)
138 end
139 return left.insert_at(s, pos) + right
140 end
141
142 redef fun to_upper do return new Concat(left.to_upper, right.to_upper)
143
144 redef fun to_lower do return new Concat(left.to_lower, right.to_lower)
145
146 redef fun +(o) do
147 var s = o.to_s
148 var slen = s.length
149 if s isa Concat then
150 return new Concat(self, s)
151 else
152 var r = right
153 var rlen = r.length
154 if rlen + slen > maxlen then return new Concat(self, s)
155 return new Concat(left, r + s)
156 end
157 end
158
159 redef fun copy_to_native(dest, n, src_offset, dest_offset) do
160 var subs = new RopeSubstrings.from(self, src_offset)
161 var st = src_offset - subs.pos
162 var off = dest_offset
163 while n > 0 do
164 var it = subs.item
165 if n > it.length then
166 var cplen = it.length - st
167 it.items.copy_to(dest, cplen, st, off)
168 off += cplen
169 n -= cplen
170 else
171 it.items.copy_to(dest, n, st, off)
172 n = 0
173 end
174 subs.next
175 st = 0
176 end
177 end
178 end
179
180 # Mutable `Rope`, optimized for concatenation operations
181 #
182 # A `RopeBuffer` is an efficient way of building a `String` when
183 # concatenating small strings.
184 #
185 # It does concatenations in an optimized way by having a
186 # mutable part and an immutable part built by efficiently
187 # concatenating strings in chain.
188 #
189 # Every concatenation operation is done by copying a string to
190 # the mutable part and flushing it when full.
191 #
192 # However, when a long string is appended to the `Buffer`,
193 # the concatenation is done at it would be in a `Rope`.
194 class RopeBuffer
195 super Rope
196 super Buffer
197
198 redef var chars: Sequence[Char] is lazy do return new RopeBufferChars(self)
199
200 # The final string being built on the fly
201 private var str: String is noinit
202
203 # Current concatenation buffer
204 private var ns: NativeString is noinit
205
206 # Next available (e.g. unset) character in the `Buffer`
207 private var rpos = 0
208
209 # Keeps track of the buffer's currently dumped part
210 #
211 # This might happen if for instance, a String was being
212 # built by concatenating small parts of string and suddenly
213 # a long string (length > maxlen) is appended.
214 private var dumped: Int is noinit
215
216 # Length of the complete rope
217 redef var length = 0
218
219 # Length of the mutable part
220 #
221 # Is also used as base to compute the size of the next
222 # mutable native string (`ns`)
223 private var buf_size: Int is noinit
224
225 redef fun substrings do return new RopeBufSubstringIterator(self)
226
227 # Builds an empty `RopeBuffer`
228 init do
229 str = ""
230 ns = new NativeString(maxlen)
231 buf_size = maxlen
232 dumped = 0
233 end
234
235 # Builds a new `RopeBuffer` with `str` in it.
236 init from(str: String) do
237 self.str = str
238 ns = new NativeString(maxlen)
239 buf_size = maxlen
240 length = str.length
241 dumped = 0
242 end
243
244 # Resets the informations of the `Buffer`
245 #
246 # This is called when doing in-place modifications
247 # on a previously to_s'd `RopeBuffer`
248 private fun reset do
249 var nns = new NativeString(buf_size)
250 var blen = rpos - dumped
251 ns.copy_to(nns, blen, dumped, 0)
252 ns = nns
253 dumped = 0
254 rpos = blen
255 written = false
256 end
257
258 redef fun empty do return new RopeBuffer
259
260 redef fun clear do
261 str = ""
262 length = 0
263 rpos = 0
264 dumped = 0
265 if written then
266 ns = new NativeString(buf_size)
267 written = false
268 end
269 end
270
271 redef fun substring(from, count) do
272 var strlen = str.length
273
274 if from < 0 then
275 count += from
276 if count < 0 then count = 0
277 from = 0
278 end
279
280 if count > length then count = length - from
281
282 if count == 0 then return empty
283
284 if from < strlen then
285 var subpos = strlen - from
286 if count <= subpos then
287 return new RopeBuffer.from(str.substring(from, count))
288 else
289 var l = str.substring_from(from)
290 var rem = count - subpos
291 var nns = new NativeString(rem)
292 ns.copy_to(nns, rem, dumped, 0)
293 return new RopeBuffer.from(l + nns.to_s_with_length(rem))
294 end
295 else
296 var nns = new NativeString(count)
297 ns.copy_to(nns, count, dumped, 0)
298 return new RopeBuffer.from(nns.to_s_with_length(count))
299 end
300 end
301
302 redef fun append(s) do
303 var slen = s.length
304 length += slen
305 var rp = rpos
306 if s isa Rope or slen > maxlen then
307 if rp > 0 and dumped != rp then
308 str += new FlatString.with_infos(ns, rp - dumped, dumped, rp - 1)
309 dumped = rp
310 end
311 str = str + s
312 return
313 end
314 var remsp = buf_size - rp
315 var sits: NativeString
316 var begin: Int
317 if s isa FlatString then
318 begin = s.index_from
319 sits = s.items
320 else if s isa FlatBuffer then
321 begin = 0
322 sits = s.items
323 else
324 if slen <= remsp then
325 for i in s.chars do
326 ns[rpos] = i
327 rpos += 1
328 end
329 else
330 var spos = 0
331 for i in [0..remsp[ do
332 ns[rpos] = s[spos]
333 rpos += 1
334 spos += 1
335 end
336 dump_buffer
337 while spos < slen do
338 ns[rpos] = s[spos]
339 spos += 1
340 rpos += 1
341 end
342 end
343 return
344 end
345 if slen <= remsp then
346 if remsp <= 0 then
347 dump_buffer
348 rpos = 0
349 else
350 sits.copy_to(ns, slen, begin, rp)
351 rpos += slen
352 end
353 else
354 sits.copy_to(ns, remsp, begin, rp)
355 rpos = buf_size
356 dump_buffer
357 var nlen = slen - remsp
358 sits.copy_to(ns, nlen, begin + remsp, 0)
359 rpos = nlen
360 end
361 end
362
363 redef fun add(c) do
364 var rp = rpos
365 if rp >= buf_size then
366 dump_buffer
367 rp = 0
368 end
369 ns[rp] = c
370 rp += 1
371 length += 1
372 rpos = rp
373 end
374
375 # Converts the Buffer to a FlatString, appends it to
376 # the final String and re-allocates a new larger Buffer.
377 private fun dump_buffer do
378 written = false
379 var nstr = new FlatString.with_infos(ns, rpos - dumped, dumped, rpos - 1)
380 str += nstr
381 var bs = buf_size
382 bs = bs * 2
383 ns = new NativeString(bs)
384 buf_size = bs
385 dumped = 0
386 end
387
388 redef fun output do
389 str.output
390 new FlatString.with_infos(ns, rpos - dumped, dumped, rpos - 1).output
391 end
392
393 # Enlarge is useless here since the `Buffer`
394 # part is automatically dumped when necessary.
395 #
396 # Also, since the buffer can not be overused by a
397 # single string, there is no need for manual
398 # resizing.
399 #
400 # "You have no power here !"
401 redef fun enlarge(i) do end
402
403 redef fun to_s do
404 written = true
405 var nnslen = rpos - dumped
406 if nnslen == 0 then return str
407 return str + new FlatString.with_infos(ns, rpos - dumped, dumped, rpos - 1)
408 end
409
410 redef fun reverse do
411 # Flush the buffer in order to only have to reverse `str`.
412 if rpos > 0 and dumped != rpos then
413 str += new FlatString.with_infos(ns, rpos - dumped, dumped, rpos - 1)
414 dumped = rpos
415 end
416 str = str.reversed
417 end
418
419 redef fun upper do
420 if written then reset
421 str = str.to_upper
422 var mits = ns
423 for i in [0 .. rpos[ do
424 mits[i] = mits[i].to_upper
425 end
426 end
427
428 redef fun lower do
429 if written then reset
430 str = str.to_lower
431 var mits = ns
432 for i in [0 .. rpos[ do
433 mits[i] = mits[i].to_lower
434 end
435 end
436 end
437
438 redef class FlatString
439
440 redef fun insert_at(s, pos) do
441 var l = substring(0, pos)
442 var r = substring_from(pos)
443 return l + s + r
444 end
445
446 redef fun +(o) do
447 var s = o.to_s
448 var slen = s.length
449 var mlen = length
450 if slen == 0 then return self
451 if mlen == 0 then return s
452 var nlen = slen + mlen
453 if s isa FlatString then
454 if nlen > maxlen then return new Concat(self, s)
455 var mits = items
456 var sifrom = s.index_from
457 var mifrom = index_from
458 var sits = s.items
459 var ns = new NativeString(nlen + 1)
460 mits.copy_to(ns, mlen, mifrom, 0)
461 sits.copy_to(ns, slen, sifrom, mlen)
462 return ns.to_s_with_length(nlen)
463 else if s isa Concat then
464 var sl = s.left
465 var sllen = sl.length
466 if sllen + mlen > maxlen then return new Concat(self, s)
467 return new Concat(self + sl, s.right)
468 else
469 abort
470 end
471 end
472 end
473
474 # A simple linked list for use with iterators
475 private class RopeIterPiece
476 # The encapsulated node of the `Rope`
477 var node: String
478 # Was its left child (if any) visited ?
479 var ldone: Bool
480 # Was its right child (if any) visited ?
481 var rdone: Bool
482 # The previous node in the list.
483 var prev: nullable RopeIterPiece
484 end
485
486 # A reverse iterator capable of working with `Rope` objects
487 private class RopeReviter
488 super IndexedIterator[Char]
489
490 # Current NativeString
491 var ns: String
492 # Current position in NativeString
493 var pns: Int
494 # Position in the Rope (0-indexed)
495 var pos: Int
496 # Iterator on the substrings, does the Postfix part of
497 # the Rope traversal.
498 var subs: IndexedIterator[String]
499
500 init(root: Concat) is old_style_init do
501 pos = root.length - 1
502 subs = new ReverseRopeSubstrings(root)
503 ns = subs.item
504 pns = ns.length - 1
505 end
506
507 init from(root: Concat, pos: Int) do
508 self.pos = pos
509 subs = new ReverseRopeSubstrings.from(root, pos)
510 ns = subs.item
511 pns = pos - subs.index
512 end
513
514 redef fun index do return pos
515
516 redef fun is_ok do return pos >= 0
517
518 redef fun item do return ns[pns]
519
520 redef fun next do
521 pns -= 1
522 pos -= 1
523 if pns >= 0 then return
524 if not subs.is_ok then return
525 subs.next
526 if not subs.is_ok then return
527 ns = subs.item
528 pns = ns.length - 1
529 end
530 end
531
532 # Forward iterator on the chars of a `Rope`
533 private class RopeIter
534 super IndexedIterator[Char]
535
536 # Position in current `String`
537 var pns: Int
538 # Current `String` being iterated on
539 var str: String
540 # Substrings of the Rope
541 var subs: IndexedIterator[String]
542 # Maximum position to iterate on (e.g. Rope.length)
543 var max: Int
544 # Position (char) in the Rope (0-indexed)
545 var pos: Int
546
547 init(root: Concat) is old_style_init do
548 subs = new RopeSubstrings(root)
549 pns = 0
550 str = subs.item
551 max = root.length - 1
552 pos = 0
553 end
554
555 init from(root: Concat, pos: Int) do
556 subs = new RopeSubstrings.from(root, pos)
557 pns = pos - subs.index
558 self.pos = pos
559 str = subs.item
560 max = root.length - 1
561 end
562
563 redef fun item do return str[pns]
564
565 redef fun is_ok do return pos <= max
566
567 redef fun index do return pos
568
569 redef fun next do
570 pns += 1
571 pos += 1
572 if pns < subs.item.length then return
573 if not subs.is_ok then return
574 subs.next
575 if not subs.is_ok then return
576 str = subs.item
577 pns = 0
578 end
579 end
580
581 # Substrings of a Rope (i.e. Reverse postfix iterator on leaves)
582 private class ReverseRopeSubstrings
583 super IndexedIterator[String]
584
585 # Visit Stack
586 var iter: RopeIterPiece is noinit
587 # Position in `Rope`
588 var pos: Int is noinit
589
590 # Current leaf
591 var str: String is noinit
592
593 init(root: Concat) is old_style_init do
594 var r = new RopeIterPiece(root, false, true, null)
595 pos = root.length - 1
596 var lnod: String = root
597 loop
598 if lnod isa Concat then
599 lnod = lnod.right
600 r = new RopeIterPiece(lnod, false, true, r)
601 else
602 str = lnod
603 iter = r
604 break
605 end
606 end
607 end
608
609 init from(root: Concat, pos: Int) do
610 var r = new RopeIterPiece(root, false, true, null)
611 var rnod: String = root
612 var off = pos
613 loop
614 if rnod isa Concat then
615 if off >= rnod.left.length then
616 off -= rnod.left.length
617 rnod = rnod.right
618 r = new RopeIterPiece(rnod, false, true, r)
619 else
620 r.ldone = true
621 rnod = rnod.left
622 r = new RopeIterPiece(rnod, false, true, r)
623 end
624 else
625 str = rnod
626 r.ldone = true
627 iter = r
628 self.pos = pos - off
629 break
630 end
631 end
632 end
633
634 redef fun item do return str
635
636 redef fun index do return pos
637
638 redef fun is_ok do return pos >= 0
639
640 redef fun next do
641 if pos < 0 then return
642 var curr = iter.prev
643 var currit = curr.node
644 while curr != null do
645 currit = curr.node
646 if not currit isa Concat then
647 str = currit
648 pos -= str.length
649 iter = curr
650 return
651 end
652 if not curr.rdone then
653 curr.rdone = true
654 curr = new RopeIterPiece(currit.right, false, false, curr)
655 continue
656 end
657 if not curr.ldone then
658 curr.ldone = true
659 curr = new RopeIterPiece(currit.left, false, false, curr)
660 continue
661 end
662 curr = curr.prev
663 end
664 pos = -1
665 end
666 end
667
668 private class RopeBufSubstringIterator
669 super Iterator[FlatText]
670
671 # Iterator on the substrings of the building string
672 var iter: Iterator[FlatText]
673 # Makes a String out of the buffered part of the Ropebuffer
674 var nsstr: FlatString
675 # Did we attain the buffered part ?
676 var nsstr_done = false
677
678 init(str: RopeBuffer) is old_style_init do
679 iter = str.str.substrings
680 nsstr = new FlatString.with_infos(str.ns, str.rpos - str.dumped, str.dumped, str.rpos - 1)
681 if str.length == 0 then nsstr_done = true
682 end
683
684 redef fun is_ok do return iter.is_ok or not nsstr_done
685
686 redef fun item do
687 assert is_ok
688 if iter.is_ok then return iter.item
689 return nsstr
690 end
691
692 redef fun next do
693 if iter.is_ok then
694 iter.next
695 return
696 end
697 nsstr_done = true
698 end
699 end
700
701 # Substrings of a Rope (i.e. Postfix iterator on leaves)
702 private class RopeSubstrings
703 super IndexedIterator[FlatString]
704
705 # Visit Stack
706 var iter: RopeIterPiece is noinit
707 # Position in `Rope`
708 var pos: Int is noinit
709 # Maximum position in `Rope` (i.e. length - 1)
710 var max: Int is noinit
711
712 # Current leaf
713 var str: FlatString is noinit
714
715 init(root: Concat) is old_style_init do
716 var r = new RopeIterPiece(root, true, false, null)
717 pos = 0
718 max = root.length - 1
719 var rnod: String = root
720 loop
721 if rnod isa Concat then
722 rnod = rnod.left
723 r = new RopeIterPiece(rnod, true, false, r)
724 else
725 str = rnod.as(FlatString)
726 r.rdone = true
727 iter = r
728 break
729 end
730 end
731 end
732
733 init from(root: Concat, pos: Int) do
734 var r = new RopeIterPiece(root, true, false, null)
735 max = root.length - 1
736 var rnod: String = root
737 var off = pos
738 loop
739 if rnod isa Concat then
740 if off >= rnod.left.length then
741 r.rdone = true
742 off -= rnod.left.length
743 rnod = rnod.right
744 r = new RopeIterPiece(rnod, true, false, r)
745 else
746 rnod = rnod.left
747 r = new RopeIterPiece(rnod, true, false, r)
748 end
749 else
750 str = rnod.as(FlatString)
751 r.rdone = true
752 iter = r
753 self.pos = pos - off
754 break
755 end
756 end
757 end
758
759 redef fun item do return str
760
761 redef fun is_ok do return pos <= max
762
763 redef fun index do return pos
764
765 redef fun next do
766 pos += str.length
767 if pos > max then return
768 var it = iter.prev
769 var rnod = it.node
770 loop
771 if not rnod isa Concat then
772 it.ldone = true
773 it.rdone = true
774 str = rnod.as(FlatString)
775 iter = it.as(not null)
776 break
777 end
778 if not it.ldone then
779 rnod = rnod.left
780 it.ldone = true
781 it = new RopeIterPiece(rnod, false, false, it)
782 else if not it.rdone then
783 it.rdone = true
784 rnod = rnod.right
785 it = new RopeIterPiece(rnod, false, false, it)
786 else
787 it = it.prev
788 rnod = it.node
789 continue
790 end
791 end
792 end
793 end
794
795 # Implementation of a `StringCharView` for `Concat` objects
796 private class RopeChars
797 super StringCharView
798
799 redef type SELFTYPE: Concat
800
801 redef fun [](i) do
802 return target[i]
803 end
804
805 redef fun iterator_from(i) do return new RopeIter.from(target, i)
806
807 redef fun reverse_iterator_from(i) do return new RopeReviter.from(target, i)
808
809 end
810
811 # An Iterator over a RopeBuffer.
812 class RopeBufferIter
813 super IndexedIterator[Char]
814
815 # Subiterator.
816 var sit: IndexedIterator[Char]
817
818 # Native string iterated over.
819 var ns: NativeString
820
821 # Current position in `ns`.
822 var pns: Int
823
824 # Maximum position iterable.
825 var maxpos: Int
826
827 redef var index
828
829 # Init the iterator from a RopeBuffer.
830 init(t: RopeBuffer) is old_style_init do
831 ns = t.ns
832 maxpos = t.rpos
833 sit = t.str.chars.iterator
834 pns = t.dumped
835 index = 0
836 end
837
838 # Init the iterator from a RopeBuffer starting from `pos`.
839 init from(t: RopeBuffer, pos: Int) do
840 ns = t.ns
841 maxpos = t.length
842 sit = t.str.chars.iterator_from(pos)
843 pns = pos - t.str.length
844 index = pos
845 end
846
847 redef fun is_ok do return index < maxpos
848
849 redef fun item do
850 if sit.is_ok then return sit.item
851 return ns[pns]
852 end
853
854 redef fun next do
855 index += 1
856 if sit.is_ok then
857 sit.next
858 else
859 pns += 1
860 end
861 end
862 end
863
864 # Reverse iterator over a RopeBuffer.
865 class RopeBufferReviter
866 super IndexedIterator[Char]
867
868 # Subiterator.
869 var sit: IndexedIterator[Char]
870
871 # Native string iterated over.
872 var ns: NativeString
873
874 # Current position in `ns`.
875 var pns: Int
876
877 redef var index
878
879 # Init the iterator from a RopeBuffer.
880 init(tgt: RopeBuffer) is old_style_init do
881 sit = tgt.str.chars.reverse_iterator
882 pns = tgt.rpos - 1
883 index = tgt.length - 1
884 ns = tgt.ns
885 end
886
887 # Init the iterator from a RopeBuffer starting from `pos`.
888 init from(tgt: RopeBuffer, pos: Int) do
889 sit = tgt.str.chars.reverse_iterator_from(pos - tgt.rpos - tgt.dumped)
890 pns = pos - tgt.str.length
891 index = pos
892 ns = tgt.ns
893 end
894
895 redef fun is_ok do return index > 0
896
897 redef fun item do
898 if pns >= 0 then return ns[pns]
899 return sit.item
900 end
901
902 redef fun next do
903 index -= 1
904 if pns >= 0 then
905 pns -= 1
906 else
907 sit.next
908 end
909 end
910 end
911
912 # View on the chars of a `RopeBuffer`
913 class RopeBufferChars
914 super BufferCharView
915
916 redef type SELFTYPE: RopeBuffer
917
918 redef fun [](i) do
919 if i < target.str.length then
920 return target.str[i]
921 else
922 return target.ns[i - target.str.length]
923 end
924 end
925
926 redef fun []=(i,c) do
927 if i == target.length then target.add c
928 if i < target.str.length then
929 var s = target.str
930 var l = s.substring(0, i)
931 var r = s.substring_from(i + 1)
932 target.str = l + c.to_s + r
933 else
934 target.ns[i - target.str.length] = c
935 end
936 end
937
938 redef fun add(c) do target.add c
939
940 redef fun push(c) do target.add c
941
942 redef fun iterator_from(i) do return new RopeBufferIter.from(target, i)
943
944 redef fun reverse_iterator_from(i) do return new RopeBufferReviter.from(target, i)
945 end