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