lib_standard_array: reprovide Array::plain_to_s
[nit.git] / lib / standard / text / flat.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # This file is free software, which comes along with NIT. This software is
4 # distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
5 # without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
6 # PARTICULAR PURPOSE. You can modify it is you want, provided this header
7 # is kept unaltered, and a notification of the changes is added.
8 # You are allowed to redistribute it and sell it, alone or is a part of
9 # another product.
10
11 # All the array-based text representations
12 module flat
13
14 intrude import abstract_text
15
16 `{
17 #include <stdio.h>
18 #include <string.h>
19 `}
20
21 private class FlatSubstringsIter
22 super Iterator[FlatText]
23
24 var tgt: nullable FlatText
25
26 redef fun item do
27 assert is_ok
28 return tgt.as(not null)
29 end
30
31 redef fun is_ok do return tgt != null
32
33 redef fun next do tgt = null
34 end
35
36 # Immutable strings of characters.
37 class FlatString
38 super FlatText
39 super String
40
41 # Index in _items of the start of the string
42 private var index_from: Int is noinit
43
44 # Indes in _items of the last item of the string
45 private var index_to: Int is noinit
46
47 redef var chars = new FlatStringCharView(self) is lazy
48
49 redef fun [](index)
50 do
51 # Check that the index (+ index_from) is not larger than indexTo
52 # In other terms, if the index is valid
53 assert index >= 0
54 assert (index + index_from) <= index_to
55 return items[index + index_from]
56 end
57
58 ################################################
59 # AbstractString specific methods #
60 ################################################
61
62 redef fun reversed
63 do
64 var native = new NativeString(self.length + 1)
65 var length = self.length
66 var items = self.items
67 var pos = 0
68 var ipos = length-1
69 while pos < length do
70 native[pos] = items[ipos]
71 pos += 1
72 ipos -= 1
73 end
74 return native.to_s_with_length(self.length)
75 end
76
77 redef fun fast_cstring do return items.fast_cstring(index_from)
78
79 redef fun substring(from, count)
80 do
81 assert count >= 0
82
83 if from < 0 then
84 count += from
85 if count < 0 then count = 0
86 from = 0
87 end
88
89 var new_from = index_from + from
90
91 if (new_from + count) > index_to then
92 var new_len = index_to - new_from + 1
93 if new_len <= 0 then return empty
94 return new FlatString.with_infos(items, new_len, new_from, index_to)
95 end
96
97 if count <= 0 then return empty
98
99 var to = new_from + count - 1
100
101 return new FlatString.with_infos(items, to - new_from + 1, new_from, to)
102 end
103
104 redef fun empty do return "".as(FlatString)
105
106 redef fun to_upper
107 do
108 var outstr = new NativeString(self.length + 1)
109 var out_index = 0
110
111 var myitems = self.items
112 var index_from = self.index_from
113 var max = self.index_to
114
115 while index_from <= max do
116 outstr[out_index] = myitems[index_from].to_upper
117 out_index += 1
118 index_from += 1
119 end
120
121 outstr[self.length] = '\0'
122
123 return outstr.to_s_with_length(self.length)
124 end
125
126 redef fun to_lower
127 do
128 var outstr = new NativeString(self.length + 1)
129 var out_index = 0
130
131 var myitems = self.items
132 var index_from = self.index_from
133 var max = self.index_to
134
135 while index_from <= max do
136 outstr[out_index] = myitems[index_from].to_lower
137 out_index += 1
138 index_from += 1
139 end
140
141 outstr[self.length] = '\0'
142
143 return outstr.to_s_with_length(self.length)
144 end
145
146 redef fun output
147 do
148 var i = self.index_from
149 var imax = self.index_to
150 while i <= imax do
151 items[i].output
152 i += 1
153 end
154 end
155
156 ##################################################
157 # String Specific Methods #
158 ##################################################
159
160 # Low-level creation of a new string with given data.
161 #
162 # `items` will be used as is, without copy, to retrieve the characters of the string.
163 # Aliasing issues is the responsibility of the caller.
164 private init with_infos(items: NativeString, length: Int, from: Int, to: Int)
165 do
166 self.items = items
167 self.length = length
168 index_from = from
169 index_to = to
170 end
171
172 redef fun to_cstring
173 do
174 if real_items != null then
175 return real_items.as(not null)
176 else
177 var newItems = new NativeString(length + 1)
178 self.items.copy_to(newItems, length, index_from, 0)
179 newItems[length] = '\0'
180 self.real_items = newItems
181 return newItems
182 end
183 end
184
185 redef fun ==(other)
186 do
187 if not other isa FlatString then return super
188
189 if self.object_id == other.object_id then return true
190
191 var my_length = length
192
193 if other.length != my_length then return false
194
195 var my_index = index_from
196 var its_index = other.index_from
197
198 var last_iteration = my_index + my_length
199
200 var itsitems = other.items
201 var myitems = self.items
202
203 while my_index < last_iteration do
204 if myitems[my_index] != itsitems[its_index] then return false
205 my_index += 1
206 its_index += 1
207 end
208
209 return true
210 end
211
212 redef fun <(other)
213 do
214 if not other isa FlatString then return super
215
216 if self.object_id == other.object_id then return false
217
218 var my_curr_char : Char
219 var its_curr_char : Char
220
221 var curr_id_self = self.index_from
222 var curr_id_other = other.index_from
223
224 var my_items = self.items
225 var its_items = other.items
226
227 var my_length = self.length
228 var its_length = other.length
229
230 var max_iterations = curr_id_self + my_length
231
232 while curr_id_self < max_iterations do
233 my_curr_char = my_items[curr_id_self]
234 its_curr_char = its_items[curr_id_other]
235
236 if my_curr_char != its_curr_char then
237 if my_curr_char < its_curr_char then return true
238 return false
239 end
240
241 curr_id_self += 1
242 curr_id_other += 1
243 end
244
245 return my_length < its_length
246 end
247
248 redef fun +(s)
249 do
250 var my_length = self.length
251 var its_length = s.length
252
253 var total_length = my_length + its_length
254
255 var target_string = new NativeString(my_length + its_length + 1)
256
257 self.items.copy_to(target_string, my_length, index_from, 0)
258 if s isa FlatString then
259 s.items.copy_to(target_string, its_length, s.index_from, my_length)
260 else if s isa FlatBuffer then
261 s.items.copy_to(target_string, its_length, 0, my_length)
262 else
263 var curr_pos = my_length
264 for i in [0..s.length[ do
265 var c = s.chars[i]
266 target_string[curr_pos] = c
267 curr_pos += 1
268 end
269 end
270
271 target_string[total_length] = '\0'
272
273 return target_string.to_s_with_length(total_length)
274 end
275
276 redef fun *(i)
277 do
278 assert i >= 0
279
280 var my_length = self.length
281
282 var final_length = my_length * i
283
284 var my_items = self.items
285
286 var target_string = new NativeString(final_length + 1)
287
288 target_string[final_length] = '\0'
289
290 var current_last = 0
291
292 for iteration in [1 .. i] do
293 my_items.copy_to(target_string, my_length, 0, current_last)
294 current_last += my_length
295 end
296
297 return target_string.to_s_with_length(final_length)
298 end
299
300 redef fun hash
301 do
302 if hash_cache == null then
303 # djb2 hash algorithm
304 var h = 5381
305 var i = index_from
306
307 var myitems = items
308
309 while i <= index_to do
310 h = h.lshift(5) + h + myitems[i].ascii
311 i += 1
312 end
313
314 hash_cache = h
315 end
316
317 return hash_cache.as(not null)
318 end
319
320 redef fun substrings do return new FlatSubstringsIter(self)
321 end
322
323 private class FlatStringReverseIterator
324 super IndexedIterator[Char]
325
326 var target: FlatString
327
328 var target_items: NativeString
329
330 var curr_pos: Int
331
332 init with_pos(tgt: FlatString, pos: Int)
333 do
334 target = tgt
335 target_items = tgt.items
336 curr_pos = pos + tgt.index_from
337 end
338
339 redef fun is_ok do return curr_pos >= target.index_from
340
341 redef fun item do return target_items[curr_pos]
342
343 redef fun next do curr_pos -= 1
344
345 redef fun index do return curr_pos - target.index_from
346
347 end
348
349 private class FlatStringIterator
350 super IndexedIterator[Char]
351
352 var target: FlatString
353
354 var target_items: NativeString
355
356 var curr_pos: Int
357
358 init with_pos(tgt: FlatString, pos: Int)
359 do
360 target = tgt
361 target_items = tgt.items
362 curr_pos = pos + target.index_from
363 end
364
365 redef fun is_ok do return curr_pos <= target.index_to
366
367 redef fun item do return target_items[curr_pos]
368
369 redef fun next do curr_pos += 1
370
371 redef fun index do return curr_pos - target.index_from
372
373 end
374
375 private class FlatStringCharView
376 super StringCharView
377
378 redef type SELFTYPE: FlatString
379
380 redef fun [](index)
381 do
382 # Check that the index (+ index_from) is not larger than indexTo
383 # In other terms, if the index is valid
384 assert index >= 0
385 var target = self.target
386 assert (index + target.index_from) <= target.index_to
387 return target.items[index + target.index_from]
388 end
389
390 redef fun iterator_from(start) do return new FlatStringIterator.with_pos(target, start)
391
392 redef fun reverse_iterator_from(start) do return new FlatStringReverseIterator.with_pos(target, start)
393
394 end
395
396 redef class Buffer
397 redef new do return new FlatBuffer
398
399 redef new with_cap(i) do return new FlatBuffer.with_capacity(i)
400 end
401
402 # Mutable strings of characters.
403 class FlatBuffer
404 super FlatText
405 super Buffer
406
407 redef var chars: Sequence[Char] = new FlatBufferCharView(self) is lazy
408
409 private var capacity: Int = 0
410
411 redef fun fast_cstring do return items.fast_cstring(0)
412
413 redef fun substrings do return new FlatSubstringsIter(self)
414
415 # Re-copies the `NativeString` into a new one and sets it as the new `Buffer`
416 #
417 # This happens when an operation modifies the current `Buffer` and
418 # the Copy-On-Write flag `written` is set at true.
419 private fun reset do
420 var nns = new NativeString(capacity)
421 items.copy_to(nns, length, 0, 0)
422 items = nns
423 written = false
424 end
425
426 redef fun [](index)
427 do
428 assert index >= 0
429 assert index < length
430 return items[index]
431 end
432
433 redef fun []=(index, item)
434 do
435 is_dirty = true
436 if index == length then
437 add(item)
438 return
439 end
440 if written then reset
441 assert index >= 0 and index < length
442 items[index] = item
443 end
444
445 redef fun add(c)
446 do
447 is_dirty = true
448 if capacity <= length then enlarge(length + 5)
449 items[length] = c
450 length += 1
451 end
452
453 redef fun clear do
454 is_dirty = true
455 if written then reset
456 length = 0
457 end
458
459 redef fun empty do return new Buffer
460
461 redef fun enlarge(cap)
462 do
463 var c = capacity
464 if cap <= c then return
465 while c <= cap do c = c * 2 + 2
466 # The COW flag can be set at false here, since
467 # it does a copy of the current `Buffer`
468 written = false
469 var a = new NativeString(c+1)
470 if length > 0 then items.copy_to(a, length, 0, 0)
471 items = a
472 capacity = c
473 end
474
475 redef fun to_s
476 do
477 written = true
478 if length == 0 then items = new NativeString(1)
479 return new FlatString.with_infos(items, length, 0, length - 1)
480 end
481
482 redef fun to_cstring
483 do
484 if is_dirty then
485 var new_native = new NativeString(length + 1)
486 new_native[length] = '\0'
487 if length > 0 then items.copy_to(new_native, length, 0, 0)
488 real_items = new_native
489 is_dirty = false
490 end
491 return real_items.as(not null)
492 end
493
494 # Create a new empty string.
495 init do end
496
497 # Low-level creation a new buffer with given data.
498 #
499 # `items` will be used as is, without copy, to store the characters of the buffer.
500 # Aliasing issues is the responsibility of the caller.
501 #
502 # If `items` is shared, `written` should be set to true after the creation
503 # so that a modification will do a copy-on-write.
504 private init with_infos(items: NativeString, capacity, length: Int)
505 do
506 self.items = items
507 self.length = length
508 self.capacity = capacity
509 end
510
511 # Create a new string copied from `s`.
512 init from(s: Text)
513 do
514 capacity = s.length + 1
515 length = s.length
516 items = new NativeString(capacity)
517 if s isa FlatString then
518 s.items.copy_to(items, length, s.index_from, 0)
519 else if s isa FlatBuffer then
520 s.items.copy_to(items, length, 0, 0)
521 else
522 var curr_pos = 0
523 for i in [0..s.length[ do
524 var c = s.chars[i]
525 items[curr_pos] = c
526 curr_pos += 1
527 end
528 end
529 end
530
531 # Create a new empty string with a given capacity.
532 init with_capacity(cap: Int)
533 do
534 assert cap >= 0
535 items = new NativeString(cap+1)
536 capacity = cap
537 length = 0
538 end
539
540 redef fun append(s)
541 do
542 if s.is_empty then return
543 is_dirty = true
544 var sl = s.length
545 if capacity < length + sl then enlarge(length + sl)
546 if s isa FlatString then
547 s.items.copy_to(items, sl, s.index_from, length)
548 else if s isa FlatBuffer then
549 s.items.copy_to(items, sl, 0, length)
550 else
551 var curr_pos = self.length
552 for i in [0..s.length[ do
553 var c = s.chars[i]
554 items[curr_pos] = c
555 curr_pos += 1
556 end
557 end
558 length += sl
559 end
560
561 # Copies the content of self in `dest`
562 fun copy(start: Int, len: Int, dest: Buffer, new_start: Int)
563 do
564 var self_chars = self.chars
565 var dest_chars = dest.chars
566 for i in [0..len-1] do
567 dest_chars[new_start+i] = self_chars[start+i]
568 end
569 end
570
571 redef fun substring(from, count)
572 do
573 assert count >= 0
574 count += from
575 if from < 0 then from = 0
576 if count > length then count = length
577 if from < count then
578 var len = count - from
579 var r_items = new NativeString(len)
580 items.copy_to(r_items, len, from, 0)
581 var r = new FlatBuffer.with_infos(r_items, len, len)
582 return r
583 else
584 return new Buffer
585 end
586 end
587
588 redef fun reverse
589 do
590 written = false
591 var ns = new NativeString(capacity)
592 var si = length - 1
593 var ni = 0
594 var it = items
595 while si >= 0 do
596 ns[ni] = it[si]
597 ni += 1
598 si -= 1
599 end
600 items = ns
601 end
602
603 redef fun times(repeats)
604 do
605 var x = new FlatString.with_infos(items, length, 0, length - 1)
606 for i in [1..repeats[ do
607 append(x)
608 end
609 end
610
611 redef fun upper
612 do
613 if written then reset
614 var it = items
615 var id = length - 1
616 while id >= 0 do
617 it[id] = it[id].to_upper
618 id -= 1
619 end
620 end
621
622 redef fun lower
623 do
624 if written then reset
625 var it = items
626 var id = length - 1
627 while id >= 0 do
628 it[id] = it[id].to_lower
629 id -= 1
630 end
631 end
632 end
633
634 private class FlatBufferReverseIterator
635 super IndexedIterator[Char]
636
637 var target: FlatBuffer
638
639 var target_items: NativeString
640
641 var curr_pos: Int
642
643 init with_pos(tgt: FlatBuffer, pos: Int)
644 do
645 target = tgt
646 if tgt.length > 0 then target_items = tgt.items
647 curr_pos = pos
648 end
649
650 redef fun index do return curr_pos
651
652 redef fun is_ok do return curr_pos >= 0
653
654 redef fun item do return target_items[curr_pos]
655
656 redef fun next do curr_pos -= 1
657
658 end
659
660 private class FlatBufferCharView
661 super BufferCharView
662
663 redef type SELFTYPE: FlatBuffer
664
665 redef fun [](index) do return target.items[index]
666
667 redef fun []=(index, item)
668 do
669 assert index >= 0 and index <= length
670 if index == length then
671 add(item)
672 return
673 end
674 target.items[index] = item
675 end
676
677 redef fun push(c)
678 do
679 target.add(c)
680 end
681
682 redef fun add(c)
683 do
684 target.add(c)
685 end
686
687 fun enlarge(cap: Int)
688 do
689 target.enlarge(cap)
690 end
691
692 redef fun append(s)
693 do
694 var s_length = s.length
695 if target.capacity < s.length then enlarge(s_length + target.length)
696 end
697
698 redef fun iterator_from(pos) do return new FlatBufferIterator.with_pos(target, pos)
699
700 redef fun reverse_iterator_from(pos) do return new FlatBufferReverseIterator.with_pos(target, pos)
701
702 end
703
704 private class FlatBufferIterator
705 super IndexedIterator[Char]
706
707 var target: FlatBuffer
708
709 var target_items: NativeString
710
711 var curr_pos: Int
712
713 init with_pos(tgt: FlatBuffer, pos: Int)
714 do
715 target = tgt
716 if tgt.length > 0 then target_items = tgt.items
717 curr_pos = pos
718 end
719
720 redef fun index do return curr_pos
721
722 redef fun is_ok do return curr_pos < target.length
723
724 redef fun item do return target_items[curr_pos]
725
726 redef fun next do curr_pos += 1
727
728 end
729
730 redef class NativeString
731 redef fun to_s
732 do
733 return to_s_with_length(cstring_length)
734 end
735
736 # Returns `self` as a String of `length`.
737 redef fun to_s_with_length(length): FlatString
738 do
739 assert length >= 0
740 var str = new FlatString.with_infos(self, length, 0, length - 1)
741 return str
742 end
743
744 # Returns `self` as a new String.
745 redef fun to_s_with_copy: FlatString
746 do
747 var length = cstring_length
748 var new_self = new NativeString(length + 1)
749 copy_to(new_self, length, 0, 0)
750 var str = new FlatString.with_infos(new_self, length, 0, length - 1)
751 new_self[length] = '\0'
752 str.real_items = new_self
753 return str
754 end
755 end
756
757 redef class Int
758 redef fun to_base(base, signed)
759 do
760 var l = digit_count(base)
761 var s = new FlatBuffer.from(" " * l)
762 fill_buffer(s, base, signed)
763 return s.to_s
764 end
765
766 # return displayable int in base 10 and signed
767 #
768 # assert 1.to_s == "1"
769 # assert (-123).to_s == "-123"
770 redef fun to_s do
771 # Fast case for common numbers
772 if self == 0 then return "0"
773 if self == 1 then return "1"
774
775 var nslen = int_to_s_len
776 var ns = new NativeString(nslen + 1)
777 ns[nslen] = '\0'
778 native_int_to_s(ns, nslen + 1)
779 return ns.to_s_with_length(nslen)
780 end
781 end
782
783 redef class Array[E]
784
785 # Fast implementation
786 redef fun plain_to_s
787 do
788 var l = length
789 if l == 0 then return ""
790 if l == 1 then if self[0] == null then return "" else return self[0].to_s
791 var its = _items
792 var na = new NativeArray[String](l)
793 var i = 0
794 var sl = 0
795 var mypos = 0
796 while i < l do
797 var itsi = its[i]
798 if itsi == null then
799 i += 1
800 continue
801 end
802 var tmp = itsi.to_s
803 sl += tmp.length
804 na[mypos] = tmp
805 i += 1
806 mypos += 1
807 end
808 var ns = new NativeString(sl + 1)
809 ns[sl] = '\0'
810 i = 0
811 var off = 0
812 while i < mypos do
813 var tmp = na[i]
814 var tpl = tmp.length
815 if tmp isa FlatString then
816 tmp.items.copy_to(ns, tpl, tmp.index_from, off)
817 off += tpl
818 else
819 for j in tmp.substrings do
820 var s = j.as(FlatString)
821 var slen = s.length
822 s.items.copy_to(ns, slen, s.index_from, off)
823 off += slen
824 end
825 end
826 i += 1
827 end
828 return ns.to_s_with_length(sl)
829 end
830 end
831
832 redef class NativeArray[E]
833 redef fun native_to_s do
834 assert self isa NativeArray[String]
835 var l = length
836 var na = self
837 var i = 0
838 var sl = 0
839 var mypos = 0
840 while i < l do
841 sl += na[i].length
842 i += 1
843 mypos += 1
844 end
845 var ns = new NativeString(sl + 1)
846 ns[sl] = '\0'
847 i = 0
848 var off = 0
849 while i < mypos do
850 var tmp = na[i]
851 var tpl = tmp.length
852 if tmp isa FlatString then
853 tmp.items.copy_to(ns, tpl, tmp.index_from, off)
854 off += tpl
855 else
856 for j in tmp.substrings do
857 var s = j.as(FlatString)
858 var slen = s.length
859 s.items.copy_to(ns, slen, s.index_from, off)
860 off += slen
861 end
862 end
863 i += 1
864 end
865 return ns.to_s_with_length(sl)
866 end
867 end
868
869 redef class Map[K,V]
870 redef fun join(sep, couple_sep)
871 do
872 if is_empty then return ""
873
874 var s = new Buffer # Result
875
876 # Concat first item
877 var i = iterator
878 var k = i.key
879 var e = i.item
880 s.append("{k or else "<null>"}{couple_sep}{e or else "<null>"}")
881
882 # Concat other items
883 i.next
884 while i.is_ok do
885 s.append(sep)
886 k = i.key
887 e = i.item
888 s.append("{k or else "<null>"}{couple_sep}{e or else "<null>"}")
889 i.next
890 end
891 return s.to_s
892 end
893 end