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