lib: move errno and strerror to legacy FFI
[nit.git] / lib / standard / string.nit
index def85f4..8db6a4e 100644 (file)
@@ -350,12 +350,12 @@ abstract class Text
        #
        #     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 +366,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,23 +379,61 @@ 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
 
-       # Returns `self` removed from its last `\n` (if any).
+       # 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"
        #    assert "Hello".chomp   == "Hello"
-       #    assert "\n\n\n".chomp  == "\n\n"
        #
-       # This method is mainly used to remove the LINE_FEED character from lines of text.
+       #    assert "\n".chomp == ""
+       #    assert "".chomp   == ""
+       #
+       # Line terminators are `"\n"`, `"\r\n"` and `"\r"`.
+       # A single line terminator, the last one, is removed.
+       #
+       #    assert "\r\n".chomp     == ""
+       #    assert "\r\n\n".chomp   == "\r\n"
+       #    assert "\r\n\r\n".chomp == "\r\n"
+       #    assert "\r\n\r".chomp   == "\r\n"
+       #
+       # Note: unlike with most IO methods like `Reader::read_line`,
+       # a single `\r` is considered here to be a line terminator and will be removed.
        fun chomp: SELFTYPE
        do
-               if self.is_empty or self.chars.last != '\n' then return self
-               return substring(0, length-1)
+               var len = length
+               if len == 0 then return self
+               var l = self.chars.last
+               if l == '\r' then
+                       return substring(0, len-1)
+               else if l != '\n' then
+                       return self
+               else if len > 1 and self.chars[len-2] == '\r' then
+                       return substring(0, len-2)
+               else
+                       return substring(0, len-1)
+               end
        end
 
        # Justify a self in a space of `length`
@@ -405,6 +443,8 @@ abstract class Text
        # * 1.0 for right-justified (all spaces at the left)
        # * 0.5 for centered (half the spaces at the left)
        #
+       # Examples
+       #
        #     assert "hello".justify(10, 0.0)  == "hello     "
        #     assert "hello".justify(10, 1.0)  == "     hello"
        #     assert "hello".justify(10, 0.5)  == "  hello   "
@@ -413,25 +453,51 @@ abstract class Text
        #
        #     assert "hello".justify(2, 0.0)   == "hello"
        #
-       # REQUIRE: left >= 0.0 and left <= 1.0
+       # REQUIRE: `left >= 0.0 and left <= 1.0`
        # ENSURE: `self.length <= length implies result.length == length`
-       # ENSURE: `self.length >= length implies result == self
-       fun justify(length: Int, left: Float): SELFTYPE
+       # ENSURE: `self.length >= length implies result == self`
+       fun justify(length: Int, left: Float): String
        do
                var diff = length - self.length
-               if diff <= 0 then return self
+               if diff <= 0 then return to_s
                assert left >= 0.0 and left <= 1.0
                var before = (diff.to_f * left).to_i
                return " " * before + self + " " * (diff-before)
        end
 
-       # Mangle a string to be a unique string only made of alphanumeric characters
+       # Mangle a string to be a unique string only made of alphanumeric characters and underscores.
+       #
+       # This method is injective (two different inputs never produce the same
+       # output) and the returned string always respect the following rules:
+       #
+       # * Contains only US-ASCII letters, digits and underscores.
+       # * Never starts with a digit.
+       # * Never ends with an underscore.
+       # * Never contains two contiguous underscores.
+       #
+       #     assert "42_is/The answer!".to_cmangle == "_52d2_is_47dThe_32danswer_33d"
+       #     assert "__".to_cmangle == "_95d_95d"
+       #     assert "__d".to_cmangle == "_95d_d"
+       #     assert "_d_".to_cmangle == "_d_95d"
+       #     assert "_42".to_cmangle == "_95d42"
+       #     assert "foo".to_cmangle == "foo"
+       #     assert "".to_cmangle == ""
        fun to_cmangle: String
        do
+               if is_empty then return ""
                var res = new FlatBuffer
                var underscore = false
-               for i in [0..length[ do
-                       var c = chars[i]
+               var start = 0
+               var c = chars[0]
+
+               if c >= '0' and c <= '9' then
+                       res.add('_')
+                       res.append(c.ascii.to_s)
+                       res.add('d')
+                       start = 1
+               end
+               for i in [start..length[ do
+                       c = chars[i]
                        if (c >= 'a' and c <= 'z') or (c >='A' and c <= 'Z') then
                                res.add(c)
                                underscore = false
@@ -454,6 +520,10 @@ abstract class Text
                                underscore = false
                        end
                end
+               if underscore then
+                       res.append('_'.ascii.to_s)
+                       res.add('d')
+               end
                return res.to_s
        end
 
@@ -461,6 +531,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
@@ -468,8 +546,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
@@ -477,7 +557,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
@@ -658,7 +748,7 @@ abstract class Text
        #     assert "a&b-<>\"x\"/'".html_escape      ==  "a&amp;b-&lt;&gt;&#34;x&#34;&#47;&#39;"
        #
        # SEE: <https://www.owasp.org/index.php/XSS_%28Cross_Site_Scripting%29_Prevention_Cheat_Sheet#RULE_.231_-_HTML_Escape_Before_Inserting_Untrusted_Data_into_HTML_Element_Content>
-       fun html_escape: SELFTYPE
+       fun html_escape: String
        do
                var buf = new FlatBuffer
 
@@ -774,6 +864,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
@@ -824,7 +931,7 @@ end
 abstract class String
        super Text
 
-       redef type SELFTYPE: String
+       redef type SELFTYPE: String is fixed
 
        redef fun to_s do return self
 
@@ -947,9 +1054,9 @@ abstract class String
        #
        # SEE : `Char::is_letter` for the definition of letter.
        #
-       #    assert "jAVASCRIPT".capitalized == "Javascript"
-       #    assert "i am root".capitalized == "I Am Root"
-       #    assert "ab_c -ab0c ab\nc".capitalized == "Ab_C -Ab0C Ab\nC"
+       #     assert "jAVASCRIPT".capitalized == "Javascript"
+       #     assert "i am root".capitalized == "I Am Root"
+       #     assert "ab_c -ab0c ab\nc".capitalized == "Ab_C -Ab0C Ab\nC"
        fun capitalized: SELFTYPE do
                if length == 0 then return self
 
@@ -999,7 +1106,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: SequenceRead[Char] = new FlatStringCharView(self) is lazy
 
        redef fun [](index)
        do
@@ -1029,6 +1136,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
@@ -1039,15 +1148,19 @@ class FlatString
                        from = 0
                end
 
-               var realFrom = index_from + from
+               var new_from = index_from + from
 
-               if (realFrom + count) > index_to then return new FlatString.with_infos(items, index_to - realFrom + 1, realFrom, index_to)
+               if (new_from + count) > index_to then
+                       var new_len = index_to - new_from + 1
+                       if new_len <= 0 then return empty
+                       return new FlatString.with_infos(items, new_len, new_from, index_to)
+               end
 
-               if count == 0 then return empty
+               if count <= 0 then return empty
 
-               var to = realFrom + count - 1
+               var to = new_from + count - 1
 
-               return new FlatString.with_infos(items, to - realFrom + 1, realFrom, to)
+               return new FlatString.with_infos(items, to - new_from + 1, new_from, to)
        end
 
        redef fun empty do return "".as(FlatString)
@@ -1106,10 +1219,14 @@ class FlatString
        #              String Specific Methods           #
        ##################################################
 
-       private init with_infos(items: NativeString, len: Int, from: Int, to: Int)
+       # Low-level creation of a new string with given data.
+       #
+       # `items` will be used as is, without copy, to retrieve the characters of the string.
+       # Aliasing issues is the responsibility of the caller.
+       private init with_infos(items: NativeString, length: Int, from: Int, to: Int)
        do
                self.items = items
-               length = len
+               self.length = length
                index_from = from
                index_to = to
        end
@@ -1342,7 +1459,7 @@ end
 abstract class Buffer
        super Text
 
-       redef type SELFTYPE: Buffer
+       redef type SELFTYPE: Buffer is fixed
 
        # Specific implementations MUST set this to `true` in order to invalidate caches
        protected var is_dirty = true
@@ -1424,15 +1541,15 @@ abstract class Buffer
        #
        # SEE: `Char::is_letter` for the definition of a letter.
        #
-       #    var b = new FlatBuffer.from("jAVAsCriPt")
-       #    b.capitalize
-       #    assert b == "Javascript"
-       #    b = new FlatBuffer.from("i am root")
-       #    b.capitalize
-       #    assert b == "I Am Root"
-       #    b = new FlatBuffer.from("ab_c -ab0c ab\nc")
-       #    b.capitalize
-       #    assert b == "Ab_C -Ab0C Ab\nC"
+       #     var b = new FlatBuffer.from("jAVAsCriPt")
+       #     b.capitalize
+       #     assert b == "Javascript"
+       #     b = new FlatBuffer.from("i am root")
+       #     b.capitalize
+       #     assert b == "I Am Root"
+       #     b = new FlatBuffer.from("ab_c -ab0c ab\nc")
+       #     b.capitalize
+       #     assert b == "Ab_C -Ab0C Ab\nC"
        fun capitalize do
                if length == 0 then return
                var c = self[0].to_upper
@@ -1465,12 +1582,12 @@ class FlatBuffer
        super FlatText
        super Buffer
 
-       redef type SELFTYPE: FlatBuffer
-
-       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`
@@ -1555,6 +1672,20 @@ class FlatBuffer
        # Create a new empty string.
        init do end
 
+       # Low-level creation a new buffer with given data.
+       #
+       # `items` will be used as is, without copy, to store the characters of the buffer.
+       # Aliasing issues is the responsibility of the caller.
+       #
+       # If `items` is shared, `written` should be set to true after the creation
+       # so that a modification will do a copy-on-write.
+       private init with_infos(items: NativeString, capacity, length: Int)
+       do
+               self.items = items
+               self.length = length
+               self.capacity = capacity
+       end
+
        # Create a new string copied from `s`.
        init from(s: Text)
        do
@@ -1579,7 +1710,6 @@ class FlatBuffer
        init with_capacity(cap: Int)
        do
                assert cap >= 0
-               # _items = new NativeString.calloc(cap)
                items = new NativeString(cap+1)
                capacity = cap
                length = 0
@@ -1623,11 +1753,10 @@ class FlatBuffer
                if from < 0 then from = 0
                if count > length then count = length
                if from < count then
-                       var r = new FlatBuffer.with_capacity(count - from)
-                       while from < count do
-                               r.chars.push(items[from])
-                               from += 1
-                       end
+                       var len = count - from
+                       var r_items = new NativeString(len)
+                       items.copy_to(r_items, len, from, 0)
+                       var r = new FlatBuffer.with_infos(r_items, len, len)
                        return r
                else
                        return new FlatBuffer
@@ -1789,7 +1918,7 @@ redef class Object
 
        # The class name of the object.
        #
-       #    assert 5.class_name == "Int"
+       #     assert 5.class_name == "Int"
        fun class_name: String do return native_class_name.to_s
 
        # Developer readable representation of `self`.
@@ -1823,9 +1952,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
@@ -1854,15 +1981,26 @@ redef class Int
                end
        end
 
+       # C function to calculate the length of the `NativeString` to receive `self`
+       private fun int_to_s_len: Int is extern "native_int_length_str"
+
        # C function to convert an nit Int to a NativeString (char*)
-       private fun native_int_to_s: NativeString is extern "native_int_to_s"
+       private fun native_int_to_s(nstr: NativeString, strlen: Int) is extern "native_int_to_s"
 
        # return displayable int in base 10 and signed
        #
        #     assert 1.to_s            == "1"
        #     assert (-123).to_s       == "-123"
        redef fun to_s do
-               return native_int_to_s.to_s
+               # 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'
+               native_int_to_s(ns, nslen + 1)
+               return ns.to_s_with_length(nslen)
        end
 
        # return displayable int in hexadecimal
@@ -1954,23 +2092,6 @@ redef class Float
 
                return p1 + "." + p2
        end
-
-       # `self` representation with `nb` digits after the '.'.
-       #
-       #     assert 12.345.to_precision_native(1) == "12.3"
-       #     assert 12.345.to_precision_native(2) == "12.35"
-       #     assert 12.345.to_precision_native(3) == "12.345"
-       #     assert 12.345.to_precision_native(4) == "12.3450"
-       fun to_precision_native(nb: Int): String import NativeString.to_s `{
-               int size;
-               char *str;
-
-               size = snprintf(NULL, 0, "%.*f", (int)nb, recv);
-               str = malloc(size + 1);
-               sprintf(str, "%.*f", (int)nb, recv );
-
-               return NativeString_to_s( str );
-       `}
 end
 
 redef class Char
@@ -1984,10 +2105,10 @@ redef class Char
 
        # Returns true if the char is a numerical digit
        #
-       #      assert '0'.is_numeric
-       #      assert '9'.is_numeric
-       #      assert not 'a'.is_numeric
-       #      assert not '?'.is_numeric
+       #     assert '0'.is_numeric
+       #     assert '9'.is_numeric
+       #     assert not 'a'.is_numeric
+       #     assert not '?'.is_numeric
        fun is_numeric: Bool
        do
                return self >= '0' and self <= '9'
@@ -1995,10 +2116,10 @@ redef class Char
 
        # Returns true if the char is an alpha digit
        #
-       #      assert 'a'.is_alpha
-       #      assert 'Z'.is_alpha
-       #      assert not '0'.is_alpha
-       #      assert not '?'.is_alpha
+       #     assert 'a'.is_alpha
+       #     assert 'Z'.is_alpha
+       #     assert not '0'.is_alpha
+       #     assert not '?'.is_alpha
        fun is_alpha: Bool
        do
                return (self >= 'a' and self <= 'z') or (self >= 'A' and self <= 'Z')
@@ -2006,11 +2127,11 @@ redef class Char
 
        # Returns true if the char is an alpha or a numeric digit
        #
-       #      assert 'a'.is_alphanumeric
-       #      assert 'Z'.is_alphanumeric
-       #      assert '0'.is_alphanumeric
-       #      assert '9'.is_alphanumeric
-       #      assert not '?'.is_alphanumeric
+       #     assert 'a'.is_alphanumeric
+       #     assert 'Z'.is_alphanumeric
+       #     assert '0'.is_alphanumeric
+       #     assert '9'.is_alphanumeric
+       #     assert not '?'.is_alphanumeric
        fun is_alphanumeric: Bool
        do
                return self.is_numeric or self.is_alpha
@@ -2102,6 +2223,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`.
@@ -2121,7 +2284,7 @@ redef class Map[K,V]
                var i = iterator
                var k = i.key
                var e = i.item
-               s.append("{k}{couple_sep}{e or else "<null>"}")
+               s.append("{k or else "<null>"}{couple_sep}{e or else "<null>"}")
 
                # Concat other items
                i.next
@@ -2129,7 +2292,7 @@ redef class Map[K,V]
                        s.append(sep)
                        k = i.key
                        e = i.item
-                       s.append("{k}{couple_sep}{e or else "<null>"}")
+                       s.append("{k or else "<null>"}{couple_sep}{e or else "<null>"}")
                        i.next
                end
                return s.to_s
@@ -2145,6 +2308,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