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