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