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