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