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