lib/standard: remove the only `attr-in-refinement` warning
[nit.git] / lib / standard / string.nit
index 4892f95..6af3693 100644 (file)
@@ -60,7 +60,7 @@ abstract class Text
        fun substring(from: Int, count: Int): SELFTYPE is abstract
 
        # Iterates on the substrings of self if any
-       fun substrings: Iterator[Text] is abstract
+       fun substrings: Iterator[FlatText] is abstract
 
        # Is the current Text empty (== "")
        #
@@ -249,6 +249,16 @@ abstract class Text
        #     assert "ff".to_hex == 255
        fun to_hex: Int do return a_to(16)
 
+       # If `self` contains only digits <= '7', return the corresponding integer.
+       #
+       #     assert "714".to_oct == 460
+       fun to_oct: Int do return a_to(8)
+
+       # If `self` contains only '0' et '1', return the corresponding integer.
+       #
+       #     assert "101101".to_bin == 45
+       fun to_bin: Int do return a_to(2)
+
        # If `self` contains only digits and letters, return the corresponding integer in a given base
        #
        #     assert "120".a_to(3)     == 15
@@ -345,7 +355,7 @@ abstract class Text
                end
                return true
        end
-                       
+
        # Removes the whitespaces at the beginning of self
        #
        #     assert " \n\thello \n\t".l_trim == "hello \n\t"
@@ -531,6 +541,14 @@ abstract class Text
        #
        #     assert "abAB12<>&".escape_to_c         == "abAB12<>&"
        #     assert "\n\"'\\".escape_to_c         == "\\n\\\"\\'\\\\"
+       #
+       # Most non-printable characters (bellow ASCII 32) are escaped to an octal form `\nnn`.
+       # Three digits are always used to avoid following digits to be interpreted as an element
+       # of the octal sequence.
+       #
+       #     assert "{0.ascii}{1.ascii}{8.ascii}{31.ascii}{32.ascii}".escape_to_c == "\\000\\001\\010\\037 "
+       #
+       # The exceptions are the common `\t` and `\n`.
        fun escape_to_c: String
        do
                var b = new FlatBuffer
@@ -538,8 +556,10 @@ abstract class Text
                        var c = chars[i]
                        if c == '\n' then
                                b.append("\\n")
+                       else if c == '\t' then
+                               b.append("\\t")
                        else if c == '\0' then
-                               b.append("\\0")
+                               b.append("\\000")
                        else if c == '"' then
                                b.append("\\\"")
                        else if c == '\'' then
@@ -547,7 +567,17 @@ abstract class Text
                        else if c == '\\' then
                                b.append("\\\\")
                        else if c.ascii < 32 then
-                               b.append("\\{c.ascii.to_base(8, false)}")
+                               b.add('\\')
+                               var oct = c.ascii.to_base(8, false)
+                               # Force 3 octal digits since it is the
+                               # maximum allowed in the C specification
+                               if oct.length == 1 then
+                                       b.add('0')
+                                       b.add('0')
+                               else if oct.length == 2 then
+                                       b.add('0')
+                               end
+                               b.append(oct)
                        else
                                b.add(c)
                        end
@@ -829,6 +859,60 @@ abstract class Text
                return hash_cache.as(not null)
        end
 
+       # Gives the formatted string back as a Nit string with `args` in place
+       #
+       #    assert "This %1 is a %2.".format("String", "formatted String") == "This String is a formatted String."
+       #    assert "\\%1 This string".format("String") == "\\%1 This string"
+       fun format(args: Object...): String do
+               var s = new Array[Text]
+               var curr_st = 0
+               var i = 0
+               while i < length do
+                       # Skip escaped characters
+                       if self[i] == '\\' then
+                               i += 1
+                       # In case of format
+                       else if self[i] == '%' then
+                               var fmt_st = i
+                               i += 1
+                               var ciph_st = i
+                               while i < length and self[i].is_numeric do
+                                       i += 1
+                               end
+                               i -= 1
+                               var fmt_end = i
+                               var ciph_len = fmt_end - ciph_st + 1
+                               s.push substring(curr_st, fmt_st - curr_st)
+                               s.push args[substring(ciph_st, ciph_len).to_i - 1].to_s
+                               curr_st = i + 1
+                       end
+                       i += 1
+               end
+               s.push substring(curr_st, length - curr_st)
+               return s.to_s
+       end
+
+       # Copies `n` bytes from `self` at `src_offset` into `dest` starting at `dest_offset`
+       #
+       # Basically a high-level synonym of NativeString::copy_to
+       #
+       # REQUIRE: `n` must be large enough to contain `len` bytes
+       #
+       #       var ns = new NativeString(8)
+       #       "Text is String".copy_to_native(ns, 8, 2, 0)
+       #       assert ns.to_s_with_length(8) == "xt is St"
+       #
+       fun copy_to_native(dest: NativeString, n, src_offset, dest_offset: Int) do
+               var mypos = src_offset
+               var itspos = dest_offset
+               while n > 0 do
+                       dest[itspos] = self.chars[mypos]
+                       itspos += 1
+                       mypos += 1
+                       n -= 1
+               end
+       end
+
 end
 
 # All kinds of array-based text representations.
@@ -844,6 +928,23 @@ abstract class FlatText
        # Real items, used as cache for to_cstring is called
        private var real_items: nullable NativeString = null
 
+       # Returns a char* starting at position `index_from`
+       #
+       # WARNING: If you choose to use this service, be careful of the following.
+       #
+       # Strings and NativeString are *ideally* always allocated through a Garbage Collector.
+       # Since the GC tracks the use of the pointer for the beginning of the char*, it may be
+       # deallocated at any moment, rendering the pointer returned by this function invalid.
+       # Any access to freed memory may very likely cause undefined behaviour or a crash.
+       # (Failure to do so will most certainly result in long and painful debugging hours)
+       #
+       # The only safe use of this pointer is if it is ephemeral (e.g. read in a C function
+       # then immediately return).
+       #
+       # As always, do not modify the content of the String in C code, if this is what you want
+       # copy locally the char* as Nit Strings are immutable.
+       private fun fast_cstring: NativeString is abstract
+
        redef var length: Int = 0
 
        redef fun output
@@ -856,6 +957,10 @@ abstract class FlatText
        end
 
        redef fun flatten do return self
+
+       redef fun copy_to_native(dest, n, src_offset, dest_offset) do
+               items.copy_to(dest, n, src_offset, dest_offset)
+       end
 end
 
 # Abstract class for the SequenceRead compatible
@@ -916,7 +1021,7 @@ abstract class String
        #     assert "helloworld".insert_at(" ", 5)     == "hello world"
        fun insert_at(s: String, pos: Int): SELFTYPE is abstract
 
-       redef fun substrings: Iterator[String] is abstract
+       redef fun substrings is abstract
 
        # Returns a reversed version of self
        #
@@ -935,41 +1040,61 @@ abstract class String
        #     assert "Hello World!".to_lower     == "hello world!"
        fun to_lower : SELFTYPE is abstract
 
-       # Takes a camel case `self` and converts it to snake case 
+       # Takes a camel case `self` and converts it to snake case
        #
        #     assert "randomMethodId".to_snake_case == "random_method_id"
        #
-       # If `self` is upper, it is returned unchanged
+       # The rules are the following:
        #
-       #     assert "RANDOM_METHOD_ID".to_snake_case == "RANDOM_METHOD_ID"
+       # An uppercase is always converted to a lowercase
        #
-       # If the identifier is prefixed by an underscore, the underscore is ignored
+       #     assert "HELLO_WORLD".to_snake_case == "hello_world"
+       #
+       # An uppercase that follows a lowercase is prefixed with an underscore
+       #
+       #     assert "HelloTheWORLD".to_snake_case == "hello_the_world"
        #
-       #     assert "_privateField".to_snake_case == "_private_field"
+       # An uppercase that follows an uppercase and is followed by a lowercase, is prefixed with an underscore
+       #
+       #     assert "HelloTHEWorld".to_snake_case == "hello_the_world"
+       #
+       # All other characters are kept as is; `self` does not need to be a proper CamelCased string.
+       #
+       #     assert "=-_H3ll0Th3W0rld_-=".to_snake_case == "=-_h3ll0th3w0rld_-="
        fun to_snake_case: SELFTYPE
        do
-               if self.is_upper then return self
+               if self.is_lower then return self
 
                var new_str = new FlatBuffer.with_capacity(self.length)
-               var is_first_char = true
+               var prev_is_lower = false
+               var prev_is_upper = false
 
                for i in [0..length[ do
                        var char = chars[i]
-                       if is_first_char then 
-                               new_str.add(char.to_lower)
-                               is_first_char = false
+                       if char.is_lower then
+                               new_str.add(char)
+                               prev_is_lower = true
+                               prev_is_upper = false
                        else if char.is_upper then
-                               new_str.add('_')
+                               if prev_is_lower then
+                                       new_str.add('_')
+                               else if prev_is_upper and i+1 < length and chars[i+1].is_lower then
+                                       new_str.add('_')
+                               end
                                new_str.add(char.to_lower)
+                               prev_is_lower = false
+                               prev_is_upper = true
                        else
                                new_str.add(char)
+                               prev_is_lower = false
+                               prev_is_upper = false
                        end
                end
-               
+
                return new_str.to_s
        end
 
-       # Takes a snake case `self` and converts it to camel case 
+       # Takes a snake case `self` and converts it to camel case
        #
        #     assert "random_method_id".to_camel_case == "randomMethodId"
        #
@@ -1099,6 +1224,8 @@ class FlatString
                return native.to_s_with_length(self.length)
        end
 
+       redef fun fast_cstring do return items.fast_cstring(index_from)
+
        redef fun substring(from, count)
        do
                assert count >= 0
@@ -1547,6 +1674,8 @@ class FlatBuffer
 
        private var capacity: Int = 0
 
+       redef fun fast_cstring do return items.fast_cstring(0)
+
        redef fun substrings do return new FlatSubstringsIter(self)
 
        # Re-copies the `NativeString` into a new one and sets it as the new `Buffer`
@@ -1911,9 +2040,7 @@ end
 redef class Int
 
        # Wrapper of strerror C function
-       private fun strerror_ext: NativeString is extern `{
-               return strerror(recv);
-       `}
+       private fun strerror_ext: NativeString is extern "strerror"
 
        # Returns a string describing error number
        fun strerror: String do return strerror_ext.to_s
@@ -2269,6 +2396,12 @@ extern class NativeString `{ char* `}
        # Creates a new NativeString with a capacity of `length`
        new(length: Int) is intern
 
+       # Returns a char* starting at `index`.
+       #
+       # WARNING: Unsafe for extern code, use only for temporary
+       # pointer manipulation purposes (e.g. write to file or such)
+       fun fast_cstring(index: Int): NativeString is intern
+
        # Get char at `index`.
        fun [](index: Int): Char is intern
 
@@ -2319,7 +2452,7 @@ extern class NativeString `{ char* `}
 end
 
 redef class Sys
-       private var args_cache: nullable Sequence[String]
+       private var args_cache: nullable Sequence[String] = null
 
        # The arguments of the program as given by the OS
        fun program_args: Sequence[String]