Merge: Model uml
[nit.git] / lib / standard / string.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2004-2008 Jean Privat <jean@pryen.org>
4 # Copyright 2006-2008 Floréal Morandat <morandat@lirmm.fr>
5 #
6 # This file is free software, which comes along with NIT. This software is
7 # distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
8 # without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
9 # PARTICULAR PURPOSE. You can modify it is you want, provided this header
10 # is kept unaltered, and a notification of the changes is added.
11 # You are allowed to redistribute it and sell it, alone or is a part of
12 # another product.
13
14 # Basic manipulations of strings of characters
15 module string
16
17 import math
18 import collection
19 intrude import collection::array
20
21 `{
22 #include <stdio.h>
23 #include <string.h>
24 `}
25
26 ###############################################################################
27 # String #
28 ###############################################################################
29
30 # High-level abstraction for all text representations
31 abstract class Text
32 super Comparable
33 super StringCapable
34
35 redef type OTHER: Text
36
37 # Type of self (used for factorization of several methods, ex : substring_from, empty...)
38 type SELFTYPE: Text
39
40 # Gets a view on the chars of the Text object
41 #
42 # assert "hello".chars.to_a == ['h', 'e', 'l', 'l', 'o']
43 fun chars: SequenceRead[Char] is abstract
44
45 # Number of characters contained in self.
46 #
47 # assert "12345".length == 5
48 # assert "".length == 0
49 fun length: Int is abstract
50
51 # Create a substring.
52 #
53 # assert "abcd".substring(1, 2) == "bc"
54 # assert "abcd".substring(-1, 2) == "a"
55 # assert "abcd".substring(1, 0) == ""
56 # assert "abcd".substring(2, 5) == "cd"
57 #
58 # A `from` index < 0 will be replaced by 0.
59 # Unless a `count` value is > 0 at the same time.
60 # In this case, `from += count` and `count -= from`.
61 fun substring(from: Int, count: Int): SELFTYPE is abstract
62
63 # Iterates on the substrings of self if any
64 fun substrings: Iterator[Text] is abstract
65
66 # Is the current Text empty (== "")
67 #
68 # assert "".is_empty
69 # assert not "foo".is_empty
70 fun is_empty: Bool do return self.length == 0
71
72 # Returns an empty Text of the right type
73 #
74 # This method is used internally to get the right
75 # implementation of an empty string.
76 protected fun empty: SELFTYPE is abstract
77
78 # Gets the first char of the Text
79 #
80 # DEPRECATED : Use self.chars.first instead
81 fun first: Char do return self.chars[0]
82
83 # Access a character at `index` in the string.
84 #
85 # assert "abcd"[2] == 'c'
86 #
87 # DEPRECATED : Use self.chars.[] instead
88 fun [](index: Int): Char do return self.chars[index]
89
90 # Gets the index of the first occurence of 'c'
91 #
92 # Returns -1 if not found
93 #
94 # DEPRECATED : Use self.chars.index_of instead
95 fun index_of(c: Char): Int
96 do
97 return index_of_from(c, 0)
98 end
99
100 # Gets the last char of self
101 #
102 # DEPRECATED : Use self.chars.last instead
103 fun last: Char do return self.chars[length-1]
104
105 # Gets the index of the first occurence of ´c´ starting from ´pos´
106 #
107 # Returns -1 if not found
108 #
109 # DEPRECATED : Use self.chars.index_of_from instead
110 fun index_of_from(c: Char, pos: Int): Int
111 do
112 var iter = self.chars.iterator_from(pos)
113 while iter.is_ok do
114 if iter.item == c then return iter.index
115 iter.next
116 end
117 return -1
118 end
119
120 # Gets the last index of char ´c´
121 #
122 # Returns -1 if not found
123 #
124 # DEPRECATED : Use self.chars.last_index_of instead
125 fun last_index_of(c: Char): Int
126 do
127 return last_index_of_from(c, length - 1)
128 end
129
130 # Return a null terminated char *
131 fun to_cstring: NativeString do return flatten.to_cstring
132
133 # The index of the last occurrence of an element starting from pos (in reverse order).
134 #
135 # var s = "/etc/bin/test/test.nit"
136 # assert s.last_index_of_from('/', s.length-1) == 13
137 # assert s.last_index_of_from('/', 12) == 8
138 #
139 # Returns -1 if not found
140 #
141 # DEPRECATED : Use self.chars.last_index_of_from instead
142 fun last_index_of_from(item: Char, pos: Int): Int
143 do
144 var iter = self.chars.reverse_iterator_from(pos)
145 while iter.is_ok do
146 if iter.item == item then return iter.index
147 iter.next
148 end
149 return -1
150 end
151
152 # Gets an iterator on the chars of self
153 #
154 # DEPRECATED : Use self.chars.iterator instead
155 fun iterator: Iterator[Char]
156 do
157 return self.chars.iterator
158 end
159
160
161 # Gets an Array containing the chars of self
162 #
163 # DEPRECATED : Use self.chars.to_a instead
164 fun to_a: Array[Char] do return chars.to_a
165
166 # Create a substring from `self` beginning at the `from` position
167 #
168 # assert "abcd".substring_from(1) == "bcd"
169 # assert "abcd".substring_from(-1) == "abcd"
170 # assert "abcd".substring_from(2) == "cd"
171 #
172 # As with substring, a `from` index < 0 will be replaced by 0
173 fun substring_from(from: Int): SELFTYPE
174 do
175 if from >= self.length then return empty
176 if from < 0 then from = 0
177 return substring(from, length - from)
178 end
179
180 # Does self have a substring `str` starting from position `pos`?
181 #
182 # assert "abcd".has_substring("bc",1) == true
183 # assert "abcd".has_substring("bc",2) == false
184 fun has_substring(str: String, pos: Int): Bool
185 do
186 var myiter = self.chars.iterator_from(pos)
187 var itsiter = str.chars.iterator
188 while myiter.is_ok and itsiter.is_ok do
189 if myiter.item != itsiter.item then return false
190 myiter.next
191 itsiter.next
192 end
193 if itsiter.is_ok then return false
194 return true
195 end
196
197 # Is this string prefixed by `prefix`?
198 #
199 # assert "abcd".has_prefix("ab") == true
200 # assert "abcbc".has_prefix("bc") == false
201 # assert "ab".has_prefix("abcd") == false
202 fun has_prefix(prefix: String): Bool do return has_substring(prefix,0)
203
204 # Is this string suffixed by `suffix`?
205 #
206 # assert "abcd".has_suffix("abc") == false
207 # assert "abcd".has_suffix("bcd") == true
208 fun has_suffix(suffix: String): Bool do return has_substring(suffix, length - suffix.length)
209
210 # If `self` contains only digits, return the corresponding integer
211 #
212 # assert "123".to_i == 123
213 # assert "-1".to_i == -1
214 fun to_i: Int
215 do
216 # Shortcut
217 return to_s.to_cstring.atoi
218 end
219
220 # If `self` contains a float, return the corresponding float
221 #
222 # assert "123".to_f == 123.0
223 # assert "-1".to_f == -1.0
224 # assert "-1.2e-3".to_f == -0.0012
225 fun to_f: Float
226 do
227 # Shortcut
228 return to_s.to_cstring.atof
229 end
230
231 # If `self` contains only digits and alpha <= 'f', return the corresponding integer.
232 #
233 # assert "ff".to_hex == 255
234 fun to_hex: Int do return a_to(16)
235
236 # If `self` contains only digits and letters, return the corresponding integer in a given base
237 #
238 # assert "120".a_to(3) == 15
239 fun a_to(base: Int) : Int
240 do
241 var i = 0
242 var neg = false
243
244 for j in [0..length[ do
245 var c = chars[j]
246 var v = c.to_i
247 if v > base then
248 if neg then
249 return -i
250 else
251 return i
252 end
253 else if v < 0 then
254 neg = true
255 else
256 i = i * base + v
257 end
258 end
259 if neg then
260 return -i
261 else
262 return i
263 end
264 end
265
266 # Returns `true` if the string contains only Numeric values (and one "," or one "." character)
267 #
268 # assert "123".is_numeric == true
269 # assert "1.2".is_numeric == true
270 # assert "1,2".is_numeric == true
271 # assert "1..2".is_numeric == false
272 fun is_numeric: Bool
273 do
274 var has_point_or_comma = false
275 for i in [0..length[ do
276 var c = chars[i]
277 if not c.is_numeric then
278 if (c == '.' or c == ',') and not has_point_or_comma then
279 has_point_or_comma = true
280 else
281 return false
282 end
283 end
284 end
285 return true
286 end
287
288 # Returns `true` if the string contains only Hex chars
289 #
290 # assert "048bf".is_hex == true
291 # assert "ABCDEF".is_hex == true
292 # assert "0G".is_hex == false
293 fun is_hex: Bool
294 do
295 for i in [0..length[ do
296 var c = chars[i]
297 if not (c >= 'a' and c <= 'f') and
298 not (c >= 'A' and c <= 'F') and
299 not (c >= '0' and c <= '9') then return false
300 end
301 return true
302 end
303
304 # Are all letters in `self` upper-case ?
305 #
306 # assert "HELLO WORLD".is_upper == true
307 # assert "%$&%!".is_upper == true
308 # assert "hello world".is_upper == false
309 # assert "Hello World".is_upper == false
310 fun is_upper: Bool
311 do
312 for i in [0..length[ do
313 var char = chars[i]
314 if char.is_lower then return false
315 end
316 return true
317 end
318
319 # Are all letters in `self` lower-case ?
320 #
321 # assert "hello world".is_lower == true
322 # assert "%$&%!".is_lower == true
323 # assert "Hello World".is_lower == false
324 fun is_lower: Bool
325 do
326 for i in [0..length[ do
327 var char = chars[i]
328 if char.is_upper then return false
329 end
330 return true
331 end
332
333 # Removes the whitespaces at the beginning of self
334 #
335 # assert " \n\thello \n\t".l_trim == "hello \n\t"
336 #
337 # A whitespace is defined as any character which ascii value is less than or equal to 32
338 fun l_trim: SELFTYPE
339 do
340 var iter = self.chars.iterator
341 while iter.is_ok do
342 if iter.item.ascii > 32 then break
343 iter.next
344 end
345 if iter.index == length then return self.empty
346 return self.substring_from(iter.index)
347 end
348
349 # Removes the whitespaces at the end of self
350 #
351 # assert " \n\thello \n\t".r_trim == " \n\thello"
352 #
353 # A whitespace is defined as any character which ascii value is less than or equal to 32
354 fun r_trim: SELFTYPE
355 do
356 var iter = self.chars.reverse_iterator
357 while iter.is_ok do
358 if iter.item.ascii > 32 then break
359 iter.next
360 end
361 if iter.index < 0 then return self.empty
362 return self.substring(0, iter.index + 1)
363 end
364
365 # Trims trailing and preceding white spaces
366 # A whitespace is defined as any character which ascii value is less than or equal to 32
367 #
368 # assert " Hello World ! ".trim == "Hello World !"
369 # assert "\na\nb\tc\t".trim == "a\nb\tc"
370 fun trim: SELFTYPE do return (self.l_trim).r_trim
371
372 # Mangle a string to be a unique string only made of alphanumeric characters
373 fun to_cmangle: String
374 do
375 var res = new FlatBuffer
376 var underscore = false
377 for i in [0..length[ do
378 var c = chars[i]
379 if (c >= 'a' and c <= 'z') or (c >='A' and c <= 'Z') then
380 res.add(c)
381 underscore = false
382 continue
383 end
384 if underscore then
385 res.append('_'.ascii.to_s)
386 res.add('d')
387 end
388 if c >= '0' and c <= '9' then
389 res.add(c)
390 underscore = false
391 else if c == '_' then
392 res.add(c)
393 underscore = true
394 else
395 res.add('_')
396 res.append(c.ascii.to_s)
397 res.add('d')
398 underscore = false
399 end
400 end
401 return res.to_s
402 end
403
404 # Escape " \ ' and non printable characters using the rules of literal C strings and characters
405 #
406 # assert "abAB12<>&".escape_to_c == "abAB12<>&"
407 # assert "\n\"'\\".escape_to_c == "\\n\\\"\\'\\\\"
408 fun escape_to_c: String
409 do
410 var b = new FlatBuffer
411 for i in [0..length[ do
412 var c = chars[i]
413 if c == '\n' then
414 b.append("\\n")
415 else if c == '\0' then
416 b.append("\\0")
417 else if c == '"' then
418 b.append("\\\"")
419 else if c == '\'' then
420 b.append("\\\'")
421 else if c == '\\' then
422 b.append("\\\\")
423 else if c.ascii < 32 then
424 b.append("\\{c.ascii.to_base(8, false)}")
425 else
426 b.add(c)
427 end
428 end
429 return b.to_s
430 end
431
432 # Escape additionnal characters
433 # The result might no be legal in C but be used in other languages
434 #
435 # assert "ab|\{\}".escape_more_to_c("|\{\}") == "ab\\|\\\{\\\}"
436 fun escape_more_to_c(chars: String): String
437 do
438 var b = new FlatBuffer
439 for c in escape_to_c.chars do
440 if chars.chars.has(c) then
441 b.add('\\')
442 end
443 b.add(c)
444 end
445 return b.to_s
446 end
447
448 # Escape to C plus braces
449 #
450 # assert "\n\"'\\\{\}".escape_to_nit == "\\n\\\"\\'\\\\\\\{\\\}"
451 fun escape_to_nit: String do return escape_more_to_c("\{\}")
452
453 # Return a string where Nit escape sequences are transformed.
454 #
455 # var s = "\\n"
456 # assert s.length == 2
457 # var u = s.unescape_nit
458 # assert u.length == 1
459 # assert u.chars[0].ascii == 10 # (the ASCII value of the "new line" character)
460 fun unescape_nit: String
461 do
462 var res = new FlatBuffer.with_capacity(self.length)
463 var was_slash = false
464 for i in [0..length[ do
465 var c = chars[i]
466 if not was_slash then
467 if c == '\\' then
468 was_slash = true
469 else
470 res.add(c)
471 end
472 continue
473 end
474 was_slash = false
475 if c == 'n' then
476 res.add('\n')
477 else if c == 'r' then
478 res.add('\r')
479 else if c == 't' then
480 res.add('\t')
481 else if c == '0' then
482 res.add('\0')
483 else
484 res.add(c)
485 end
486 end
487 return res.to_s
488 end
489
490 # Encode `self` to percent (or URL) encoding
491 #
492 # assert "aBc09-._~".to_percent_encoding == "aBc09-._~"
493 # assert "%()< >".to_percent_encoding == "%25%28%29%3c%20%3e"
494 # assert ".com/post?e=asdf&f=123".to_percent_encoding == ".com%2fpost%3fe%3dasdf%26f%3d123"
495 fun to_percent_encoding: String
496 do
497 var buf = new FlatBuffer
498
499 for i in [0..length[ do
500 var c = chars[i]
501 if (c >= '0' and c <= '9') or
502 (c >= 'a' and c <= 'z') or
503 (c >= 'A' and c <= 'Z') or
504 c == '-' or c == '.' or
505 c == '_' or c == '~'
506 then
507 buf.add c
508 else buf.append "%{c.ascii.to_hex}"
509 end
510
511 return buf.to_s
512 end
513
514 # Decode `self` from percent (or URL) encoding to a clear string
515 #
516 # Replace invalid use of '%' with '?'.
517 #
518 # assert "aBc09-._~".from_percent_encoding == "aBc09-._~"
519 # assert "%25%28%29%3c%20%3e".from_percent_encoding == "%()< >"
520 # assert ".com%2fpost%3fe%3dasdf%26f%3d123".from_percent_encoding == ".com/post?e=asdf&f=123"
521 # assert "%25%28%29%3C%20%3E".from_percent_encoding == "%()< >"
522 # assert "incomplete %".from_percent_encoding == "incomplete ?"
523 # assert "invalid % usage".from_percent_encoding == "invalid ? usage"
524 fun from_percent_encoding: String
525 do
526 var buf = new FlatBuffer
527
528 var i = 0
529 while i < length do
530 var c = chars[i]
531 if c == '%' then
532 if i + 2 >= length then
533 # What follows % has been cut off
534 buf.add '?'
535 else
536 i += 1
537 var hex_s = substring(i, 2)
538 if hex_s.is_hex then
539 var hex_i = hex_s.to_hex
540 buf.add hex_i.ascii
541 i += 1
542 else
543 # What follows a % is not Hex
544 buf.add '?'
545 i -= 1
546 end
547 end
548 else buf.add c
549
550 i += 1
551 end
552
553 return buf.to_s
554 end
555
556 # Escape the four characters `<`, `>`, `&`, and `"` with their html counterpart
557 #
558 # assert "a&b->\"x\"".html_escape == "a&amp;b-&gt;&quot;x&quot;"
559 fun html_escape: SELFTYPE
560 do
561 var buf = new FlatBuffer
562
563 for i in [0..length[ do
564 var c = chars[i]
565 if c == '&' then
566 buf.append "&amp;"
567 else if c == '<' then
568 buf.append "&lt;"
569 else if c == '>' then
570 buf.append "&gt;"
571 else if c == '"' then
572 buf.append "&quot;"
573 else buf.add c
574 end
575
576 return buf.to_s
577 end
578
579 # Equality of text
580 # Two pieces of text are equals if thez have the same characters in the same order.
581 #
582 # assert "hello" == "hello"
583 # assert "hello" != "HELLO"
584 # assert "hello" == "hel"+"lo"
585 #
586 # Things that are not Text are not equal.
587 #
588 # assert "9" != '9'
589 # assert "9" != ['9']
590 # assert "9" != 9
591 #
592 # assert "9".chars.first == '9' # equality of Char
593 # assert "9".chars == ['9'] # equality of Sequence
594 # assert "9".to_i == 9 # equality of Int
595 redef fun ==(o)
596 do
597 if o == null then return false
598 if not o isa Text then return false
599 if self.is_same_instance(o) then return true
600 if self.length != o.length then return false
601 return self.chars == o.chars
602 end
603
604 # Lexicographical comparaison
605 #
606 # assert "abc" < "xy"
607 # assert "ABC" < "abc"
608 redef fun <(other)
609 do
610 var self_chars = self.chars.iterator
611 var other_chars = other.chars.iterator
612
613 while self_chars.is_ok and other_chars.is_ok do
614 if self_chars.item < other_chars.item then return true
615 if self_chars.item > other_chars.item then return false
616 self_chars.next
617 other_chars.next
618 end
619
620 if self_chars.is_ok then
621 return false
622 else
623 return true
624 end
625 end
626
627 # Escape string used in labels for graphviz
628 #
629 # assert ">><<".escape_to_dot == "\\>\\>\\<\\<"
630 fun escape_to_dot: String
631 do
632 return escape_more_to_c("|\{\}<>")
633 end
634
635 # Flat representation of self
636 fun flatten: FlatText is abstract
637
638 private var hash_cache: nullable Int = null
639
640 redef fun hash
641 do
642 if hash_cache == null then
643 # djb2 hash algorithm
644 var h = 5381
645
646 for i in [0..length[ do
647 var char = chars[i]
648 h = h.lshift(5) + h + char.ascii
649 end
650
651 hash_cache = h
652 end
653 return hash_cache.as(not null)
654 end
655
656 end
657
658 # All kinds of array-based text representations.
659 abstract class FlatText
660 super Text
661
662 # Underlying C-String (`char*`)
663 #
664 # Warning : Might be void in some subclasses, be sure to check
665 # if set before using it.
666 private var items: NativeString
667
668 # Real items, used as cache for to_cstring is called
669 private var real_items: nullable NativeString = null
670
671 redef var length: Int = 0
672
673 init do end
674
675 redef fun output
676 do
677 var i = 0
678 while i < length do
679 items[i].output
680 i += 1
681 end
682 end
683
684 redef fun flatten do return self
685 end
686
687 # Abstract class for the SequenceRead compatible
688 # views on String and Buffer objects
689 private abstract class StringCharView
690 super SequenceRead[Char]
691
692 type SELFTYPE: Text
693
694 private var target: SELFTYPE
695
696 private init(tgt: SELFTYPE)
697 do
698 target = tgt
699 end
700
701 redef fun is_empty do return target.is_empty
702
703 redef fun length do return target.length
704
705 redef fun iterator: IndexedIterator[Char] do return self.iterator_from(0)
706
707 redef fun reverse_iterator do return self.reverse_iterator_from(self.length - 1)
708 end
709
710 # View on Buffer objects, extends Sequence
711 # for mutation operations
712 private abstract class BufferCharView
713 super StringCharView
714 super Sequence[Char]
715
716 redef type SELFTYPE: Buffer
717
718 end
719
720 abstract class String
721 super Text
722
723 redef type SELFTYPE: String
724
725 redef fun to_s do return self
726
727 # Concatenates `o` to `self`
728 #
729 # assert "hello" + "world" == "helloworld"
730 # assert "" + "hello" + "" == "hello"
731 fun +(o: Text): SELFTYPE is abstract
732
733 # Concatenates self `i` times
734 #
735 # assert "abc" * 4 == "abcabcabcabc"
736 # assert "abc" * 1 == "abc"
737 # assert "abc" * 0 == ""
738 fun *(i: Int): SELFTYPE is abstract
739
740 fun insert_at(s: String, pos: Int): SELFTYPE is abstract
741
742 # Returns a reversed version of self
743 #
744 # assert "hello".reversed == "olleh"
745 # assert "bob".reversed == "bob"
746 # assert "".reversed == ""
747 fun reversed: SELFTYPE is abstract
748
749 # A upper case version of `self`
750 #
751 # assert "Hello World!".to_upper == "HELLO WORLD!"
752 fun to_upper: SELFTYPE is abstract
753
754 # A lower case version of `self`
755 #
756 # assert "Hello World!".to_lower == "hello world!"
757 fun to_lower : SELFTYPE is abstract
758
759 # Takes a camel case `self` and converts it to snake case
760 #
761 # assert "randomMethodId".to_snake_case == "random_method_id"
762 #
763 # If `self` is upper, it is returned unchanged
764 #
765 # assert "RANDOM_METHOD_ID".to_snake_case == "RANDOM_METHOD_ID"
766 #
767 # If the identifier is prefixed by an underscore, the underscore is ignored
768 #
769 # assert "_privateField".to_snake_case == "_private_field"
770 fun to_snake_case: SELFTYPE
771 do
772 if self.is_upper then return self
773
774 var new_str = new FlatBuffer.with_capacity(self.length)
775 var is_first_char = true
776
777 for i in [0..length[ do
778 var char = chars[i]
779 if is_first_char then
780 new_str.add(char.to_lower)
781 is_first_char = false
782 else if char.is_upper then
783 new_str.add('_')
784 new_str.add(char.to_lower)
785 else
786 new_str.add(char)
787 end
788 end
789
790 return new_str.to_s
791 end
792
793 # Takes a snake case `self` and converts it to camel case
794 #
795 # assert "random_method_id".to_camel_case == "randomMethodId"
796 #
797 # If the identifier is prefixed by an underscore, the underscore is ignored
798 #
799 # assert "_private_field".to_camel_case == "_privateField"
800 #
801 # If `self` is upper, it is returned unchanged
802 #
803 # assert "RANDOM_ID".to_camel_case == "RANDOM_ID"
804 #
805 # If there are several consecutive underscores, they are considered as a single one
806 #
807 # assert "random__method_id".to_camel_case == "randomMethodId"
808 fun to_camel_case: SELFTYPE
809 do
810 if self.is_upper then return self
811
812 var new_str = new FlatBuffer
813 var is_first_char = true
814 var follows_us = false
815
816 for i in [0..length[ do
817 var char = chars[i]
818 if is_first_char then
819 new_str.add(char)
820 is_first_char = false
821 else if char == '_' then
822 follows_us = true
823 else if follows_us then
824 new_str.add(char.to_upper)
825 follows_us = false
826 else
827 new_str.add(char)
828 end
829 end
830
831 return new_str.to_s
832 end
833
834 # Returns a capitalized `self`
835 #
836 # Letters that follow a letter are lowercased
837 # Letters that follow a non-letter are upcased.
838 #
839 # SEE : `Char::is_letter` for the definition of letter.
840 #
841 # assert "jAVASCRIPT".capitalized == "Javascript"
842 # assert "i am root".capitalized == "I Am Root"
843 # assert "ab_c -ab0c ab\nc".capitalized == "Ab_C -Ab0C Ab\nC"
844 fun capitalized: SELFTYPE do
845 if length == 0 then return self
846
847 var buf = new FlatBuffer.with_capacity(length)
848
849 var curr = chars[0].to_upper
850 var prev = curr
851 buf[0] = curr
852
853 for i in [1 .. length[ do
854 prev = curr
855 curr = self[i]
856 if prev.is_letter then
857 buf[i] = curr.to_lower
858 else
859 buf[i] = curr.to_upper
860 end
861 end
862
863 return buf.to_s
864 end
865 end
866
867 private class FlatSubstringsIter
868 super Iterator[FlatText]
869
870 var tgt: nullable FlatText
871
872 init(tgt: FlatText) do self.tgt = tgt
873
874 redef fun item do
875 assert is_ok
876 return tgt.as(not null)
877 end
878
879 redef fun is_ok do return tgt != null
880
881 redef fun next do tgt = null
882 end
883
884 # Immutable strings of characters.
885 class FlatString
886 super FlatText
887 super String
888
889 # Index in _items of the start of the string
890 private var index_from: Int
891
892 # Indes in _items of the last item of the string
893 private var index_to: Int
894
895 redef var chars: SequenceRead[Char] = new FlatStringCharView(self)
896
897 redef fun [](index)
898 do
899 # Check that the index (+ index_from) is not larger than indexTo
900 # In other terms, if the index is valid
901 assert index >= 0
902 assert (index + index_from) <= index_to
903 return items[index + index_from]
904 end
905
906 ################################################
907 # AbstractString specific methods #
908 ################################################
909
910 redef fun reversed
911 do
912 var native = calloc_string(self.length + 1)
913 var length = self.length
914 var items = self.items
915 var pos = 0
916 var ipos = length-1
917 while pos < length do
918 native[pos] = items[ipos]
919 pos += 1
920 ipos -= 1
921 end
922 return native.to_s_with_length(self.length)
923 end
924
925 redef fun substring(from, count)
926 do
927 assert count >= 0
928
929 if from < 0 then
930 count += from
931 if count < 0 then count = 0
932 from = 0
933 end
934
935 var realFrom = index_from + from
936
937 if (realFrom + count) > index_to then return new FlatString.with_infos(items, index_to - realFrom + 1, realFrom, index_to)
938
939 if count == 0 then return empty
940
941 var to = realFrom + count - 1
942
943 return new FlatString.with_infos(items, to - realFrom + 1, realFrom, to)
944 end
945
946 redef fun empty do return "".as(FlatString)
947
948 redef fun to_upper
949 do
950 var outstr = calloc_string(self.length + 1)
951 var out_index = 0
952
953 var myitems = self.items
954 var index_from = self.index_from
955 var max = self.index_to
956
957 while index_from <= max do
958 outstr[out_index] = myitems[index_from].to_upper
959 out_index += 1
960 index_from += 1
961 end
962
963 outstr[self.length] = '\0'
964
965 return outstr.to_s_with_length(self.length)
966 end
967
968 redef fun to_lower
969 do
970 var outstr = calloc_string(self.length + 1)
971 var out_index = 0
972
973 var myitems = self.items
974 var index_from = self.index_from
975 var max = self.index_to
976
977 while index_from <= max do
978 outstr[out_index] = myitems[index_from].to_lower
979 out_index += 1
980 index_from += 1
981 end
982
983 outstr[self.length] = '\0'
984
985 return outstr.to_s_with_length(self.length)
986 end
987
988 redef fun output
989 do
990 var i = self.index_from
991 var imax = self.index_to
992 while i <= imax do
993 items[i].output
994 i += 1
995 end
996 end
997
998 ##################################################
999 # String Specific Methods #
1000 ##################################################
1001
1002 private init with_infos(items: NativeString, len: Int, from: Int, to: Int)
1003 do
1004 self.items = items
1005 length = len
1006 index_from = from
1007 index_to = to
1008 end
1009
1010 redef fun to_cstring: NativeString
1011 do
1012 if real_items != null then
1013 return real_items.as(not null)
1014 else
1015 var newItems = calloc_string(length + 1)
1016 self.items.copy_to(newItems, length, index_from, 0)
1017 newItems[length] = '\0'
1018 self.real_items = newItems
1019 return newItems
1020 end
1021 end
1022
1023 redef fun ==(other)
1024 do
1025 if not other isa FlatString then return super
1026
1027 if self.object_id == other.object_id then return true
1028
1029 var my_length = length
1030
1031 if other.length != my_length then return false
1032
1033 var my_index = index_from
1034 var its_index = other.index_from
1035
1036 var last_iteration = my_index + my_length
1037
1038 var itsitems = other.items
1039 var myitems = self.items
1040
1041 while my_index < last_iteration do
1042 if myitems[my_index] != itsitems[its_index] then return false
1043 my_index += 1
1044 its_index += 1
1045 end
1046
1047 return true
1048 end
1049
1050 redef fun <(other)
1051 do
1052 if not other isa FlatString then return super
1053
1054 if self.object_id == other.object_id then return false
1055
1056 var my_curr_char : Char
1057 var its_curr_char : Char
1058
1059 var curr_id_self = self.index_from
1060 var curr_id_other = other.index_from
1061
1062 var my_items = self.items
1063 var its_items = other.items
1064
1065 var my_length = self.length
1066 var its_length = other.length
1067
1068 var max_iterations = curr_id_self + my_length
1069
1070 while curr_id_self < max_iterations do
1071 my_curr_char = my_items[curr_id_self]
1072 its_curr_char = its_items[curr_id_other]
1073
1074 if my_curr_char != its_curr_char then
1075 if my_curr_char < its_curr_char then return true
1076 return false
1077 end
1078
1079 curr_id_self += 1
1080 curr_id_other += 1
1081 end
1082
1083 return my_length < its_length
1084 end
1085
1086 redef fun +(s)
1087 do
1088 var my_length = self.length
1089 var its_length = s.length
1090
1091 var total_length = my_length + its_length
1092
1093 var target_string = calloc_string(my_length + its_length + 1)
1094
1095 self.items.copy_to(target_string, my_length, index_from, 0)
1096 if s isa FlatString then
1097 s.items.copy_to(target_string, its_length, s.index_from, my_length)
1098 else if s isa FlatBuffer then
1099 s.items.copy_to(target_string, its_length, 0, my_length)
1100 else
1101 var curr_pos = my_length
1102 for i in [0..s.length[ do
1103 var c = s.chars[i]
1104 target_string[curr_pos] = c
1105 curr_pos += 1
1106 end
1107 end
1108
1109 target_string[total_length] = '\0'
1110
1111 return target_string.to_s_with_length(total_length)
1112 end
1113
1114 redef fun *(i)
1115 do
1116 assert i >= 0
1117
1118 var my_length = self.length
1119
1120 var final_length = my_length * i
1121
1122 var my_items = self.items
1123
1124 var target_string = calloc_string((final_length) + 1)
1125
1126 target_string[final_length] = '\0'
1127
1128 var current_last = 0
1129
1130 for iteration in [1 .. i] do
1131 my_items.copy_to(target_string, my_length, 0, current_last)
1132 current_last += my_length
1133 end
1134
1135 return target_string.to_s_with_length(final_length)
1136 end
1137
1138 redef fun hash
1139 do
1140 if hash_cache == null then
1141 # djb2 hash algorithm
1142 var h = 5381
1143 var i = index_from
1144
1145 var myitems = items
1146
1147 while i <= index_to do
1148 h = h.lshift(5) + h + myitems[i].ascii
1149 i += 1
1150 end
1151
1152 hash_cache = h
1153 end
1154
1155 return hash_cache.as(not null)
1156 end
1157
1158 redef fun substrings do return new FlatSubstringsIter(self)
1159 end
1160
1161 private class FlatStringReverseIterator
1162 super IndexedIterator[Char]
1163
1164 var target: FlatString
1165
1166 var target_items: NativeString
1167
1168 var curr_pos: Int
1169
1170 init with_pos(tgt: FlatString, pos: Int)
1171 do
1172 target = tgt
1173 target_items = tgt.items
1174 curr_pos = pos + tgt.index_from
1175 end
1176
1177 redef fun is_ok do return curr_pos >= target.index_from
1178
1179 redef fun item do return target_items[curr_pos]
1180
1181 redef fun next do curr_pos -= 1
1182
1183 redef fun index do return curr_pos - target.index_from
1184
1185 end
1186
1187 private class FlatStringIterator
1188 super IndexedIterator[Char]
1189
1190 var target: FlatString
1191
1192 var target_items: NativeString
1193
1194 var curr_pos: Int
1195
1196 init with_pos(tgt: FlatString, pos: Int)
1197 do
1198 target = tgt
1199 target_items = tgt.items
1200 curr_pos = pos + target.index_from
1201 end
1202
1203 redef fun is_ok do return curr_pos <= target.index_to
1204
1205 redef fun item do return target_items[curr_pos]
1206
1207 redef fun next do curr_pos += 1
1208
1209 redef fun index do return curr_pos - target.index_from
1210
1211 end
1212
1213 private class FlatStringCharView
1214 super StringCharView
1215
1216 redef type SELFTYPE: FlatString
1217
1218 redef fun [](index)
1219 do
1220 # Check that the index (+ index_from) is not larger than indexTo
1221 # In other terms, if the index is valid
1222 assert index >= 0
1223 var target = self.target
1224 assert (index + target.index_from) <= target.index_to
1225 return target.items[index + target.index_from]
1226 end
1227
1228 redef fun iterator_from(start) do return new FlatStringIterator.with_pos(target, start)
1229
1230 redef fun reverse_iterator_from(start) do return new FlatStringReverseIterator.with_pos(target, start)
1231
1232 end
1233
1234 abstract class Buffer
1235 super Text
1236
1237 redef type SELFTYPE: Buffer
1238
1239 # Specific implementations MUST set this to `true` in order to invalidate caches
1240 protected var is_dirty = true
1241
1242 # Copy-On-Write flag
1243 #
1244 # If the `Buffer` was to_s'd, the next in-place altering
1245 # operation will cause the current `Buffer` to be re-allocated.
1246 #
1247 # The flag will then be set at `false`.
1248 protected var written = false
1249
1250 # Modifies the char contained at pos `index`
1251 #
1252 # DEPRECATED : Use self.chars.[]= instead
1253 fun []=(index: Int, item: Char) is abstract
1254
1255 # Adds a char `c` at the end of self
1256 #
1257 # DEPRECATED : Use self.chars.add instead
1258 fun add(c: Char) is abstract
1259
1260 # Clears the buffer
1261 #
1262 # var b = new FlatBuffer
1263 # b.append "hello"
1264 # assert not b.is_empty
1265 # b.clear
1266 # assert b.is_empty
1267 fun clear is abstract
1268
1269 # Enlarges the subsequent array containing the chars of self
1270 fun enlarge(cap: Int) is abstract
1271
1272 # Adds the content of text `s` at the end of self
1273 #
1274 # var b = new FlatBuffer
1275 # b.append "hello"
1276 # b.append "world"
1277 # assert b == "helloworld"
1278 fun append(s: Text) is abstract
1279
1280 # `self` is appended in such a way that `self` is repeated `r` times
1281 #
1282 # var b = new FlatBuffer
1283 # b.append "hello"
1284 # b.times 3
1285 # assert b == "hellohellohello"
1286 fun times(r: Int) is abstract
1287
1288 # Reverses itself in-place
1289 #
1290 # var b = new FlatBuffer
1291 # b.append("hello")
1292 # b.reverse
1293 # assert b == "olleh"
1294 fun reverse is abstract
1295
1296 # Changes each lower-case char in `self` by its upper-case variant
1297 #
1298 # var b = new FlatBuffer
1299 # b.append("Hello World!")
1300 # b.upper
1301 # assert b == "HELLO WORLD!"
1302 fun upper is abstract
1303
1304 # Changes each upper-case char in `self` by its lower-case variant
1305 #
1306 # var b = new FlatBuffer
1307 # b.append("Hello World!")
1308 # b.lower
1309 # assert b == "hello world!"
1310 fun lower is abstract
1311
1312 # Capitalizes each word in `self`
1313 #
1314 # Letters that follow a letter are lowercased
1315 # Letters that follow a non-letter are upcased.
1316 #
1317 # SEE: `Char::is_letter` for the definition of a letter.
1318 #
1319 # var b = new FlatBuffer.from("jAVAsCriPt")"
1320 # b.capitalize
1321 # assert b == "Javascript"
1322 # b = new FlatBuffer.from("i am root")
1323 # b.capitalize
1324 # assert b == "I Am Root"
1325 # b = new FlatBuffer.from("ab_c -ab0c ab\nc")
1326 # b.capitalize
1327 # assert b == "Ab_C -Ab0C Ab\nC"
1328 fun capitalize do
1329 if length == 0 then return
1330 var c = self[0].to_upper
1331 self[0] = c
1332 var prev: Char = c
1333 for i in [1 .. length[ do
1334 prev = c
1335 c = self[i]
1336 if prev.is_letter then
1337 self[i] = c.to_lower
1338 else
1339 self[i] = c.to_upper
1340 end
1341 end
1342 end
1343
1344 redef fun hash
1345 do
1346 if is_dirty then hash_cache = null
1347 return super
1348 end
1349
1350 # In Buffers, the internal sequence of character is mutable
1351 # Thus, `chars` can be used to modify the buffer.
1352 redef fun chars: Sequence[Char] is abstract
1353 end
1354
1355 # Mutable strings of characters.
1356 class FlatBuffer
1357 super FlatText
1358 super Buffer
1359
1360 redef type SELFTYPE: FlatBuffer
1361
1362 redef var chars: Sequence[Char] = new FlatBufferCharView(self)
1363
1364 private var capacity: Int = 0
1365
1366 redef fun substrings do return new FlatSubstringsIter(self)
1367
1368 # Re-copies the `NativeString` into a new one and sets it as the new `Buffer`
1369 #
1370 # This happens when an operation modifies the current `Buffer` and
1371 # the Copy-On-Write flag `written` is set at true.
1372 private fun reset do
1373 var nns = new NativeString(capacity)
1374 items.copy_to(nns, length, 0, 0)
1375 items = nns
1376 written = false
1377 end
1378
1379 redef fun [](index)
1380 do
1381 assert index >= 0
1382 assert index < length
1383 return items[index]
1384 end
1385
1386 redef fun []=(index, item)
1387 do
1388 is_dirty = true
1389 if index == length then
1390 add(item)
1391 return
1392 end
1393 if written then reset
1394 assert index >= 0 and index < length
1395 items[index] = item
1396 end
1397
1398 redef fun add(c)
1399 do
1400 is_dirty = true
1401 if capacity <= length then enlarge(length + 5)
1402 items[length] = c
1403 length += 1
1404 end
1405
1406 redef fun clear do
1407 is_dirty = true
1408 if written then reset
1409 length = 0
1410 end
1411
1412 redef fun empty do return new FlatBuffer
1413
1414 redef fun enlarge(cap)
1415 do
1416 var c = capacity
1417 if cap <= c then return
1418 while c <= cap do c = c * 2 + 2
1419 # The COW flag can be set at false here, since
1420 # it does a copy of the current `Buffer`
1421 written = false
1422 var a = calloc_string(c+1)
1423 if length > 0 then items.copy_to(a, length, 0, 0)
1424 items = a
1425 capacity = c
1426 end
1427
1428 redef fun to_s: String
1429 do
1430 written = true
1431 if length == 0 then items = new NativeString(1)
1432 return new FlatString.with_infos(items, length, 0, length - 1)
1433 end
1434
1435 redef fun to_cstring
1436 do
1437 if is_dirty then
1438 var new_native = calloc_string(length + 1)
1439 new_native[length] = '\0'
1440 if length > 0 then items.copy_to(new_native, length, 0, 0)
1441 real_items = new_native
1442 is_dirty = false
1443 end
1444 return real_items.as(not null)
1445 end
1446
1447 # Create a new empty string.
1448 init do end
1449
1450 init from(s: Text)
1451 do
1452 capacity = s.length + 1
1453 length = s.length
1454 items = calloc_string(capacity)
1455 if s isa FlatString then
1456 s.items.copy_to(items, length, s.index_from, 0)
1457 else if s isa FlatBuffer then
1458 s.items.copy_to(items, length, 0, 0)
1459 else
1460 var curr_pos = 0
1461 for i in [0..s.length[ do
1462 var c = s.chars[i]
1463 items[curr_pos] = c
1464 curr_pos += 1
1465 end
1466 end
1467 end
1468
1469 # Create a new empty string with a given capacity.
1470 init with_capacity(cap: Int)
1471 do
1472 assert cap >= 0
1473 # _items = new NativeString.calloc(cap)
1474 items = calloc_string(cap+1)
1475 capacity = cap
1476 length = 0
1477 end
1478
1479 redef fun append(s)
1480 do
1481 if s.is_empty then return
1482 is_dirty = true
1483 var sl = s.length
1484 if capacity < length + sl then enlarge(length + sl)
1485 if s isa FlatString then
1486 s.items.copy_to(items, sl, s.index_from, length)
1487 else if s isa FlatBuffer then
1488 s.items.copy_to(items, sl, 0, length)
1489 else
1490 var curr_pos = self.length
1491 for i in [0..s.length[ do
1492 var c = s.chars[i]
1493 items[curr_pos] = c
1494 curr_pos += 1
1495 end
1496 end
1497 length += sl
1498 end
1499
1500 # Copies the content of self in `dest`
1501 fun copy(start: Int, len: Int, dest: Buffer, new_start: Int)
1502 do
1503 var self_chars = self.chars
1504 var dest_chars = dest.chars
1505 for i in [0..len-1] do
1506 dest_chars[new_start+i] = self_chars[start+i]
1507 end
1508 end
1509
1510 redef fun substring(from, count)
1511 do
1512 assert count >= 0
1513 count += from
1514 if from < 0 then from = 0
1515 if count > length then count = length
1516 if from < count then
1517 var r = new FlatBuffer.with_capacity(count - from)
1518 while from < count do
1519 r.chars.push(items[from])
1520 from += 1
1521 end
1522 return r
1523 else
1524 return new FlatBuffer
1525 end
1526 end
1527
1528 redef fun reverse
1529 do
1530 written = false
1531 var ns = calloc_string(capacity)
1532 var si = length - 1
1533 var ni = 0
1534 var it = items
1535 while si >= 0 do
1536 ns[ni] = it[si]
1537 ni += 1
1538 si -= 1
1539 end
1540 items = ns
1541 end
1542
1543 redef fun times(repeats)
1544 do
1545 var x = new FlatString.with_infos(items, length, 0, length - 1)
1546 for i in [1..repeats[ do
1547 append(x)
1548 end
1549 end
1550
1551 redef fun upper
1552 do
1553 if written then reset
1554 var it = items
1555 var id = length - 1
1556 while id >= 0 do
1557 it[id] = it[id].to_upper
1558 id -= 1
1559 end
1560 end
1561
1562 redef fun lower
1563 do
1564 if written then reset
1565 var it = items
1566 var id = length - 1
1567 while id >= 0 do
1568 it[id] = it[id].to_lower
1569 id -= 1
1570 end
1571 end
1572 end
1573
1574 private class FlatBufferReverseIterator
1575 super IndexedIterator[Char]
1576
1577 var target: FlatBuffer
1578
1579 var target_items: NativeString
1580
1581 var curr_pos: Int
1582
1583 init with_pos(tgt: FlatBuffer, pos: Int)
1584 do
1585 target = tgt
1586 if tgt.length > 0 then target_items = tgt.items
1587 curr_pos = pos
1588 end
1589
1590 redef fun index do return curr_pos
1591
1592 redef fun is_ok do return curr_pos >= 0
1593
1594 redef fun item do return target_items[curr_pos]
1595
1596 redef fun next do curr_pos -= 1
1597
1598 end
1599
1600 private class FlatBufferCharView
1601 super BufferCharView
1602 super StringCapable
1603
1604 redef type SELFTYPE: FlatBuffer
1605
1606 redef fun [](index) do return target.items[index]
1607
1608 redef fun []=(index, item)
1609 do
1610 assert index >= 0 and index <= length
1611 if index == length then
1612 add(item)
1613 return
1614 end
1615 target.items[index] = item
1616 end
1617
1618 redef fun push(c)
1619 do
1620 target.add(c)
1621 end
1622
1623 redef fun add(c)
1624 do
1625 target.add(c)
1626 end
1627
1628 fun enlarge(cap: Int)
1629 do
1630 target.enlarge(cap)
1631 end
1632
1633 redef fun append(s)
1634 do
1635 var my_items = target.items
1636 var s_length = s.length
1637 if target.capacity < s.length then enlarge(s_length + target.length)
1638 end
1639
1640 redef fun iterator_from(pos) do return new FlatBufferIterator.with_pos(target, pos)
1641
1642 redef fun reverse_iterator_from(pos) do return new FlatBufferReverseIterator.with_pos(target, pos)
1643
1644 end
1645
1646 private class FlatBufferIterator
1647 super IndexedIterator[Char]
1648
1649 var target: FlatBuffer
1650
1651 var target_items: NativeString
1652
1653 var curr_pos: Int
1654
1655 init with_pos(tgt: FlatBuffer, pos: Int)
1656 do
1657 target = tgt
1658 if tgt.length > 0 then target_items = tgt.items
1659 curr_pos = pos
1660 end
1661
1662 redef fun index do return curr_pos
1663
1664 redef fun is_ok do return curr_pos < target.length
1665
1666 redef fun item do return target_items[curr_pos]
1667
1668 redef fun next do curr_pos += 1
1669
1670 end
1671
1672 ###############################################################################
1673 # Refinement #
1674 ###############################################################################
1675
1676 redef class Object
1677 # User readable representation of `self`.
1678 fun to_s: String do return inspect
1679
1680 # The class name of the object in NativeString format.
1681 private fun native_class_name: NativeString is intern
1682
1683 # The class name of the object.
1684 #
1685 # assert 5.class_name == "Int"
1686 fun class_name: String do return native_class_name.to_s
1687
1688 # Developer readable representation of `self`.
1689 # Usually, it uses the form "<CLASSNAME:#OBJECTID bla bla bla>"
1690 fun inspect: String
1691 do
1692 return "<{inspect_head}>"
1693 end
1694
1695 # Return "CLASSNAME:#OBJECTID".
1696 # This function is mainly used with the redefinition of the inspect method
1697 protected fun inspect_head: String
1698 do
1699 return "{class_name}:#{object_id.to_hex}"
1700 end
1701 end
1702
1703 redef class Bool
1704 # assert true.to_s == "true"
1705 # assert false.to_s == "false"
1706 redef fun to_s
1707 do
1708 if self then
1709 return once "true"
1710 else
1711 return once "false"
1712 end
1713 end
1714 end
1715
1716 redef class Int
1717
1718 # Wrapper of strerror C function
1719 private fun strerror_ext: NativeString is extern `{
1720 return strerror(recv);
1721 `}
1722
1723 # Returns a string describing error number
1724 fun strerror: String do return strerror_ext.to_s
1725
1726 # Fill `s` with the digits in base `base` of `self` (and with the '-' sign if 'signed' and negative).
1727 # assume < to_c max const of char
1728 private fun fill_buffer(s: Buffer, base: Int, signed: Bool)
1729 do
1730 var n: Int
1731 # Sign
1732 if self < 0 then
1733 n = - self
1734 s.chars[0] = '-'
1735 else if self == 0 then
1736 s.chars[0] = '0'
1737 return
1738 else
1739 n = self
1740 end
1741 # Fill digits
1742 var pos = digit_count(base) - 1
1743 while pos >= 0 and n > 0 do
1744 s.chars[pos] = (n % base).to_c
1745 n = n / base # /
1746 pos -= 1
1747 end
1748 end
1749
1750 # C function to convert an nit Int to a NativeString (char*)
1751 private fun native_int_to_s: NativeString is extern "native_int_to_s"
1752
1753 # return displayable int in base 10 and signed
1754 #
1755 # assert 1.to_s == "1"
1756 # assert (-123).to_s == "-123"
1757 redef fun to_s do
1758 return native_int_to_s.to_s
1759 end
1760
1761 # return displayable int in hexadecimal
1762 #
1763 # assert 1.to_hex == "1"
1764 # assert (-255).to_hex == "-ff"
1765 fun to_hex: String do return to_base(16,false)
1766
1767 # return displayable int in base base and signed
1768 fun to_base(base: Int, signed: Bool): String
1769 do
1770 var l = digit_count(base)
1771 var s = new FlatBuffer.from(" " * l)
1772 fill_buffer(s, base, signed)
1773 return s.to_s
1774 end
1775 end
1776
1777 redef class Float
1778 # Pretty print self, print needoed decimals up to a max of 3.
1779 #
1780 # assert 12.34.to_s == "12.34"
1781 # assert (-0120.03450).to_s == "-120.035"
1782 #
1783 # see `to_precision` for a different precision.
1784 redef fun to_s do
1785 var str = to_precision( 3 )
1786 if is_inf != 0 or is_nan then return str
1787 var len = str.length
1788 for i in [0..len-1] do
1789 var j = len-1-i
1790 var c = str.chars[j]
1791 if c == '0' then
1792 continue
1793 else if c == '.' then
1794 return str.substring( 0, j+2 )
1795 else
1796 return str.substring( 0, j+1 )
1797 end
1798 end
1799 return str
1800 end
1801
1802 # `self` representation with `nb` digits after the '.'.
1803 #
1804 # assert 12.345.to_precision(1) == "12.3"
1805 # assert 12.345.to_precision(2) == "12.35"
1806 # assert 12.345.to_precision(3) == "12.345"
1807 # assert 12.345.to_precision(4) == "12.3450"
1808 fun to_precision(nb: Int): String
1809 do
1810 if is_nan then return "nan"
1811
1812 var isinf = self.is_inf
1813 if isinf == 1 then
1814 return "inf"
1815 else if isinf == -1 then
1816 return "-inf"
1817 end
1818
1819 if nb == 0 then return self.to_i.to_s
1820 var f = self
1821 for i in [0..nb[ do f = f * 10.0
1822 if self > 0.0 then
1823 f = f + 0.5
1824 else
1825 f = f - 0.5
1826 end
1827 var i = f.to_i
1828 if i == 0 then return "0.0"
1829 var s = i.to_s
1830 var sl = s.length
1831 if sl > nb then
1832 var p1 = s.substring(0, s.length-nb)
1833 var p2 = s.substring(s.length-nb, nb)
1834 return p1 + "." + p2
1835 else
1836 return "0." + ("0"*(nb-sl)) + s
1837 end
1838 end
1839
1840 # `self` representation with `nb` digits after the '.'.
1841 #
1842 # assert 12.345.to_precision_native(1) == "12.3"
1843 # assert 12.345.to_precision_native(2) == "12.35"
1844 # assert 12.345.to_precision_native(3) == "12.345"
1845 # assert 12.345.to_precision_native(4) == "12.3450"
1846 fun to_precision_native(nb: Int): String import NativeString.to_s `{
1847 int size;
1848 char *str;
1849
1850 size = snprintf(NULL, 0, "%.*f", (int)nb, recv);
1851 str = malloc(size + 1);
1852 sprintf(str, "%.*f", (int)nb, recv );
1853
1854 return NativeString_to_s( str );
1855 `}
1856 end
1857
1858 redef class Char
1859 # assert 'x'.to_s == "x"
1860 redef fun to_s
1861 do
1862 var s = new FlatBuffer.with_capacity(1)
1863 s.chars[0] = self
1864 return s.to_s
1865 end
1866
1867 # Returns true if the char is a numerical digit
1868 #
1869 # assert '0'.is_numeric
1870 # assert '9'.is_numeric
1871 # assert not 'a'.is_numeric
1872 # assert not '?'.is_numeric
1873 fun is_numeric: Bool
1874 do
1875 return self >= '0' and self <= '9'
1876 end
1877
1878 # Returns true if the char is an alpha digit
1879 #
1880 # assert 'a'.is_alpha
1881 # assert 'Z'.is_alpha
1882 # assert not '0'.is_alpha
1883 # assert not '?'.is_alpha
1884 fun is_alpha: Bool
1885 do
1886 return (self >= 'a' and self <= 'z') or (self >= 'A' and self <= 'Z')
1887 end
1888
1889 # Returns true if the char is an alpha or a numeric digit
1890 #
1891 # assert 'a'.is_alphanumeric
1892 # assert 'Z'.is_alphanumeric
1893 # assert '0'.is_alphanumeric
1894 # assert '9'.is_alphanumeric
1895 # assert not '?'.is_alphanumeric
1896 fun is_alphanumeric: Bool
1897 do
1898 return self.is_numeric or self.is_alpha
1899 end
1900 end
1901
1902 redef class Collection[E]
1903 # Concatenate elements.
1904 redef fun to_s
1905 do
1906 var s = new FlatBuffer
1907 for e in self do if e != null then s.append(e.to_s)
1908 return s.to_s
1909 end
1910
1911 # Concatenate and separate each elements with `sep`.
1912 #
1913 # assert [1, 2, 3].join(":") == "1:2:3"
1914 # assert [1..3].join(":") == "1:2:3"
1915 fun join(sep: Text): String
1916 do
1917 if is_empty then return ""
1918
1919 var s = new FlatBuffer # Result
1920
1921 # Concat first item
1922 var i = iterator
1923 var e = i.item
1924 if e != null then s.append(e.to_s)
1925
1926 # Concat other items
1927 i.next
1928 while i.is_ok do
1929 s.append(sep)
1930 e = i.item
1931 if e != null then s.append(e.to_s)
1932 i.next
1933 end
1934 return s.to_s
1935 end
1936 end
1937
1938 redef class Array[E]
1939
1940 # Fast implementation
1941 redef fun to_s
1942 do
1943 var l = length
1944 if l == 0 then return ""
1945 if l == 1 then if self[0] == null then return "" else return self[0].to_s
1946 var its = _items
1947 var na = new NativeArray[String](l)
1948 var i = 0
1949 var sl = 0
1950 var mypos = 0
1951 while i < l do
1952 var itsi = its[i]
1953 if itsi == null then
1954 i += 1
1955 continue
1956 end
1957 var tmp = itsi.to_s
1958 sl += tmp.length
1959 na[mypos] = tmp
1960 i += 1
1961 mypos += 1
1962 end
1963 var ns = new NativeString(sl + 1)
1964 ns[sl] = '\0'
1965 i = 0
1966 var off = 0
1967 while i < mypos do
1968 var tmp = na[i]
1969 var tpl = tmp.length
1970 if tmp isa FlatString then
1971 tmp.items.copy_to(ns, tpl, tmp.index_from, off)
1972 off += tpl
1973 else
1974 for j in tmp.substrings do
1975 var s = j.as(FlatString)
1976 var slen = s.length
1977 s.items.copy_to(ns, slen, s.index_from, off)
1978 off += slen
1979 end
1980 end
1981 i += 1
1982 end
1983 return ns.to_s_with_length(sl)
1984 end
1985 end
1986
1987 redef class Map[K,V]
1988 # Concatenate couple of 'key value'.
1989 # key and value are separated by `couple_sep`.
1990 # each couple is separated each couple with `sep`.
1991 #
1992 # var m = new ArrayMap[Int, String]
1993 # m[1] = "one"
1994 # m[10] = "ten"
1995 # assert m.join("; ", "=") == "1=one; 10=ten"
1996 fun join(sep: String, couple_sep: String): String
1997 do
1998 if is_empty then return ""
1999
2000 var s = new FlatBuffer # Result
2001
2002 # Concat first item
2003 var i = iterator
2004 var k = i.key
2005 var e = i.item
2006 s.append("{k}{couple_sep}{e or else "<null>"}")
2007
2008 # Concat other items
2009 i.next
2010 while i.is_ok do
2011 s.append(sep)
2012 k = i.key
2013 e = i.item
2014 s.append("{k}{couple_sep}{e or else "<null>"}")
2015 i.next
2016 end
2017 return s.to_s
2018 end
2019 end
2020
2021 ###############################################################################
2022 # Native classes #
2023 ###############################################################################
2024
2025 # Native strings are simple C char *
2026 extern class NativeString `{ char* `}
2027 super StringCapable
2028 # Creates a new NativeString with a capacity of `length`
2029 new(length: Int) is intern
2030 fun [](index: Int): Char is intern
2031 fun []=(index: Int, item: Char) is intern
2032 fun copy_to(dest: NativeString, length: Int, from: Int, to: Int) is intern
2033
2034 # Position of the first nul character.
2035 fun cstring_length: Int
2036 do
2037 var l = 0
2038 while self[l] != '\0' do l += 1
2039 return l
2040 end
2041 fun atoi: Int is intern
2042 fun atof: Float is extern "atof"
2043
2044 redef fun to_s
2045 do
2046 return to_s_with_length(cstring_length)
2047 end
2048
2049 fun to_s_with_length(length: Int): FlatString
2050 do
2051 assert length >= 0
2052 var str = new FlatString.with_infos(self, length, 0, length - 1)
2053 str.real_items = self
2054 return str
2055 end
2056
2057 fun to_s_with_copy: FlatString
2058 do
2059 var length = cstring_length
2060 var new_self = calloc_string(length + 1)
2061 copy_to(new_self, length, 0, 0)
2062 var str = new FlatString.with_infos(new_self, length, 0, length - 1)
2063 str.real_items = self
2064 return str
2065 end
2066 end
2067
2068 # StringCapable objects can create native strings
2069 interface StringCapable
2070 protected fun calloc_string(size: Int): NativeString is intern
2071 end
2072
2073 redef class Sys
2074 private var args_cache: nullable Sequence[String]
2075
2076 # The arguments of the program as given by the OS
2077 fun program_args: Sequence[String]
2078 do
2079 if _args_cache == null then init_args
2080 return _args_cache.as(not null)
2081 end
2082
2083 # The name of the program as given by the OS
2084 fun program_name: String
2085 do
2086 return native_argv(0).to_s
2087 end
2088
2089 # Initialize `program_args` with the contents of `native_argc` and `native_argv`.
2090 private fun init_args
2091 do
2092 var argc = native_argc
2093 var args = new Array[String].with_capacity(0)
2094 var i = 1
2095 while i < argc do
2096 args[i-1] = native_argv(i).to_s
2097 i += 1
2098 end
2099 _args_cache = args
2100 end
2101
2102 # First argument of the main C function.
2103 private fun native_argc: Int is intern
2104
2105 # Second argument of the main C function.
2106 private fun native_argv(i: Int): NativeString is intern
2107 end
2108
2109 # Comparator that efficienlty use `to_s` to compare things
2110 #
2111 # The comparaison call `to_s` on object and use the result to order things.
2112 #
2113 # var a = [1, 2, 3, 10, 20]
2114 # (new CachedAlphaComparator).sort(a)
2115 # assert a == [1, 10, 2, 20, 3]
2116 #
2117 # Internally the result of `to_s` is cached in a HashMap to counter
2118 # uneficient implementation of `to_s`.
2119 #
2120 # Note: it caching is not usefull, see `alpha_comparator`
2121 class CachedAlphaComparator
2122 super Comparator[Object]
2123
2124 private var cache = new HashMap[Object, String]
2125
2126 private fun do_to_s(a: Object): String do
2127 if cache.has_key(a) then return cache[a]
2128 var res = a.to_s
2129 cache[a] = res
2130 return res
2131 end
2132
2133 redef fun compare(a, b) do
2134 return do_to_s(a) <=> do_to_s(b)
2135 end
2136 end
2137
2138 # see `alpha_comparator`
2139 private class AlphaComparator
2140 super Comparator[Object]
2141 redef fun compare(a, b) do return a.to_s <=> b.to_s
2142 end
2143
2144 # Stateless comparator that naively use `to_s` to compare things.
2145 #
2146 # Note: the result of `to_s` is not cached, thus can be invoked a lot
2147 # on a single instace. See `CachedAlphaComparator` as an alternative.
2148 #
2149 # var a = [1, 2, 3, 10, 20]
2150 # alpha_comparator.sort(a)
2151 # assert a == [1, 10, 2, 20, 3]
2152 fun alpha_comparator: Comparator[Object] do return once new AlphaComparator
2153
2154 # The arguments of the program as given by the OS
2155 fun args: Sequence[String]
2156 do
2157 return sys.program_args
2158 end