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