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