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