c0a2421effc141a1d6c1486f753412cba81418ff
[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 bytes is lazy do return new RopeBytes(self)
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 RopeCharIterator(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 redef var bytes: Sequence[Byte] is lazy do return new RopeBufferBytes(self)
203
204 # The final string being built on the fly
205 private var str: String is noinit
206
207 # Current concatenation buffer
208 private var ns: NativeString is noinit
209
210 # Next available (e.g. unset) character in the `Buffer`
211 private var rpos = 0
212
213 # Keeps track of the buffer's currently dumped part
214 #
215 # This might happen if for instance, a String was being
216 # built by concatenating small parts of string and suddenly
217 # a long string (length > maxlen) is appended.
218 private var dumped: Int is noinit
219
220 # Length of the complete rope
221 redef var length = 0
222
223 # Length of the mutable part
224 #
225 # Is also used as base to compute the size of the next
226 # mutable native string (`ns`)
227 private var buf_size: Int is noinit
228
229 redef fun substrings do return new RopeBufSubstringIterator(self)
230
231 # Builds an empty `RopeBuffer`
232 init do
233 str = ""
234 ns = new NativeString(maxlen)
235 buf_size = maxlen
236 dumped = 0
237 end
238
239 # Builds a new `RopeBuffer` with `str` in it.
240 init from(str: String) do
241 self.str = str
242 ns = new NativeString(maxlen)
243 buf_size = maxlen
244 length = str.length
245 dumped = 0
246 end
247
248 # Resets the informations of the `Buffer`
249 #
250 # This is called when doing in-place modifications
251 # on a previously to_s'd `RopeBuffer`
252 private fun reset do
253 var nns = new NativeString(buf_size)
254 var blen = rpos - dumped
255 ns.copy_to(nns, blen, dumped, 0)
256 ns = nns
257 dumped = 0
258 rpos = blen
259 written = false
260 end
261
262 redef fun empty do return new RopeBuffer
263
264 redef fun clear do
265 str = ""
266 length = 0
267 rpos = 0
268 dumped = 0
269 if written then
270 ns = new NativeString(buf_size)
271 written = false
272 end
273 end
274
275 redef fun substring(from, count) do
276 var strlen = str.length
277
278 if from < 0 then
279 count += from
280 if count < 0 then count = 0
281 from = 0
282 end
283
284 if count > length then count = length - from
285
286 if count == 0 then return empty
287
288 if from < strlen then
289 var subpos = strlen - from
290 if count <= subpos then
291 return new RopeBuffer.from(str.substring(from, count))
292 else
293 var l = str.substring_from(from)
294 var rem = count - subpos
295 var nns = new NativeString(rem)
296 ns.copy_to(nns, rem, dumped, 0)
297 return new RopeBuffer.from(l + nns.to_s_with_length(rem))
298 end
299 else
300 var nns = new NativeString(count)
301 ns.copy_to(nns, count, dumped, 0)
302 return new RopeBuffer.from(nns.to_s_with_length(count))
303 end
304 end
305
306 redef fun append(s) do
307 var slen = s.length
308 length += slen
309 var rp = rpos
310 if s isa Rope or slen > maxlen then
311 if rp > 0 and dumped != rp then
312 str += new FlatString.with_infos(ns, rp - dumped, dumped, rp - 1)
313 dumped = rp
314 end
315 str = str + s
316 return
317 end
318 var remsp = buf_size - rp
319 var sits: NativeString
320 var begin: Int
321 if s isa FlatString then
322 begin = s.index_from
323 sits = s.items
324 else if s isa FlatBuffer then
325 begin = 0
326 sits = s.items
327 else
328 if slen <= remsp then
329 for i in s.chars do
330 ns[rpos] = i
331 rpos += 1
332 end
333 else
334 var spos = 0
335 for i in [0..remsp[ do
336 ns[rpos] = s[spos]
337 rpos += 1
338 spos += 1
339 end
340 dump_buffer
341 while spos < slen do
342 ns[rpos] = s[spos]
343 spos += 1
344 rpos += 1
345 end
346 end
347 return
348 end
349 if slen <= remsp then
350 if remsp <= 0 then
351 dump_buffer
352 rpos = 0
353 else
354 sits.copy_to(ns, slen, begin, rp)
355 rpos += slen
356 end
357 else
358 sits.copy_to(ns, remsp, begin, rp)
359 rpos = buf_size
360 dump_buffer
361 var nlen = slen - remsp
362 sits.copy_to(ns, nlen, begin + remsp, 0)
363 rpos = nlen
364 end
365 end
366
367 redef fun add(c) do
368 var rp = rpos
369 if rp >= buf_size then
370 dump_buffer
371 rp = 0
372 end
373 # TODO: Fix when supporting UTF-8
374 ns[rp] = c.ascii.to_b
375 rp += 1
376 length += 1
377 rpos = rp
378 end
379
380 private fun add_byte(b: Byte) do
381 var rp = rpos
382 if rp >= buf_size then
383 dump_buffer
384 rp = 0
385 end
386 ns[rp] = b
387 rp += 1
388 length += 1
389 rpos = rp
390 end
391
392 # Converts the Buffer to a FlatString, appends it to
393 # the final String and re-allocates a new larger Buffer.
394 private fun dump_buffer do
395 written = false
396 var nstr = new FlatString.with_infos(ns, rpos - dumped, dumped, rpos - 1)
397 str += nstr
398 var bs = buf_size
399 bs = bs * 2
400 ns = new NativeString(bs)
401 buf_size = bs
402 dumped = 0
403 end
404
405 redef fun output do
406 str.output
407 new FlatString.with_infos(ns, rpos - dumped, dumped, rpos - 1).output
408 end
409
410 # Enlarge is useless here since the `Buffer`
411 # part is automatically dumped when necessary.
412 #
413 # Also, since the buffer can not be overused by a
414 # single string, there is no need for manual
415 # resizing.
416 #
417 # "You have no power here !"
418 redef fun enlarge(i) do end
419
420 redef fun to_s do
421 written = true
422 var nnslen = rpos - dumped
423 if nnslen == 0 then return str
424 return str + new FlatString.with_infos(ns, rpos - dumped, dumped, rpos - 1)
425 end
426
427 redef fun reverse do
428 # Flush the buffer in order to only have to reverse `str`.
429 if rpos > 0 and dumped != rpos then
430 str += new FlatString.with_infos(ns, rpos - dumped, dumped, rpos - 1)
431 dumped = rpos
432 end
433 str = str.reversed
434 end
435
436 redef fun upper do
437 if written then reset
438 str = str.to_upper
439 var mits = ns
440 for i in [0 .. rpos[ do
441 mits[i] = mits[i].to_upper
442 end
443 end
444
445 redef fun lower do
446 if written then reset
447 str = str.to_lower
448 var mits = ns
449 for i in [0 .. rpos[ do
450 mits[i] = mits[i].to_lower
451 end
452 end
453 end
454
455 redef class FlatString
456
457 redef fun insert_at(s, pos) do
458 var l = substring(0, pos)
459 var r = substring_from(pos)
460 return l + s + r
461 end
462
463 redef fun +(o) do
464 var s = o.to_s
465 var slen = s.length
466 var mlen = length
467 if slen == 0 then return self
468 if mlen == 0 then return s
469 var nlen = slen + mlen
470 if s isa FlatString then
471 if nlen > maxlen then return new Concat(self, s)
472 var mits = items
473 var sifrom = s.index_from
474 var mifrom = index_from
475 var sits = s.items
476 var ns = new NativeString(nlen + 1)
477 mits.copy_to(ns, mlen, mifrom, 0)
478 sits.copy_to(ns, slen, sifrom, mlen)
479 return ns.to_s_with_length(nlen)
480 else if s isa Concat then
481 var sl = s.left
482 var sllen = sl.length
483 if sllen + mlen > maxlen then return new Concat(self, s)
484 return new Concat(self + sl, s.right)
485 else
486 abort
487 end
488 end
489 end
490
491 # A simple linked list for use with iterators
492 private class RopeCharIteratorPiece
493 # The encapsulated node of the `Rope`
494 var node: String
495 # Was its left child (if any) visited ?
496 var ldone: Bool
497 # Was its right child (if any) visited ?
498 var rdone: Bool
499 # The previous node in the list.
500 var prev: nullable RopeCharIteratorPiece
501 end
502
503 # A reverse iterator capable of working with `Rope` objects
504 private class RopeByteReverseIterator
505 super IndexedIterator[Byte]
506
507 # Current NativeString
508 var ns: NativeString
509 # Current position in NativeString
510 var pns: Int
511 # Position in the Rope (0-indexed)
512 var pos: Int
513 # Iterator on the substrings, does the Postfix part of
514 # the Rope traversal.
515 var subs: IndexedIterator[FlatString]
516
517 init(root: Concat) is old_style_init do
518 pos = root.length - 1
519 subs = new ReverseRopeSubstrings(root)
520 var s = subs.item
521 ns = s.items
522 pns = s.index_to
523 end
524
525 init from(root: Concat, pos: Int) do
526 self.pos = pos
527 subs = new ReverseRopeSubstrings.from(root, pos)
528 var s = subs.item
529 ns = s.items
530 pns = pos - subs.index
531 end
532
533 redef fun index do return pos
534
535 redef fun is_ok do return pos >= 0
536
537 redef fun item do return ns[pns]
538
539 redef fun next do
540 pns -= 1
541 pos -= 1
542 if pns >= 0 then return
543 if not subs.is_ok then return
544 subs.next
545 if not subs.is_ok then return
546 var s = subs.item
547 ns = s.items
548 pns = s.index_to
549 end
550 end
551
552 # Forward iterator on the bytes of a `Rope`
553 private class RopeByteIterator
554 super IndexedIterator[Byte]
555
556 # Position in current `String`
557 var pns: Int
558 # Current `String` being iterated on
559 var ns: NativeString
560 # Substrings of the Rope
561 var subs: IndexedIterator[FlatString]
562 # Maximum position to iterate on (e.g. Rope.length)
563 var max: Int
564 # Position (char) in the Rope (0-indexed)
565 var pos: Int
566
567 init(root: Concat) is old_style_init do
568 subs = new RopeSubstrings(root)
569 pns = 0
570 ns = subs.item.items
571 max = root.length - 1
572 pos = 0
573 end
574
575 init from(root: Concat, pos: Int) do
576 subs = new RopeSubstrings.from(root, pos)
577 pns = pos - subs.index
578 self.pos = pos
579 ns = subs.item.items
580 max = root.length - 1
581 end
582
583 redef fun item do return ns[pns]
584
585 redef fun is_ok do return pos <= max
586
587 redef fun index do return pos
588
589 redef fun next do
590 pns += 1
591 pos += 1
592 if pns < subs.item.length then return
593 if not subs.is_ok then return
594 subs.next
595 if not subs.is_ok then return
596 ns = subs.item.items
597 pns = 0
598 end
599 end
600
601
602 # A reverse iterator capable of working with `Rope` objects
603 private class RopeCharReverseIterator
604 super IndexedIterator[Char]
605
606 # Current NativeString
607 var ns: String
608 # Current position in NativeString
609 var pns: Int
610 # Position in the Rope (0-indexed)
611 var pos: Int
612 # Iterator on the substrings, does the Postfix part of
613 # the Rope traversal.
614 var subs: IndexedIterator[String]
615
616 init(root: Concat) is old_style_init do
617 pos = root.length - 1
618 subs = new ReverseRopeSubstrings(root)
619 ns = subs.item
620 pns = ns.length - 1
621 end
622
623 init from(root: Concat, pos: Int) do
624 self.pos = pos
625 subs = new ReverseRopeSubstrings.from(root, pos)
626 ns = subs.item
627 pns = pos - subs.index
628 end
629
630 redef fun index do return pos
631
632 redef fun is_ok do return pos >= 0
633
634 redef fun item do return ns[pns]
635
636 redef fun next do
637 pns -= 1
638 pos -= 1
639 if pns >= 0 then return
640 if not subs.is_ok then return
641 subs.next
642 if not subs.is_ok then return
643 ns = subs.item
644 pns = ns.length - 1
645 end
646 end
647
648 # Forward iterator on the chars of a `Rope`
649 private class RopeCharIterator
650 super IndexedIterator[Char]
651
652 # Position in current `String`
653 var pns: Int
654 # Current `String` being iterated on
655 var str: String
656 # Substrings of the Rope
657 var subs: IndexedIterator[String]
658 # Maximum position to iterate on (e.g. Rope.length)
659 var max: Int
660 # Position (char) in the Rope (0-indexed)
661 var pos: Int
662
663 init(root: Concat) is old_style_init do
664 subs = new RopeSubstrings(root)
665 pns = 0
666 str = subs.item
667 max = root.length - 1
668 pos = 0
669 end
670
671 init from(root: Concat, pos: Int) do
672 subs = new RopeSubstrings.from(root, pos)
673 pns = pos - subs.index
674 self.pos = pos
675 str = subs.item
676 max = root.length - 1
677 end
678
679 redef fun item do return str[pns]
680
681 redef fun is_ok do return pos <= max
682
683 redef fun index do return pos
684
685 redef fun next do
686 pns += 1
687 pos += 1
688 if pns < subs.item.length then return
689 if not subs.is_ok then return
690 subs.next
691 if not subs.is_ok then return
692 str = subs.item
693 pns = 0
694 end
695 end
696
697 # Substrings of a Rope (i.e. Reverse postfix iterator on leaves)
698 private class ReverseRopeSubstrings
699 super IndexedIterator[String]
700
701 # Visit Stack
702 var iter: RopeIterPiece is noinit
703 # Position in `Rope`
704 var pos: Int is noinit
705
706 # Current leaf
707 var str: String is noinit
708
709 init(root: Concat) is old_style_init do
710 var r = new RopeIterPiece(root, false, true, null)
711 pos = root.length - 1
712 var lnod: String = root
713 loop
714 if lnod isa Concat then
715 lnod = lnod.right
716 r = new RopeIterPiece(lnod, false, true, r)
717 else
718 str = lnod
719 iter = r
720 break
721 end
722 end
723 end
724
725 init from(root: Concat, pos: Int) do
726 var r = new RopeIterPiece(root, false, true, null)
727 var rnod: String = root
728 var off = pos
729 loop
730 if rnod isa Concat then
731 if off >= rnod.left.length then
732 off -= rnod.left.length
733 rnod = rnod.right
734 r = new RopeIterPiece(rnod, false, true, r)
735 else
736 r.ldone = true
737 rnod = rnod.left
738 r = new RopeIterPiece(rnod, false, true, r)
739 end
740 else
741 str = rnod
742 r.ldone = true
743 iter = r
744 self.pos = pos - off
745 break
746 end
747 end
748 end
749
750 redef fun item do return str
751
752 redef fun index do return pos
753
754 redef fun is_ok do return pos >= 0
755
756 redef fun next do
757 if pos < 0 then return
758 var curr = iter.prev
759 var currit = curr.node
760 while curr != null do
761 currit = curr.node
762 if not currit isa Concat then
763 str = currit
764 pos -= str.length
765 iter = curr
766 return
767 end
768 if not curr.rdone then
769 curr.rdone = true
770 curr = new RopeIterPiece(currit.right, false, false, curr)
771 continue
772 end
773 if not curr.ldone then
774 curr.ldone = true
775 curr = new RopeIterPiece(currit.left, false, false, curr)
776 continue
777 end
778 curr = curr.prev
779 end
780 pos = -1
781 end
782 end
783
784 private class RopeBufSubstringIterator
785 super Iterator[FlatText]
786
787 # Iterator on the substrings of the building string
788 var iter: Iterator[FlatText]
789 # Makes a String out of the buffered part of the Ropebuffer
790 var nsstr: FlatString
791 # Did we attain the buffered part ?
792 var nsstr_done = false
793
794 init(str: RopeBuffer) is old_style_init do
795 iter = str.str.substrings
796 nsstr = new FlatString.with_infos(str.ns, str.rpos - str.dumped, str.dumped, str.rpos - 1)
797 if str.length == 0 then nsstr_done = true
798 end
799
800 redef fun is_ok do return iter.is_ok or not nsstr_done
801
802 redef fun item do
803 assert is_ok
804 if iter.is_ok then return iter.item
805 return nsstr
806 end
807
808 redef fun next do
809 if iter.is_ok then
810 iter.next
811 return
812 end
813 nsstr_done = true
814 end
815 end
816
817 # Substrings of a Rope (i.e. Postfix iterator on leaves)
818 private class RopeSubstrings
819 super IndexedIterator[FlatString]
820
821 # Visit Stack
822 var iter: RopeIterPiece is noinit
823 # Position in `Rope`
824 var pos: Int is noinit
825 # Maximum position in `Rope` (i.e. length - 1)
826 var max: Int is noinit
827
828 # Current leaf
829 var str: FlatString is noinit
830
831 init(root: Concat) is old_style_init do
832 var r = new RopeIterPiece(root, true, false, null)
833 pos = 0
834 max = root.length - 1
835 var rnod: String = root
836 loop
837 if rnod isa Concat then
838 rnod = rnod.left
839 r = new RopeIterPiece(rnod, true, false, r)
840 else
841 str = rnod.as(FlatString)
842 r.rdone = true
843 iter = r
844 break
845 end
846 end
847 end
848
849 init from(root: Concat, pos: Int) do
850 var r = new RopeIterPiece(root, true, false, null)
851 max = root.length - 1
852 var rnod: String = root
853 var off = pos
854 loop
855 if rnod isa Concat then
856 if off >= rnod.left.length then
857 r.rdone = true
858 off -= rnod.left.length
859 rnod = rnod.right
860 r = new RopeIterPiece(rnod, true, false, r)
861 else
862 rnod = rnod.left
863 r = new RopeIterPiece(rnod, true, false, r)
864 end
865 else
866 str = rnod.as(FlatString)
867 r.rdone = true
868 iter = r
869 self.pos = pos - off
870 break
871 end
872 end
873 end
874
875 redef fun item do return str
876
877 redef fun is_ok do return pos <= max
878
879 redef fun index do return pos
880
881 redef fun next do
882 pos += str.length
883 if pos > max then return
884 var it = iter.prev
885 var rnod = it.node
886 loop
887 if not rnod isa Concat then
888 it.ldone = true
889 it.rdone = true
890 str = rnod.as(FlatString)
891 iter = it.as(not null)
892 break
893 end
894 if not it.ldone then
895 rnod = rnod.left
896 it.ldone = true
897 it = new RopeIterPiece(rnod, false, false, it)
898 else if not it.rdone then
899 it.rdone = true
900 rnod = rnod.right
901 it = new RopeIterPiece(rnod, false, false, it)
902 else
903 it = it.prev
904 rnod = it.node
905 continue
906 end
907 end
908 end
909 end
910
911 # Implementation of a `StringCharView` for `Concat` objects
912 private class RopeChars
913 super StringCharView
914
915 redef type SELFTYPE: Concat
916
917 redef fun [](i) do
918 return target[i]
919 end
920
921 redef fun iterator_from(i) do return new RopeCharIterator.from(target, i)
922
923 redef fun reverse_iterator_from(i) do return new RopeCharReverseIterator.from(target, i)
924
925 end
926
927 # Implementation of a `StringCharView` for `Concat` objects
928 private class RopeBytes
929 super StringByteView
930
931 redef type SELFTYPE: Concat
932
933 redef fun [](i) do
934 var b: Int
935 var nod: String = target
936 loop
937 if nod isa FlatString then return nod.items[i]
938 if not nod isa Concat then abort
939 if nod.left.bytelen >= i then
940 nod = nod.right
941 else
942 nod = nod.left
943 end
944 end
945 end
946
947 redef fun iterator_from(i) do return new RopeByteIterator.from(target, i)
948
949 redef fun reverse_iterator_from(i) do return new RopeByteReverseIterator.from(target, i)
950
951 end
952
953 # An Iterator over a RopeBuffer.
954 class RopeBufferCharIterator
955 super IndexedIterator[Char]
956
957 # Subiterator.
958 var sit: IndexedIterator[Char]
959
960 redef fun index do return sit.index
961
962 # Init the iterator from a RopeBuffer.
963 init(t: RopeBuffer) is old_style_init do
964 t.persist_buffer
965 sit = t.str.chars.iterator
966 end
967
968 # Init the iterator from a RopeBuffer starting from `pos`.
969 init from(t: RopeBuffer, pos: Int) do
970 t.persist_buffer
971 sit = t.str.chars.iterator_from(pos)
972 end
973
974 redef fun is_ok do return sit.is_ok
975
976 redef fun item do
977 assert is_ok
978 return sit.item
979 end
980
981 redef fun next do sit.next
982 end
983
984 # Reverse iterator over a RopeBuffer.
985 class RopeBufferCharReverseIterator
986 super IndexedIterator[Char]
987
988 # Subiterator.
989 var sit: IndexedIterator[Char]
990
991 redef fun index do return sit.index
992
993 # Init the iterator from a RopeBuffer.
994 init(tgt: RopeBuffer) is old_style_init do
995 tgt.persist_buffer
996 sit = tgt.str.chars.reverse_iterator
997 end
998
999 # Init the iterator from a RopeBuffer starting from `pos`.
1000 init from(tgt: RopeBuffer, pos: Int) do
1001 tgt.persist_buffer
1002 sit = tgt.str.chars.reverse_iterator_from(pos)
1003 end
1004
1005 redef fun is_ok do return sit.is_ok
1006
1007 redef fun item do
1008 assert is_ok
1009 return sit.item
1010 end
1011
1012 redef fun next do sit.next
1013 end
1014
1015 # View on the chars of a `RopeBuffer`
1016 class RopeBufferChars
1017 super BufferCharView
1018
1019 redef type SELFTYPE: RopeBuffer
1020
1021 redef fun [](i) do
1022 if i < target.str.length then
1023 return target.str[i]
1024 else
1025 # TODO: Fix when supporting UTF-8
1026 return target.ns[i - target.str.length].to_i.ascii
1027 end
1028 end
1029
1030 redef fun []=(i,c) do
1031 if i == target.length then target.add c
1032 if i < target.str.length then
1033 var s = target.str
1034 var l = s.substring(0, i)
1035 var r = s.substring_from(i + 1)
1036 target.str = l + c.to_s + r
1037 else
1038 # TODO: Fix when supporting UTF-8
1039 target.ns[i - target.str.length] = c.to_i.to_b
1040 end
1041 end
1042
1043 redef fun add(c) do target.add c
1044
1045 redef fun push(c) do target.add c
1046
1047 redef fun iterator_from(i) do return new RopeBufferCharIterator.from(target, i)
1048
1049 redef fun reverse_iterator_from(i) do return new RopeBufferCharReverseIterator.from(target, i)
1050 end
1051
1052 # An Iterator over a RopeBuffer.
1053 class RopeBufferByteIterator
1054 super IndexedIterator[Byte]
1055
1056 # Subiterator.
1057 var sit: IndexedIterator[Byte]
1058
1059 # Native string iterated over.
1060 var ns: NativeString
1061
1062 # Current position in `ns`.
1063 var pns: Int
1064
1065 # Maximum position iterable.
1066 var maxpos: Int
1067
1068 redef var index
1069
1070 # Init the iterator from a RopeBuffer.
1071 init(t: RopeBuffer) is old_style_init do
1072 ns = t.ns
1073 maxpos = t.rpos
1074 sit = t.str.bytes.iterator
1075 pns = t.dumped
1076 index = 0
1077 end
1078
1079 # Init the iterator from a RopeBuffer starting from `pos`.
1080 init from(t: RopeBuffer, pos: Int) do
1081 ns = t.ns
1082 maxpos = t.length
1083 sit = t.str.bytes.iterator_from(pos)
1084 pns = pos - t.str.length
1085 index = pos
1086 end
1087
1088 redef fun is_ok do return index < maxpos
1089
1090 redef fun item do
1091 if sit.is_ok then return sit.item
1092 return ns[pns]
1093 end
1094
1095 redef fun next do
1096 index += 1
1097 if sit.is_ok then
1098 sit.next
1099 else
1100 pns += 1
1101 end
1102 end
1103 end
1104
1105 # Reverse iterator over a RopeBuffer.
1106 class RopeBufferByteReverseIterator
1107 super IndexedIterator[Byte]
1108
1109 # Subiterator.
1110 var sit: IndexedIterator[Byte]
1111
1112 # Native string iterated over.
1113 var ns: NativeString
1114
1115 # Current position in `ns`.
1116 var pns: Int
1117
1118 redef var index
1119
1120 # Init the iterator from a RopeBuffer.
1121 init(tgt: RopeBuffer) is old_style_init do
1122 sit = tgt.str.bytes.reverse_iterator
1123 pns = tgt.rpos - 1
1124 index = tgt.length - 1
1125 ns = tgt.ns
1126 end
1127
1128 # Init the iterator from a RopeBuffer starting from `pos`.
1129 init from(tgt: RopeBuffer, pos: Int) do
1130 sit = tgt.str.bytes.reverse_iterator_from(pos - tgt.rpos - tgt.dumped)
1131 pns = pos - tgt.str.length
1132 index = pos
1133 ns = tgt.ns
1134 end
1135
1136 redef fun is_ok do return index > 0
1137
1138 redef fun item do
1139 if pns >= 0 then return ns[pns]
1140 return sit.item
1141 end
1142
1143 redef fun next do
1144 index -= 1
1145 if pns >= 0 then
1146 pns -= 1
1147 else
1148 sit.next
1149 end
1150 end
1151 end
1152
1153 # View on the chars of a `RopeBuffer`
1154 class RopeBufferBytes
1155 super BufferByteView
1156
1157 redef type SELFTYPE: RopeBuffer
1158
1159 redef fun [](i) do
1160 if i < target.str.bytelen then
1161 return target.str.bytes[i]
1162 else
1163 return target.ns[i - target.str.length]
1164 end
1165 end
1166
1167 redef fun []=(i,c) do
1168 if i == target.length then target.add_byte c
1169 if i < target.str.length then
1170 # FIXME: Will need to be optimized and rewritten with Unicode
1171 var s = target.str
1172 var l = s.substring(0, i)
1173 var r = s.substring_from(i + 1)
1174 target.str = l + c.to_i.ascii.to_s + r
1175 else
1176 target.ns[i - target.str.length] = c
1177 end
1178 end
1179
1180 redef fun add(c) do target.add_byte c
1181
1182 redef fun push(c) do target.add_byte c
1183
1184 redef fun iterator_from(i) do return new RopeBufferByteIterator.from(target, i)
1185
1186 redef fun reverse_iterator_from(i) do return new RopeBufferByteReverseIterator.from(target, i)
1187 end