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