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