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