Merge: modelize: ask that attributes in refinements are either noautoninit or have...
[nit.git] / lib / standard / string.nit
index d3aa602..7fe5de3 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,17 +355,17 @@ 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"
        #
-       # A whitespace is defined as any character which ascii value is less than or equal to 32
+       # `Char::is_whitespace` determines what is a whitespace.
        fun l_trim: SELFTYPE
        do
                var iter = self.chars.iterator
                while iter.is_ok do
-                       if iter.item.ascii > 32 then break
+                       if not iter.item.is_whitespace then break
                        iter.next
                end
                if iter.index == length then return self.empty
@@ -366,12 +376,12 @@ abstract class Text
        #
        #     assert " \n\thello \n\t".r_trim == " \n\thello"
        #
-       # A whitespace is defined as any character which ascii value is less than or equal to 32
+       # `Char::is_whitespace` determines what is a whitespace.
        fun r_trim: SELFTYPE
        do
                var iter = self.chars.reverse_iterator
                while iter.is_ok do
-                       if iter.item.ascii > 32 then break
+                       if not iter.item.is_whitespace then break
                        iter.next
                end
                if iter.index < 0 then return self.empty
@@ -379,12 +389,29 @@ abstract class Text
        end
 
        # Trims trailing and preceding white spaces
-       # A whitespace is defined as any character which ascii value is less than or equal to 32
        #
        #     assert "  Hello  World !  ".trim   == "Hello  World !"
        #     assert "\na\nb\tc\t".trim          == "a\nb\tc"
+       #
+       # `Char::is_whitespace` determines what is a whitespace.
        fun trim: SELFTYPE do return (self.l_trim).r_trim
 
+       # Is the string non-empty but only made of whitespaces?
+       #
+       #    assert " \n\t ".is_whitespace    == true
+       #    assert "  hello  ".is_whitespace == false
+       #    assert "".is_whitespace          == false
+       #
+       # `Char::is_whitespace` determines what is a whitespace.
+       fun is_whitespace: Bool
+       do
+               if is_empty then return false
+               for c in self.chars do
+                       if not c.is_whitespace then return false
+               end
+               return true
+       end
+
        # Returns `self` removed from its last line terminator (if any).
        #
        #    assert "Hello\n".chomp == "Hello"
@@ -514,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
@@ -521,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
@@ -530,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
@@ -812,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.
@@ -827,7 +928,24 @@ abstract class FlatText
        # Real items, used as cache for to_cstring is called
        private var real_items: nullable NativeString = null
 
-       redef var length: Int = 0
+       # 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 = 0
 
        redef fun output
        do
@@ -839,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
@@ -899,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
        #
@@ -918,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"
+       #
+       # 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"
        #
-       #     assert "_privateField".to_snake_case == "_private_field"
+       # 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"
        #
@@ -1052,7 +1194,7 @@ class FlatString
        # Indes in _items of the last item of the string
        private var index_to: Int is noinit
 
-       redef var chars: SequenceRead[Char] = new FlatStringCharView(self)
+       redef var chars = new FlatStringCharView(self) is lazy
 
        redef fun [](index)
        do
@@ -1082,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
@@ -1175,8 +1319,7 @@ class FlatString
                index_to = to
        end
 
-       redef fun to_cstring: NativeString
-       do
+       redef fun to_cstring do
                if real_items != null then
                        return real_items.as(not null)
                else
@@ -1526,10 +1669,12 @@ class FlatBuffer
        super FlatText
        super Buffer
 
-       redef var chars: Sequence[Char] = new FlatBufferCharView(self)
+       redef var chars: Sequence[Char] = new FlatBufferCharView(self) is lazy
 
        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`
@@ -1592,8 +1737,7 @@ class FlatBuffer
                capacity = c
        end
 
-       redef fun to_s: String
-       do
+       redef fun to_s do
                written = true
                if length == 0 then items = new NativeString(1)
                return new FlatString.with_infos(items, length, 0, length - 1)
@@ -1891,12 +2035,30 @@ redef class Bool
        end
 end
 
+redef class Byte
+       # C function to calculate the length of the `NativeString` to receive `self`
+       private fun byte_to_s_len: Int is extern "native_byte_length_str"
+
+       # C function to convert an nit Int to a NativeString (char*)
+       private fun native_byte_to_s(nstr: NativeString, strlen: Int) is extern "native_byte_to_s"
+
+       # Displayable byte in its hexadecimal form (0x..)
+       #
+       #     assert 1.to_b.to_s       == "0x01"
+       #     assert (-123).to_b.to_s  == "0x85"
+       redef fun to_s do
+               var nslen = byte_to_s_len
+               var ns = new NativeString(nslen + 1)
+               ns[nslen] = '\0'
+               native_byte_to_s(ns, nslen + 1)
+               return ns.to_s_with_length(nslen)
+       end
+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
@@ -1936,6 +2098,10 @@ redef class Int
        #     assert 1.to_s            == "1"
        #     assert (-123).to_s       == "-123"
        redef fun to_s do
+               # Fast case for common numbers
+               if self == 0 then return "0"
+               if self == 1 then return "1"
+
                var nslen = int_to_s_len
                var ns = new NativeString(nslen + 1)
                ns[nslen] = '\0'
@@ -2082,6 +2248,12 @@ redef class Collection[E]
        # Concatenate elements.
        redef fun to_s
        do
+               return plain_to_s
+       end
+
+       # Concatenate element without separators
+       fun plain_to_s: String
+       do
                var s = new FlatBuffer
                for e in self do if e != null then s.append(e.to_s)
                return s.to_s
@@ -2117,7 +2289,7 @@ end
 redef class Array[E]
 
        # Fast implementation
-       redef fun to_s
+       redef fun plain_to_s
        do
                var l = length
                if l == 0 then return ""
@@ -2163,6 +2335,48 @@ redef class Array[E]
        end
 end
 
+redef class NativeArray[E]
+       # Join all the elements using `to_s`
+       #
+       # REQUIRE: `self isa NativeArray[String]`
+       # REQUIRE: all elements are initialized
+       fun native_to_s: String
+       do
+               assert self isa NativeArray[String]
+               var l = length
+               var na = self
+               var i = 0
+               var sl = 0
+               var mypos = 0
+               while i < l do
+                       sl += na[i].length
+                       i += 1
+                       mypos += 1
+               end
+               var ns = new NativeString(sl + 1)
+               ns[sl] = '\0'
+               i = 0
+               var off = 0
+               while i < mypos do
+                       var tmp = na[i]
+                       var tpl = tmp.length
+                       if tmp isa FlatString then
+                               tmp.items.copy_to(ns, tpl, tmp.index_from, off)
+                               off += tpl
+                       else
+                               for j in tmp.substrings do
+                                       var s = j.as(FlatString)
+                                       var slen = s.length
+                                       s.items.copy_to(ns, slen, s.index_from, off)
+                                       off += slen
+                               end
+                       end
+                       i += 1
+               end
+               return ns.to_s_with_length(sl)
+       end
+end
+
 redef class Map[K,V]
        # Concatenate couple of 'key value'.
        # key and value are separated by `couple_sep`.
@@ -2206,6 +2420,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
 
@@ -2256,7 +2476,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]