text/ropes: Fixed `RopeBuffer::add` when dealing with something other than an ASCII...
[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(self)
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(self)
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 + len < llen then return lft.substring(from,len)
158 var lsublen = llen - from
159 return lft.substring_from(from) + _right.substring(0, len - lsublen)
160 else
161 return _right.substring(from - llen, len)
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(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, rpos - 1)
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, rpos - 1)
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, rpos - 1).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, rpos - 1)
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, nlen - 1, 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
597 # Current position in NativeString
598 var pns: Int
599 # Position in the Rope (0-indexed)
600 var pos: Int
601 # Iterator on the substrings, does the Postfix part of
602 # the Rope traversal.
603 var subs: IndexedIterator[FlatString]
604
605 init(root: Concat) is old_style_init do
606 pos = root._bytelen - 1
607 subs = new ReverseRopeSubstrings(root)
608 var s = subs.item
609 ns = s._items
610 pns = s._last_byte
611 end
612
613 init from(root: Concat, pos: Int) do
614 self.pos = pos
615 subs = new ReverseRopeSubstrings.from(root, pos)
616 var s = subs.item
617 ns = s._items
618 pns = pos - subs.index
619 end
620
621 redef fun index do return pos
622
623 redef fun is_ok do return pos >= 0
624
625 redef fun item do return ns[pns]
626
627 redef fun next do
628 pns -= 1
629 pos -= 1
630 if pns >= 0 then return
631 if not subs.is_ok then return
632 subs.next
633 if not subs.is_ok then return
634 var s = subs.item
635 ns = s._items
636 pns = s._last_byte
637 end
638 end
639
640 # Forward iterator on the bytes of a `Rope`
641 private class RopeByteIterator
642 super IndexedIterator[Byte]
643
644 # Position in current `String`
645 var pns: Int
646 # Current `String` being iterated on
647 var ns: NativeString
648 # Substrings of the Rope
649 var subs: IndexedIterator[FlatString]
650 # Maximum position to iterate on (e.g. Rope.length)
651 var max: Int
652 # Position (char) in the Rope (0-indexed)
653 var pos: Int
654
655 init(root: Concat) is old_style_init do
656 subs = new RopeSubstrings(root)
657 pns = 0
658 ns = subs.item._items
659 max = root.length - 1
660 pos = 0
661 end
662
663 init from(root: Concat, pos: Int) do
664 subs = new RopeSubstrings.from(root, pos)
665 pns = pos - subs.index
666 self.pos = pos
667 ns = subs.item._items
668 max = root.length - 1
669 end
670
671 redef fun item do return ns[pns]
672
673 redef fun is_ok do return pos <= max
674
675 redef fun index do return pos
676
677 redef fun next do
678 pns += 1
679 pos += 1
680 if pns < subs.item._bytelen then return
681 if not subs.is_ok then return
682 subs.next
683 if not subs.is_ok then return
684 ns = subs.item._items
685 pns = 0
686 end
687 end
688
689
690 # A reverse iterator capable of working with `Rope` objects
691 private class RopeCharReverseIterator
692 super IndexedIterator[Char]
693
694 # Current NativeString
695 var ns: String
696 # Current position in NativeString
697 var pns: Int
698 # Position in the Rope (0-indexed)
699 var pos: Int
700 # Iterator on the substrings, does the Postfix part of
701 # the Rope traversal.
702 var subs: IndexedIterator[String]
703
704 init(root: Concat) is old_style_init do
705 pos = root.length - 1
706 subs = new ReverseRopeSubstrings(root)
707 ns = subs.item
708 pns = ns.length - 1
709 end
710
711 init from(root: Concat, pos: Int) do
712 self.pos = pos
713 subs = new ReverseRopeSubstrings.from(root, pos)
714 ns = subs.item
715 pns = pos - subs.index
716 end
717
718 redef fun index do return pos
719
720 redef fun is_ok do return pos >= 0
721
722 redef fun item do return ns[pns]
723
724 redef fun next do
725 pns -= 1
726 pos -= 1
727 if pns >= 0 then return
728 if not subs.is_ok then return
729 subs.next
730 if not subs.is_ok then return
731 ns = subs.item
732 pns = ns.length - 1
733 end
734 end
735
736 # Forward iterator on the chars of a `Rope`
737 private class RopeCharIterator
738 super IndexedIterator[Char]
739
740 # Position in current `String`
741 var pns: Int
742 # Current `String` being iterated on
743 var str: String
744 # Substrings of the Rope
745 var subs: IndexedIterator[String]
746 # Maximum position to iterate on (e.g. Rope.length)
747 var max: Int
748 # Position (char) in the Rope (0-indexed)
749 var pos: Int
750
751 init(root: Concat) is old_style_init do
752 subs = new RopeSubstrings(root)
753 pns = 0
754 str = subs.item
755 max = root.length - 1
756 pos = 0
757 end
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(root: Concat) is old_style_init do
798 var r = new RopeCharIteratorPiece(root, false, true, null)
799 pos = root.length - 1
800 var lnod: String = root
801 loop
802 if lnod isa Concat then
803 lnod = lnod._right
804 r = new RopeCharIteratorPiece(lnod, false, true, r)
805 else
806 str = lnod.as(FlatString)
807 iter = r
808 break
809 end
810 end
811 end
812
813 init from(root: Concat, pos: Int) do
814 var r = new RopeCharIteratorPiece(root, false, true, null)
815 var rnod: String = root
816 var off = pos
817 loop
818 if rnod isa Concat then
819 if off >= rnod._left.length then
820 off -= rnod._left.length
821 rnod = rnod._right
822 r = new RopeCharIteratorPiece(rnod, false, true, r)
823 else
824 r.ldone = true
825 rnod = rnod._left
826 r = new RopeCharIteratorPiece(rnod, false, true, r)
827 end
828 else
829 str = rnod.as(FlatString)
830 r.ldone = true
831 iter = r
832 self.pos = pos - off
833 break
834 end
835 end
836 end
837
838 redef fun item do return str
839
840 redef fun index do return pos
841
842 redef fun is_ok do return pos >= 0
843
844 redef fun next do
845 if pos < 0 then return
846 var curr = iter.prev
847 var currit = curr.node
848 while curr != null do
849 currit = curr.node
850 if not currit isa Concat then
851 str = currit.as(FlatString)
852 pos -= str.length
853 iter = curr
854 return
855 end
856 if not curr.rdone then
857 curr.rdone = true
858 curr = new RopeCharIteratorPiece(currit._right, false, false, curr)
859 continue
860 end
861 if not curr.ldone then
862 curr.ldone = true
863 curr = new RopeCharIteratorPiece(currit._left, false, false, curr)
864 continue
865 end
866 curr = curr.prev
867 end
868 pos = -1
869 end
870 end
871
872 private class RopeBufSubstringIterator
873 super Iterator[FlatText]
874
875 # Iterator on the substrings of the building string
876 var iter: Iterator[FlatText]
877 # Makes a String out of the buffered part of the Ropebuffer
878 var nsstr: FlatString
879 # Did we attain the buffered part ?
880 var nsstr_done = false
881
882 init(str: RopeBuffer) is old_style_init do
883 iter = str.str.substrings
884 nsstr = new FlatString.with_infos(str.ns, str.rpos - str.dumped, str.dumped, str.rpos - 1)
885 if str.length == 0 then nsstr_done = true
886 end
887
888 redef fun is_ok do return iter.is_ok or not nsstr_done
889
890 redef fun item do
891 assert is_ok
892 if iter.is_ok then return iter.item
893 return nsstr
894 end
895
896 redef fun next do
897 if iter.is_ok then
898 iter.next
899 return
900 end
901 nsstr_done = true
902 end
903 end
904
905 # Substrings of a Rope (i.e. Postfix iterator on leaves)
906 private class RopeSubstrings
907 super IndexedIterator[FlatString]
908
909 # Visit Stack
910 var iter: RopeCharIteratorPiece is noinit
911 # Position in `Rope`
912 var pos: Int is noinit
913 # Maximum position in `Rope` (i.e. length - 1)
914 var max: Int is noinit
915
916 # Current leaf
917 var str: FlatString is noinit
918
919 init(root: Concat) is old_style_init do
920 var r = new RopeCharIteratorPiece(root, true, false, null)
921 pos = 0
922 max = root.length - 1
923 var rnod: String = root
924 loop
925 if rnod isa Concat then
926 rnod = rnod._left
927 r = new RopeCharIteratorPiece(rnod, true, false, r)
928 else
929 str = rnod.as(FlatString)
930 r.rdone = true
931 iter = r
932 break
933 end
934 end
935 end
936
937 init from(root: Concat, pos: Int) do
938 var r = new RopeCharIteratorPiece(root, true, false, null)
939 max = root.length - 1
940 var rnod: String = root
941 var off = pos
942 loop
943 if rnod isa Concat then
944 if off >= rnod._left.length then
945 r.rdone = true
946 off -= rnod._left.length
947 rnod = rnod._right
948 r = new RopeCharIteratorPiece(rnod, true, false, r)
949 else
950 rnod = rnod._left
951 r = new RopeCharIteratorPiece(rnod, true, false, r)
952 end
953 else
954 str = rnod.as(FlatString)
955 r.rdone = true
956 iter = r
957 self.pos = pos - off
958 break
959 end
960 end
961 end
962
963 redef fun item do return str
964
965 redef fun is_ok do return pos <= max
966
967 redef fun index do return pos
968
969 redef fun next do
970 pos += str.length
971 if pos > max then return
972 var it = iter.prev
973 var rnod = it.node
974 loop
975 if not rnod isa Concat then
976 it.ldone = true
977 it.rdone = true
978 str = rnod.as(FlatString)
979 iter = it.as(not null)
980 break
981 end
982 if not it.ldone then
983 rnod = rnod._left
984 it.ldone = true
985 it = new RopeCharIteratorPiece(rnod, false, false, it)
986 else if not it.rdone then
987 it.rdone = true
988 rnod = rnod._right
989 it = new RopeCharIteratorPiece(rnod, false, false, it)
990 else
991 it = it.prev
992 rnod = it.node
993 continue
994 end
995 end
996 end
997 end
998
999 # Implementation of a `StringCharView` for `Concat` objects
1000 private class RopeChars
1001 super StringCharView
1002
1003 redef type SELFTYPE: Concat
1004
1005 redef fun [](i) do
1006 return target[i]
1007 end
1008
1009 redef fun iterator_from(i) do return new RopeCharIterator.from(target, i)
1010
1011 redef fun reverse_iterator_from(i) do return new RopeCharReverseIterator.from(target, i)
1012
1013 end
1014
1015 # Implementation of a `StringCharView` for `Concat` objects
1016 private class RopeBytes
1017 super StringByteView
1018
1019 redef type SELFTYPE: Concat
1020
1021 redef fun [](i) do
1022 var nod: String = target
1023 loop
1024 if nod isa FlatString then return nod._items[i]
1025 if not nod isa Concat then abort
1026 var lft = nod._left
1027 if lft.bytelen >= i then
1028 nod = nod._right
1029 else
1030 nod = lft
1031 end
1032 end
1033 end
1034
1035 redef fun iterator_from(i) do return new RopeByteIterator.from(target, i)
1036
1037 redef fun reverse_iterator_from(i) do return new RopeByteReverseIterator.from(target, i)
1038
1039 end
1040
1041 # An Iterator over a RopeBuffer.
1042 class RopeBufferCharIterator
1043 super IndexedIterator[Char]
1044
1045 # Subiterator.
1046 var sit: IndexedIterator[Char]
1047
1048 redef fun index do return sit.index
1049
1050 # Init the iterator from a RopeBuffer.
1051 init(t: RopeBuffer) is old_style_init do
1052 t.persist_buffer
1053 sit = t.str.chars.iterator
1054 end
1055
1056 # Init the iterator from a RopeBuffer starting from `pos`.
1057 init from(t: RopeBuffer, pos: Int) do
1058 t.persist_buffer
1059 sit = t.str.chars.iterator_from(pos)
1060 end
1061
1062 redef fun is_ok do return sit.is_ok
1063
1064 redef fun item do
1065 assert is_ok
1066 return sit.item
1067 end
1068
1069 redef fun next do sit.next
1070 end
1071
1072 # Reverse iterator over a RopeBuffer.
1073 class RopeBufferCharReverseIterator
1074 super IndexedIterator[Char]
1075
1076 # Subiterator.
1077 var sit: IndexedIterator[Char]
1078
1079 redef fun index do return sit.index
1080
1081 # Init the iterator from a RopeBuffer.
1082 init(tgt: RopeBuffer) is old_style_init do
1083 tgt.persist_buffer
1084 sit = tgt.str.chars.reverse_iterator
1085 end
1086
1087 # Init the iterator from a RopeBuffer starting from `pos`.
1088 init from(tgt: RopeBuffer, pos: Int) do
1089 tgt.persist_buffer
1090 sit = tgt.str.chars.reverse_iterator_from(pos)
1091 end
1092
1093 redef fun is_ok do return sit.is_ok
1094
1095 redef fun item do
1096 assert is_ok
1097 return sit.item
1098 end
1099
1100 redef fun next do sit.next
1101 end
1102
1103 # View on the chars of a `RopeBuffer`
1104 class RopeBufferChars
1105 super BufferCharView
1106
1107 redef type SELFTYPE: RopeBuffer
1108
1109 redef fun [](i) do return target[i]
1110
1111 redef fun []=(i,c) do target[i] = c
1112
1113 redef fun add(c) do target.add c
1114
1115 redef fun push(c) do target.add c
1116
1117 redef fun iterator_from(i) do return new RopeBufferCharIterator.from(target, i)
1118
1119 redef fun reverse_iterator_from(i) do return new RopeBufferCharReverseIterator.from(target, i)
1120 end
1121
1122 # An Iterator over a RopeBuffer.
1123 class RopeBufferByteIterator
1124 super IndexedIterator[Byte]
1125
1126 # Subiterator.
1127 var sit: IndexedIterator[Byte]
1128
1129 # Native string iterated over.
1130 var ns: NativeString
1131
1132 # Current position in `ns`.
1133 var pns: Int
1134
1135 # Maximum position iterable.
1136 var maxpos: Int
1137
1138 redef var index
1139
1140 # Init the iterator from a RopeBuffer.
1141 init(t: RopeBuffer) is old_style_init do
1142 ns = t.ns
1143 maxpos = t._bytelen
1144 sit = t.str.bytes.iterator
1145 pns = t.dumped
1146 index = 0
1147 end
1148
1149 # Init the iterator from a RopeBuffer starting from `pos`.
1150 init from(t: RopeBuffer, pos: Int) do
1151 ns = t.ns
1152 maxpos = t._bytelen
1153 sit = t.str.bytes.iterator_from(pos)
1154 pns = pos - t.str.length
1155 index = pos
1156 end
1157
1158 redef fun is_ok do return index < maxpos
1159
1160 redef fun item do
1161 if sit.is_ok then return sit.item
1162 return ns[pns]
1163 end
1164
1165 redef fun next do
1166 index += 1
1167 if sit.is_ok then
1168 sit.next
1169 else
1170 pns += 1
1171 end
1172 end
1173 end
1174
1175 # Reverse iterator over a RopeBuffer.
1176 class RopeBufferByteReverseIterator
1177 super IndexedIterator[Byte]
1178
1179 # Subiterator.
1180 var sit: IndexedIterator[Byte]
1181
1182 # Native string iterated over.
1183 var ns: NativeString
1184
1185 # Current position in `ns`.
1186 var pns: Int
1187
1188 redef var index
1189
1190 # Init the iterator from a RopeBuffer.
1191 init(tgt: RopeBuffer) is old_style_init do
1192 sit = tgt.str.bytes.reverse_iterator
1193 pns = tgt.rpos - 1
1194 index = tgt._bytelen - 1
1195 ns = tgt.ns
1196 end
1197
1198 # Init the iterator from a RopeBuffer starting from `pos`.
1199 init from(tgt: RopeBuffer, pos: Int) do
1200 sit = tgt.str.bytes.reverse_iterator_from(pos - (tgt.rpos - tgt.dumped))
1201 pns = pos - tgt.str.bytelen + tgt.rpos
1202 index = pos
1203 ns = tgt.ns
1204 end
1205
1206 redef fun is_ok do return index >= 0
1207
1208 redef fun item do
1209 if pns >= 0 then return ns[pns]
1210 return sit.item
1211 end
1212
1213 redef fun next do
1214 index -= 1
1215 if pns > 0 then
1216 pns -= 1
1217 else
1218 sit.next
1219 end
1220 end
1221 end
1222
1223 # View on the chars of a `RopeBuffer`
1224 class RopeBufferBytes
1225 super BufferByteView
1226
1227 redef type SELFTYPE: RopeBuffer
1228
1229 redef fun [](i) do
1230 if i < target.str.bytelen then
1231 return target.str.bytes[i]
1232 else
1233 return target.ns[i - target.str.bytelen]
1234 end
1235 end
1236
1237 redef fun iterator_from(i) do return new RopeBufferByteIterator.from(target, i)
1238
1239 redef fun reverse_iterator_from(i) do return new RopeBufferByteReverseIterator.from(target, i)
1240 end