lib: move errno and strerror to legacy FFI
[nit.git] / lib / standard / string.nit
index b18e698..8db6a4e 100644 (file)
@@ -30,7 +30,6 @@ intrude import collection::array
 # High-level abstraction for all text representations
 abstract class Text
        super Comparable
-       super StringCapable
 
        redef type OTHER: Text
 
@@ -351,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
@@ -367,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
@@ -380,19 +379,125 @@ 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
 
-       # Mangle a string to be a unique string only made of alphanumeric characters
+       # 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".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
+               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`
+       #
+       # `left` is the space ratio on the left side.
+       # * 0.0 for left-justified (no space at the left)
+       # * 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   "
+       #
+       # If `length` is not enough, `self` is returned as is.
+       #
+       #     assert "hello".justify(2, 0.0)   == "hello"
+       #
+       # 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): String
+       do
+               var diff = length - self.length
+               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 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
@@ -415,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
 
@@ -422,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
@@ -429,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
@@ -438,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
@@ -467,6 +596,50 @@ abstract class Text
        #     assert "\n\"'\\\{\}".escape_to_nit      == "\\n\\\"\\'\\\\\\\{\\\}"
        fun escape_to_nit: String do return escape_more_to_c("\{\}")
 
+       # Escape to POSIX Shell (sh).
+       #
+       # Abort if the text contains a null byte.
+       #
+       #     assert "\n\"'\\\{\}0".escape_to_sh == "'\n\"'\\''\\\{\}0'"
+       fun escape_to_sh: String do
+               var b = new FlatBuffer
+               b.chars.add '\''
+               for i in [0..length[ do
+                       var c = chars[i]
+                       if c == '\'' then
+                               b.append("'\\''")
+                       else
+                               assert without_null_byte: c != '\0'
+                               b.add(c)
+                       end
+               end
+               b.chars.add '\''
+               return b.to_s
+       end
+
+       # Escape to include in a Makefile
+       #
+       # Unfortunately, some characters are not escapable in Makefile.
+       # These characters are `;`, `|`, `\`, and the non-printable ones.
+       # They will be rendered as `"?{hex}"`.
+       fun escape_to_mk: String do
+               var b = new FlatBuffer
+               for i in [0..length[ do
+                       var c = chars[i]
+                       if c == '$' then
+                               b.append("$$")
+                       else if c == ':' or c == ' ' or c == '#' then
+                               b.add('\\')
+                               b.add(c)
+                       else if c.ascii < 32 or c == ';' or c == '|' or c == '\\' or c == '=' then
+                               b.append("?{c.ascii.to_base(16, false)}")
+                       else
+                               b.add(c)
+                       end
+               end
+               return b.to_s
+       end
+
        # Return a string where Nit escape sequences are transformed.
        #
        #     var s = "\\n"
@@ -570,10 +743,12 @@ abstract class Text
                return buf.to_s
        end
 
-       # Escape the four characters `<`, `>`, `&`, and `"` with their html counterpart
+       # Escape the characters `<`, `>`, `&`, `"`, `'` and `/` as HTML/XML entity references.
+       #
+       #     assert "a&b-<>\"x\"/'".html_escape      ==  "a&amp;b-&lt;&gt;&#34;x&#34;&#47;&#39;"
        #
-       #     assert "a&b->\"x\"".html_escape      ==  "a&amp;b-&gt;&quot;x&quot;"
-       fun html_escape: SELFTYPE
+       # 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: String
        do
                var buf = new FlatBuffer
 
@@ -586,7 +761,11 @@ abstract class Text
                        else if c == '>' then
                                buf.append "&gt;"
                        else if c == '"' then
-                               buf.append "&quot;"
+                               buf.append "&#34;"
+                       else if c == '\'' then
+                               buf.append "&#39;"
+                       else if c == '/' then
+                               buf.append "&#47;"
                        else buf.add c
                end
 
@@ -685,9 +864,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
 
-       init do end
+       redef var length: Int = 0
 
        redef fun output
        do
@@ -708,12 +902,7 @@ private abstract class StringCharView
 
        type SELFTYPE: Text
 
-       private var target: SELFTYPE
-
-       private init(tgt: SELFTYPE)
-       do
-               target = tgt
-       end
+       var target: SELFTYPE
 
        redef fun is_empty do return target.is_empty
 
@@ -734,10 +923,15 @@ private abstract class BufferCharView
 
 end
 
+# A `String` holds and manipulates an arbitrary sequence of characters.
+#
+# String objects may be created using literals.
+#
+#     assert "Hello World!" isa String
 abstract class String
        super Text
 
-       redef type SELFTYPE: String
+       redef type SELFTYPE: String is fixed
 
        redef fun to_s do return self
 
@@ -754,8 +948,13 @@ abstract class String
        #     assert "abc" * 0 == ""
        fun *(i: Int): SELFTYPE is abstract
 
+       # Insert `s` at `pos`.
+       #
+       #     assert "helloworld".insert_at(" ", 5)     == "hello world"
        fun insert_at(s: String, pos: Int): SELFTYPE is abstract
 
+       redef fun substrings: Iterator[String] is abstract
+
        # Returns a reversed version of self
        #
        #     assert "hello".reversed  == "olleh"
@@ -855,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
 
@@ -886,8 +1085,6 @@ private class FlatSubstringsIter
 
        var tgt: nullable FlatText
 
-       init(tgt: FlatText) do self.tgt = tgt
-
        redef fun item do
                assert is_ok
                return tgt.as(not null)
@@ -909,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
@@ -926,7 +1123,7 @@ class FlatString
 
        redef fun reversed
        do
-               var native = calloc_string(self.length + 1)
+               var native = new NativeString(self.length + 1)
                var length = self.length
                var items = self.items
                var pos = 0
@@ -939,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
@@ -949,22 +1148,26 @@ 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)
 
        redef fun to_upper
        do
-               var outstr = calloc_string(self.length + 1)
+               var outstr = new NativeString(self.length + 1)
                var out_index = 0
 
                var myitems = self.items
@@ -984,7 +1187,7 @@ class FlatString
 
        redef fun to_lower
        do
-               var outstr = calloc_string(self.length + 1)
+               var outstr = new NativeString(self.length + 1)
                var out_index = 0
 
                var myitems = self.items
@@ -1016,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
@@ -1029,7 +1236,7 @@ class FlatString
                if real_items != null then
                        return real_items.as(not null)
                else
-                       var newItems = calloc_string(length + 1)
+                       var newItems = new NativeString(length + 1)
                        self.items.copy_to(newItems, length, index_from, 0)
                        newItems[length] = '\0'
                        self.real_items = newItems
@@ -1107,7 +1314,7 @@ class FlatString
 
                var total_length = my_length + its_length
 
-               var target_string = calloc_string(my_length + its_length + 1)
+               var target_string = new NativeString(my_length + its_length + 1)
 
                self.items.copy_to(target_string, my_length, index_from, 0)
                if s isa FlatString then
@@ -1138,7 +1345,7 @@ class FlatString
 
                var my_items = self.items
 
-               var target_string = calloc_string((final_length) + 1)
+               var target_string = new NativeString(final_length + 1)
 
                target_string[final_length] = '\0'
 
@@ -1248,10 +1455,11 @@ private class FlatStringCharView
 
 end
 
+# A mutable sequence of characters.
 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
@@ -1333,20 +1541,20 @@ 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
                self[0] = c
-               var prev: Char = c
+               var prev = c
                for i in [1 .. length[ do
                        prev = c
                        c = self[i]
@@ -1374,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`
@@ -1436,7 +1644,7 @@ class FlatBuffer
                # The COW flag can be set at false here, since
                # it does a copy of the current `Buffer`
                written = false
-               var a = calloc_string(c+1)
+               var a = new NativeString(c+1)
                if length > 0 then items.copy_to(a, length, 0, 0)
                items = a
                capacity = c
@@ -1452,7 +1660,7 @@ class FlatBuffer
        redef fun to_cstring
        do
                if is_dirty then
-                       var new_native = calloc_string(length + 1)
+                       var new_native = new NativeString(length + 1)
                        new_native[length] = '\0'
                        if length > 0 then items.copy_to(new_native, length, 0, 0)
                        real_items = new_native
@@ -1464,11 +1672,26 @@ 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
                capacity = s.length + 1
                length = s.length
-               items = calloc_string(capacity)
+               items = new NativeString(capacity)
                if s isa FlatString then
                        s.items.copy_to(items, length, s.index_from, 0)
                else if s isa FlatBuffer then
@@ -1487,8 +1710,7 @@ class FlatBuffer
        init with_capacity(cap: Int)
        do
                assert cap >= 0
-               # _items = new NativeString.calloc(cap)
-               items = calloc_string(cap+1)
+               items = new NativeString(cap+1)
                capacity = cap
                length = 0
        end
@@ -1531,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
@@ -1545,7 +1766,7 @@ class FlatBuffer
        redef fun reverse
        do
                written = false
-               var ns = calloc_string(capacity)
+               var ns = new NativeString(capacity)
                var si = length - 1
                var ni = 0
                var it = items
@@ -1616,7 +1837,6 @@ end
 
 private class FlatBufferCharView
        super BufferCharView
-       super StringCapable
 
        redef type SELFTYPE: FlatBuffer
 
@@ -1649,7 +1869,6 @@ private class FlatBufferCharView
 
        redef fun append(s)
        do
-               var my_items = target.items
                var s_length = s.length
                if target.capacity < s.length then enlarge(s_length + target.length)
        end
@@ -1699,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`.
@@ -1733,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
@@ -1764,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
@@ -1864,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
@@ -1894,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'
@@ -1905,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')
@@ -1916,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
@@ -2012,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`.
@@ -2031,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
@@ -2039,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
@@ -2052,11 +2305,22 @@ end
 
 # Native strings are simple C char *
 extern class NativeString `{ char* `}
-       super StringCapable
        # 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
+
+       # Set char `item` at index.
        fun []=(index: Int, item: Char) is intern
+
+       # Copy `self` to `dest`.
        fun copy_to(dest: NativeString, length: Int, from: Int, to: Int) is intern
 
        # Position of the first nul character.
@@ -2066,7 +2330,11 @@ extern class NativeString `{ char* `}
                while self[l] != '\0' do l += 1
                return l
        end
+
+       # Parse `self` as an Int.
        fun atoi: Int is intern
+
+       # Parse `self` as a Float.
        fun atof: Float is extern "atof"
 
        redef fun to_s
@@ -2074,30 +2342,27 @@ extern class NativeString `{ char* `}
                return to_s_with_length(cstring_length)
        end
 
+       # Returns `self` as a String of `length`.
        fun to_s_with_length(length: Int): FlatString
        do
                assert length >= 0
                var str = new FlatString.with_infos(self, length, 0, length - 1)
-               str.real_items = self
                return str
        end
 
+       # Returns `self` as a new String.
        fun to_s_with_copy: FlatString
        do
                var length = cstring_length
-               var new_self = calloc_string(length + 1)
+               var new_self = new NativeString(length + 1)
                copy_to(new_self, length, 0, 0)
                var str = new FlatString.with_infos(new_self, length, 0, length - 1)
-               str.real_items = self
+               new_self[length] = '\0'
+               str.real_items = new_self
                return str
        end
 end
 
-# StringCapable objects can create native strings
-interface StringCapable
-       protected fun calloc_string(size: Int): NativeString is intern
-end
-
 redef class Sys
        private var args_cache: nullable Sequence[String]