lib/string: named constructors call the autoinit explicitly
[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 var bytes = new FlatStringByteView(self) is lazy
50
51 redef fun [](index)
52 do
53 # Check that the index (+ index_from) is not larger than indexTo
54 # In other terms, if the index is valid
55 assert index >= 0
56 assert (index + index_from) <= index_to
57 return items[index + index_from].to_i.ascii
58 end
59
60 ################################################
61 # AbstractString specific methods #
62 ################################################
63
64 redef fun reversed
65 do
66 var native = new NativeString(self.length + 1)
67 var length = self.length
68 var items = self.items
69 var pos = 0
70 var ipos = length-1
71 while pos < length do
72 native[pos] = items[ipos]
73 pos += 1
74 ipos -= 1
75 end
76 return native.to_s_with_length(self.length)
77 end
78
79 redef fun fast_cstring do return items.fast_cstring(index_from)
80
81 redef fun substring(from, count)
82 do
83 assert count >= 0
84
85 if from < 0 then
86 count += from
87 if count < 0 then count = 0
88 from = 0
89 end
90
91 var new_from = index_from + from
92
93 if (new_from + count) > index_to then
94 var new_len = index_to - new_from + 1
95 if new_len <= 0 then return empty
96 return new FlatString.with_infos(items, new_len, new_from, index_to)
97 end
98
99 if count <= 0 then return empty
100
101 var to = new_from + count - 1
102
103 return new FlatString.with_infos(items, to - new_from + 1, new_from, to)
104 end
105
106 redef fun empty do return "".as(FlatString)
107
108 redef fun to_upper
109 do
110 var outstr = new FlatBuffer.with_capacity(self.bytelen + 1)
111
112 var mylen = length
113 var pos = 0
114
115 while pos < mylen do
116 outstr.add(chars[pos].to_upper)
117 pos += 1
118 end
119
120 return outstr.to_s
121 end
122
123 redef fun to_lower
124 do
125 var outstr = new FlatBuffer.with_capacity(self.bytelen + 1)
126
127 var mylen = length
128 var pos = 0
129
130 while pos < mylen do
131 outstr.add(chars[pos].to_lower)
132 pos += 1
133 end
134
135 return outstr.to_s
136 end
137
138 redef fun output
139 do
140 for i in chars do i.output
141 end
142
143 ##################################################
144 # String Specific Methods #
145 ##################################################
146
147 # Low-level creation of a new string with given data.
148 #
149 # `items` will be used as is, without copy, to retrieve the characters of the string.
150 # Aliasing issues is the responsibility of the caller.
151 private init with_infos(items: NativeString, length: Int, from: Int, to: Int)
152 do
153 self.items = items
154 self.length = length
155 index_from = from
156 index_to = to
157 end
158
159 redef fun to_cstring
160 do
161 if real_items != null then
162 return real_items.as(not null)
163 else
164 var newItems = new NativeString(length + 1)
165 self.items.copy_to(newItems, length, index_from, 0)
166 newItems[length] = 0u8
167 self.real_items = newItems
168 return newItems
169 end
170 end
171
172 redef fun ==(other)
173 do
174 if not other isa FlatString then return super
175
176 if self.object_id == other.object_id then return true
177
178 var my_length = length
179
180 if other.length != my_length then return false
181
182 var my_index = index_from
183 var its_index = other.index_from
184
185 var last_iteration = my_index + my_length
186
187 var itsitems = other.items
188 var myitems = self.items
189
190 while my_index < last_iteration do
191 if myitems[my_index] != itsitems[its_index] then return false
192 my_index += 1
193 its_index += 1
194 end
195
196 return true
197 end
198
199 redef fun <(other)
200 do
201 if not other isa FlatString then return super
202
203 if self.object_id == other.object_id then return false
204
205 var my_curr_char : Char
206 var its_curr_char : Char
207
208 var my_length = self.length
209 var its_length = other.length
210 var max
211
212 if my_length < its_length then
213 max = my_length
214 else
215 max = its_length
216 end
217
218 var my_chars = chars
219 var its_chars = other.chars
220
221 var pos = 0
222 while pos < max do
223 my_curr_char = my_chars[pos]
224 its_curr_char = its_chars[pos]
225
226 if my_curr_char != its_curr_char then
227 if my_curr_char < its_curr_char then return true
228 return false
229 end
230
231 pos += 1
232 end
233
234 return my_length < its_length
235 end
236
237 redef fun +(s)
238 do
239 var my_length = self.length
240 var its_length = s.length
241
242 var total_length = my_length + its_length
243
244 var target_string = new NativeString(my_length + its_length + 1)
245
246 self.items.copy_to(target_string, my_length, index_from, 0)
247 if s isa FlatString then
248 s.items.copy_to(target_string, its_length, s.index_from, my_length)
249 else if s isa FlatBuffer then
250 s.items.copy_to(target_string, its_length, 0, my_length)
251 else
252 var curr_pos = my_length
253 for i in [0 .. s.bytelen[ do
254 target_string[curr_pos] = s.bytes[i]
255 curr_pos += 1
256 end
257 end
258
259 target_string[total_length] = 0u8
260
261 return target_string.to_s_with_length(total_length)
262 end
263
264 redef fun *(i)
265 do
266 assert i >= 0
267
268 var my_length = self.length
269
270 var final_length = my_length * i
271
272 var my_items = self.items
273
274 var target_string = new NativeString(final_length + 1)
275
276 target_string[final_length] = 0u8
277
278 var current_last = 0
279
280 for iteration in [1 .. i] do
281 my_items.copy_to(target_string, my_length, 0, current_last)
282 current_last += my_length
283 end
284
285 return target_string.to_s_with_length(final_length)
286 end
287
288 redef fun hash
289 do
290 if hash_cache == null then
291 # djb2 hash algorithm
292 var h = 5381
293 var i = index_from
294
295 var myitems = items
296
297 while i <= index_to do
298 h = h.lshift(5) + h + myitems[i].to_i
299 i += 1
300 end
301
302 hash_cache = h
303 end
304
305 return hash_cache.as(not null)
306 end
307
308 redef fun substrings do return new FlatSubstringsIter(self)
309 end
310
311 private class FlatStringCharReverseIterator
312 super IndexedIterator[Char]
313
314 var target: FlatString
315
316 var curr_pos: Int
317
318 init with_pos(tgt: FlatString, pos: Int)
319 do
320 init(tgt, pos)
321 end
322
323 redef fun is_ok do return curr_pos >= 0
324
325 redef fun item do return target[curr_pos]
326
327 redef fun next do curr_pos -= 1
328
329 redef fun index do return curr_pos
330
331 end
332
333 private class FlatStringCharIterator
334 super IndexedIterator[Char]
335
336 var target: FlatString
337
338 var max: Int
339
340 var curr_pos: Int
341
342 init with_pos(tgt: FlatString, pos: Int)
343 do
344 init(tgt, tgt.length - 1, pos)
345 end
346
347 redef fun is_ok do return curr_pos <= max
348
349 redef fun item do return target[curr_pos]
350
351 redef fun next do curr_pos += 1
352
353 redef fun index do return curr_pos
354
355 end
356
357 private class FlatStringCharView
358 super StringCharView
359
360 redef type SELFTYPE: FlatString
361
362 redef fun [](index) do return target[index]
363
364 redef fun iterator_from(start) do return new FlatStringCharIterator.with_pos(target, start)
365
366 redef fun reverse_iterator_from(start) do return new FlatStringCharReverseIterator.with_pos(target, start)
367
368 end
369
370 private class FlatStringByteReverseIterator
371 super IndexedIterator[Byte]
372
373 var target: FlatString
374
375 var target_items: NativeString
376
377 var curr_pos: Int
378
379 init with_pos(tgt: FlatString, pos: Int)
380 do
381 init(tgt, tgt.items, pos + tgt.index_from)
382 end
383
384 redef fun is_ok do return curr_pos >= target.index_from
385
386 redef fun item do return target_items[curr_pos]
387
388 redef fun next do curr_pos -= 1
389
390 redef fun index do return curr_pos - target.index_from
391
392 end
393
394 private class FlatStringByteIterator
395 super IndexedIterator[Byte]
396
397 var target: FlatString
398
399 var target_items: NativeString
400
401 var curr_pos: Int
402
403 init with_pos(tgt: FlatString, pos: Int)
404 do
405 init(tgt, tgt.items, pos + tgt.index_from)
406 end
407
408 redef fun is_ok do return curr_pos <= target.index_to
409
410 redef fun item do return target_items[curr_pos]
411
412 redef fun next do curr_pos += 1
413
414 redef fun index do return curr_pos - target.index_from
415
416 end
417
418 private class FlatStringByteView
419 super StringByteView
420
421 redef type SELFTYPE: FlatString
422
423 redef fun [](index)
424 do
425 # Check that the index (+ index_from) is not larger than indexTo
426 # In other terms, if the index is valid
427 assert index >= 0
428 var target = self.target
429 assert (index + target.index_from) <= target.index_to
430 return target.items[index + target.index_from]
431 end
432
433 redef fun iterator_from(start) do return new FlatStringByteIterator.with_pos(target, start)
434
435 redef fun reverse_iterator_from(start) do return new FlatStringByteReverseIterator.with_pos(target, start)
436
437 end
438
439 redef class Buffer
440 redef new do return new FlatBuffer
441
442 redef new with_cap(i) do return new FlatBuffer.with_capacity(i)
443 end
444
445 # Mutable strings of characters.
446 class FlatBuffer
447 super FlatText
448 super Buffer
449
450 redef var chars: Sequence[Char] = new FlatBufferCharView(self) is lazy
451
452 redef var bytes: Sequence[Byte] = new FlatBufferByteView(self) is lazy
453
454 private var capacity: Int = 0
455
456 redef fun fast_cstring do return items.fast_cstring(0)
457
458 redef fun substrings do return new FlatSubstringsIter(self)
459
460 # Re-copies the `NativeString` into a new one and sets it as the new `Buffer`
461 #
462 # This happens when an operation modifies the current `Buffer` and
463 # the Copy-On-Write flag `written` is set at true.
464 private fun reset do
465 var nns = new NativeString(capacity)
466 items.copy_to(nns, length, 0, 0)
467 items = nns
468 written = false
469 end
470
471 redef fun [](index)
472 do
473 assert index >= 0
474 assert index < length
475 return items[index].to_i.ascii
476 end
477
478 redef fun []=(index, item)
479 do
480 is_dirty = true
481 if index == length then
482 add(item)
483 return
484 end
485 if written then reset
486 assert index >= 0 and index < length
487 items[index] = item.ascii.to_b
488 end
489
490 redef fun add(c)
491 do
492 is_dirty = true
493 if capacity <= length then enlarge(length + 5)
494 items[length] = c.ascii.to_b
495 length += 1
496 end
497
498 private fun add_byte(b: Byte) do
499 is_dirty = true
500 if capacity <= length then enlarge(length + 5)
501 items[bytelen] = b
502 length += 1
503 end
504
505 redef fun clear do
506 is_dirty = true
507 if written then reset
508 length = 0
509 end
510
511 redef fun empty do return new Buffer
512
513 redef fun enlarge(cap)
514 do
515 var c = capacity
516 if cap <= c then return
517 while c <= cap do c = c * 2 + 2
518 # The COW flag can be set at false here, since
519 # it does a copy of the current `Buffer`
520 written = false
521 var a = new NativeString(c+1)
522 if length > 0 then items.copy_to(a, length, 0, 0)
523 items = a
524 capacity = c
525 end
526
527 redef fun to_s
528 do
529 written = true
530 if length == 0 then items = new NativeString(1)
531 return new FlatString.with_infos(items, length, 0, length - 1)
532 end
533
534 redef fun to_cstring
535 do
536 if is_dirty then
537 var new_native = new NativeString(length + 1)
538 new_native[length] = 0u8
539 if length > 0 then items.copy_to(new_native, length, 0, 0)
540 real_items = new_native
541 is_dirty = false
542 end
543 return real_items.as(not null)
544 end
545
546 # Create a new empty string.
547 init do end
548
549 # Low-level creation a new buffer with given data.
550 #
551 # `items` will be used as is, without copy, to store the characters of the buffer.
552 # Aliasing issues is the responsibility of the caller.
553 #
554 # If `items` is shared, `written` should be set to true after the creation
555 # so that a modification will do a copy-on-write.
556 private init with_infos(items: NativeString, capacity, length: Int)
557 do
558 self.items = items
559 self.length = length
560 self.capacity = capacity
561 end
562
563 # Create a new string copied from `s`.
564 init from(s: Text)
565 do
566 capacity = s.length + 1
567 length = s.length
568 items = new NativeString(capacity)
569 if s isa FlatString then
570 s.items.copy_to(items, length, s.index_from, 0)
571 else if s isa FlatBuffer then
572 s.items.copy_to(items, length, 0, 0)
573 else
574 var curr_pos = 0
575 for i in s.bytes do
576 items[curr_pos] = i
577 curr_pos += 1
578 end
579 end
580 end
581
582 # Create a new empty string with a given capacity.
583 init with_capacity(cap: Int)
584 do
585 assert cap >= 0
586 items = new NativeString(cap+1)
587 capacity = cap
588 length = 0
589 end
590
591 redef fun append(s)
592 do
593 if s.is_empty then return
594 is_dirty = true
595 var sl = s.length
596 if capacity < length + sl then enlarge(length + sl)
597 if s isa FlatString then
598 s.items.copy_to(items, sl, s.index_from, length)
599 else if s isa FlatBuffer then
600 s.items.copy_to(items, sl, 0, length)
601 else
602 var curr_pos = self.length
603 for i in s.bytes do
604 items[curr_pos] = i
605 curr_pos += 1
606 end
607 end
608 length += sl
609 end
610
611 # Copies the content of self in `dest`
612 fun copy(start: Int, len: Int, dest: Buffer, new_start: Int)
613 do
614 var self_chars = self.chars
615 var dest_chars = dest.chars
616 for i in [0..len-1] do
617 dest_chars[new_start+i] = self_chars[start+i]
618 end
619 end
620
621 redef fun substring(from, count)
622 do
623 assert count >= 0
624 count += from
625 if from < 0 then from = 0
626 if count > length then count = length
627 if from < count then
628 var len = count - from
629 var r_items = new NativeString(len)
630 items.copy_to(r_items, len, from, 0)
631 var r = new FlatBuffer.with_infos(r_items, len, len)
632 return r
633 else
634 return new Buffer
635 end
636 end
637
638 redef fun reverse
639 do
640 written = false
641 var ns = new NativeString(capacity)
642 var si = length - 1
643 var ni = 0
644 var it = items
645 while si >= 0 do
646 ns[ni] = it[si]
647 ni += 1
648 si -= 1
649 end
650 items = ns
651 end
652
653 redef fun times(repeats)
654 do
655 var x = new FlatString.with_infos(items, length, 0, length - 1)
656 for i in [1..repeats[ do
657 append(x)
658 end
659 end
660
661 redef fun upper
662 do
663 if written then reset
664 var id = length - 1
665 while id >= 0 do
666 self[id] = self[id].to_upper
667 id -= 1
668 end
669 end
670
671 redef fun lower
672 do
673 if written then reset
674 var id = length - 1
675 while id >= 0 do
676 self[id] = self[id].to_lower
677 id -= 1
678 end
679 end
680 end
681
682 private class FlatBufferByteReverseIterator
683 super IndexedIterator[Byte]
684
685 var target: FlatBuffer
686
687 var target_items: NativeString
688
689 var curr_pos: Int
690
691 init with_pos(tgt: FlatBuffer, pos: Int)
692 do
693 init(tgt, tgt.items, pos)
694 end
695
696 redef fun index do return curr_pos
697
698 redef fun is_ok do return curr_pos >= 0
699
700 redef fun item do return target_items[curr_pos]
701
702 redef fun next do curr_pos -= 1
703
704 end
705
706 private class FlatBufferByteView
707 super BufferByteView
708
709 redef type SELFTYPE: FlatBuffer
710
711 redef fun [](index) do return target.items[index]
712
713 redef fun []=(index, item)
714 do
715 assert index >= 0 and index <= target.bytelen
716 if index == target.bytelen then
717 add(item)
718 return
719 end
720 target.items[index] = item
721 end
722
723 redef fun push(c)
724 do
725 target.add_byte(c)
726 end
727
728 fun enlarge(cap: Int)
729 do
730 target.enlarge(cap)
731 end
732
733 redef fun append(s)
734 do
735 var s_length = s.length
736 if target.capacity < (target.length + s_length) then enlarge(s_length + target.length)
737 var pos = target.length
738 var its = target.items
739 for i in s do
740 its[pos] = i
741 pos += 1
742 end
743 target.length += s.length
744 end
745
746 redef fun iterator_from(pos) do return new FlatBufferByteIterator.with_pos(target, pos)
747
748 redef fun reverse_iterator_from(pos) do return new FlatBufferByteReverseIterator.with_pos(target, pos)
749
750 end
751
752 private class FlatBufferByteIterator
753 super IndexedIterator[Byte]
754
755 var target: FlatBuffer
756
757 var target_items: NativeString
758
759 var curr_pos: Int
760
761 init with_pos(tgt: FlatBuffer, pos: Int)
762 do
763 init(tgt, tgt.items, pos)
764 end
765
766 redef fun index do return curr_pos
767
768 redef fun is_ok do return curr_pos < target.length
769
770 redef fun item do return target_items[curr_pos]
771
772 redef fun next do curr_pos += 1
773
774 end
775
776 private class FlatBufferCharReverseIterator
777 super IndexedIterator[Char]
778
779 var target: FlatBuffer
780
781 var curr_pos: Int
782
783 init with_pos(tgt: FlatBuffer, pos: Int)
784 do
785 init(tgt, pos)
786 end
787
788 redef fun index do return curr_pos
789
790 redef fun is_ok do return curr_pos >= 0
791
792 redef fun item do return target[curr_pos]
793
794 redef fun next do curr_pos -= 1
795
796 end
797
798 private class FlatBufferCharView
799 super BufferCharView
800
801 redef type SELFTYPE: FlatBuffer
802
803 redef fun [](index) do return target[index]
804
805 redef fun []=(index, item)
806 do
807 assert index >= 0 and index <= length
808 if index == length then
809 add(item)
810 return
811 end
812 target[index] = item
813 end
814
815 redef fun push(c)
816 do
817 target.add(c)
818 end
819
820 redef fun add(c)
821 do
822 target.add(c)
823 end
824
825 fun enlarge(cap: Int)
826 do
827 target.enlarge(cap)
828 end
829
830 redef fun append(s)
831 do
832 var s_length = s.length
833 if target.capacity < s.length then enlarge(s_length + target.length)
834 for i in s do target.add i
835 end
836
837 redef fun iterator_from(pos) do return new FlatBufferCharIterator.with_pos(target, pos)
838
839 redef fun reverse_iterator_from(pos) do return new FlatBufferCharReverseIterator.with_pos(target, pos)
840
841 end
842
843 private class FlatBufferCharIterator
844 super IndexedIterator[Char]
845
846 var target: FlatBuffer
847
848 var max: Int
849
850 var curr_pos: Int
851
852 init with_pos(tgt: FlatBuffer, pos: Int)
853 do
854 init(tgt, tgt.length - 1, pos)
855 end
856
857 redef fun index do return curr_pos
858
859 redef fun is_ok do return curr_pos <= max
860
861 redef fun item do return target[curr_pos]
862
863 redef fun next do curr_pos += 1
864
865 end
866
867 redef class NativeString
868 redef fun to_s
869 do
870 return to_s_with_length(cstring_length)
871 end
872
873 # Returns `self` as a String of `length`.
874 redef fun to_s_with_length(length): FlatString
875 do
876 assert length >= 0
877 var str = new FlatString.with_infos(self, length, 0, length - 1)
878 return str
879 end
880
881 # Returns `self` as a new String.
882 redef fun to_s_with_copy: FlatString
883 do
884 var length = cstring_length
885 var new_self = new NativeString(length + 1)
886 copy_to(new_self, length, 0, 0)
887 var str = new FlatString.with_infos(new_self, length, 0, length - 1)
888 new_self[length] = 0u8
889 str.real_items = new_self
890 return str
891 end
892 end
893
894 redef class Int
895 redef fun to_base(base, signed)
896 do
897 var l = digit_count(base)
898 var s = new FlatBuffer.from(" " * l)
899 fill_buffer(s, base, signed)
900 return s.to_s
901 end
902
903 # return displayable int in base 10 and signed
904 #
905 # assert 1.to_s == "1"
906 # assert (-123).to_s == "-123"
907 redef fun to_s do
908 # Fast case for common numbers
909 if self == 0 then return "0"
910 if self == 1 then return "1"
911
912 var nslen = int_to_s_len
913 var ns = new NativeString(nslen + 1)
914 ns[nslen] = 0u8
915 native_int_to_s(ns, nslen + 1)
916 return ns.to_s_with_length(nslen)
917 end
918 end
919
920 redef class Array[E]
921
922 # Fast implementation
923 redef fun plain_to_s
924 do
925 var l = length
926 if l == 0 then return ""
927 if l == 1 then if self[0] == null then return "" else return self[0].to_s
928 var its = _items
929 var na = new NativeArray[String](l)
930 var i = 0
931 var sl = 0
932 var mypos = 0
933 while i < l do
934 var itsi = its[i]
935 if itsi == null then
936 i += 1
937 continue
938 end
939 var tmp = itsi.to_s
940 sl += tmp.length
941 na[mypos] = tmp
942 i += 1
943 mypos += 1
944 end
945 var ns = new NativeString(sl + 1)
946 ns[sl] = 0u8
947 i = 0
948 var off = 0
949 while i < mypos do
950 var tmp = na[i]
951 var tpl = tmp.length
952 if tmp isa FlatString then
953 tmp.items.copy_to(ns, tpl, tmp.index_from, off)
954 off += tpl
955 else
956 for j in tmp.substrings do
957 var s = j.as(FlatString)
958 var slen = s.length
959 s.items.copy_to(ns, slen, s.index_from, off)
960 off += slen
961 end
962 end
963 i += 1
964 end
965 return ns.to_s_with_length(sl)
966 end
967 end
968
969 redef class NativeArray[E]
970 redef fun native_to_s do
971 assert self isa NativeArray[String]
972 var l = length
973 var na = self
974 var i = 0
975 var sl = 0
976 var mypos = 0
977 while i < l do
978 sl += na[i].length
979 i += 1
980 mypos += 1
981 end
982 var ns = new NativeString(sl + 1)
983 ns[sl] = 0u8
984 i = 0
985 var off = 0
986 while i < mypos do
987 var tmp = na[i]
988 var tpl = tmp.length
989 if tmp isa FlatString then
990 tmp.items.copy_to(ns, tpl, tmp.index_from, off)
991 off += tpl
992 else
993 for j in tmp.substrings do
994 var s = j.as(FlatString)
995 var slen = s.length
996 s.items.copy_to(ns, slen, s.index_from, off)
997 off += slen
998 end
999 end
1000 i += 1
1001 end
1002 return ns.to_s_with_length(sl)
1003 end
1004 end
1005
1006 redef class Map[K,V]
1007 redef fun join(sep, couple_sep)
1008 do
1009 if is_empty then return ""
1010
1011 var s = new Buffer # Result
1012
1013 # Concat first item
1014 var i = iterator
1015 var k = i.key
1016 var e = i.item
1017 s.append("{k or else "<null>"}{couple_sep}{e or else "<null>"}")
1018
1019 # Concat other items
1020 i.next
1021 while i.is_ok do
1022 s.append(sep)
1023 k = i.key
1024 e = i.item
1025 s.append("{k or else "<null>"}{couple_sep}{e or else "<null>"}")
1026 i.next
1027 end
1028 return s.to_s
1029 end
1030 end