core: implement Float::to_precision in C without callbacks
[nit.git] / lib / core / text / abstract_text.nit
index ba667df..3f37a81 100644 (file)
@@ -789,17 +789,17 @@ abstract class Text
                if pos == null then pos = 0
                if ln == null then ln = length - pos
                if ln < 6 then return 0xFFFD.code_point
-               var cp = from_utf16_digit(pos + 2)
-               if cp < 0xD800 then return cp.code_point
-               if cp > 0xDFFF then return cp.code_point
-               if cp > 0xDBFF then return 0xFFFD.code_point
+               var cp = from_utf16_digit(pos + 2).to_u32
+               if cp < 0xD800u32 then return cp.code_point
+               if cp > 0xDFFFu32 then return cp.code_point
+               if cp > 0xDBFFu32 then return 0xFFFD.code_point
                if ln == 6 then return 0xFFFD.code_point
                if ln < 12 then return 0xFFFD.code_point
                cp <<= 16
-               cp += from_utf16_digit(pos + 8)
-               var cplo = cp & 0xFFFF
-               if cplo < 0xDC00 then return 0xFFFD.code_point
-               if cplo > 0xDFFF then return 0xFFFD.code_point
+               cp += from_utf16_digit(pos + 8).to_u32
+               var cplo = cp & 0xFFFFu32
+               if cplo < 0xDC00u32 then return 0xFFFD.code_point
+               if cplo > 0xDFFFu32 then return 0xFFFD.code_point
                return cp.from_utf16_surr.code_point
        end
 
@@ -843,15 +843,16 @@ abstract class Text
 
        # Decode `self` from percent (or URL) encoding to a clear string
        #
-       # Replace invalid use of '%' with '?'.
+       # Invalid '%' are not decoded.
        #
        #     assert "aBc09-._~".from_percent_encoding == "aBc09-._~"
        #     assert "%25%28%29%3c%20%3e".from_percent_encoding == "%()< >"
        #     assert ".com%2fpost%3fe%3dasdf%26f%3d123".from_percent_encoding == ".com/post?e=asdf&f=123"
        #     assert "%25%28%29%3C%20%3E".from_percent_encoding == "%()< >"
-       #     assert "incomplete %".from_percent_encoding == "incomplete ?"
-       #     assert "invalid % usage".from_percent_encoding == "invalid ? usage"
+       #     assert "incomplete %".from_percent_encoding == "incomplete %"
+       #     assert "invalid % usage".from_percent_encoding == "invalid % usage"
        #     assert "%c3%a9%e3%81%82%e3%81%84%e3%81%86".from_percent_encoding == "éあいう"
+       #     assert "%1 %A %C3%A9A9".from_percent_encoding == "%1 %A éA9"
        fun from_percent_encoding: String
        do
                var len = byte_length
@@ -874,7 +875,7 @@ abstract class Text
                        if c == '%' then
                                if i + 2 >= length then
                                        # What follows % has been cut off
-                                       buf[l] = '?'.ascii
+                                       buf[l] = '%'.ascii
                                else
                                        i += 1
                                        var hex_s = substring(i, 2)
@@ -884,7 +885,7 @@ abstract class Text
                                                i += 1
                                        else
                                                # What follows a % is not Hex
-                                               buf[l] = '?'.ascii
+                                               buf[l] = '%'.ascii
                                                i -= 1
                                        end
                                end
@@ -894,7 +895,7 @@ abstract class Text
                        l += 1
                end
 
-               return buf.to_s_unsafe(l)
+               return buf.to_s_unsafe(l, copy=false)
        end
 
        # Escape the characters `<`, `>`, `&`, `"`, `'` and `/` as HTML/XML entity references.
@@ -1114,7 +1115,7 @@ abstract class Text
        #
        #       var ns = new CString(8)
        #       "Text is String".copy_to_native(ns, 8, 2, 0)
-       #       assert ns.to_s_unsafe(8) == "xt is St"
+       #       assert ns.to_s_with_length(8) == "xt is St"
        #
        fun copy_to_native(dest: CString, n, src_offset, dest_offset: Int) do
                var mypos = src_offset
@@ -1798,7 +1799,7 @@ redef class Byte
                var ns = new CString(nslen + 1)
                ns[nslen] = 0u8
                native_byte_to_s(ns, nslen + 1)
-               return ns.to_s_unsafe(nslen)
+               return ns.to_s_unsafe(nslen, copy=false, clean=false)
        end
 end
 
@@ -1914,35 +1915,23 @@ redef class Float
                        return  "-inf"
                end
 
-               if decimals == 0 then return self.to_i.to_s
-               var f = self
-               for i in [0..decimals[ do f = f * 10.0
-               if self > 0.0 then
-                       f = f + 0.5
-               else
-                       f = f - 0.5
-               end
-               var i = f.to_i
-               if i == 0 then return "0." + "0"*decimals
-
-               # Prepare both parts of the float, before and after the "."
-               var s = i.abs.to_s
-               var sl = s.length
-               var p1
-               var p2
-               if sl > decimals then
-                       # Has something before the "."
-                       p1 = s.substring(0, sl-decimals)
-                       p2 = s.substring(sl-decimals, decimals)
-               else
-                       p1 = "0"
-                       p2 = "0"*(decimals-sl) + s
-               end
+               var size = to_precision_size(decimals)
+               var cstr = new CString(size+1)
+               to_precision_fill(decimals, size+1, cstr)
+               return cstr.to_s_unsafe(byte_length=size, copy=false)
+       end
 
-               if i < 0 then p1 = "-" + p1
+       # Required string length to hold `self` with `nb` decimals
+       #
+       # The length does not include the terminating null byte.
+       private fun to_precision_size(nb: Int): Int `{
+               return snprintf(NULL, 0, "%.*f", (int)nb, self);
+       `}
 
-               return p1 + "." + p2
-       end
+       # Fill `cstr` with `self` and `nb` decimals
+       private fun to_precision_fill(nb, size: Int, cstr: CString) `{
+               snprintf(cstr, size, "%.*f", (int)nb, self);
+       `}
 end
 
 redef class Char
@@ -1987,7 +1976,7 @@ redef class Char
                var ln = u8char_len
                var ns = new CString(ln + 1)
                u8char_tos(ns, ln)
-               return ns.to_s_unsafe(ln)
+               return ns.to_s_unsafe(ln, copy=false, clean=false)
        end
 
        # Returns `self` escaped to UTF-16
@@ -2295,48 +2284,39 @@ do
 end
 
 redef class CString
-       # Get a `String` from the data at `self` copied into Nit memory
-       #
-       # Require: `self` is a null-terminated string.
-       fun to_s_with_copy: String is abstract
 
-       # Get a `String` from `length` bytes at `self`
+       # Get a `String` from the data at `self` (with unsafe options)
        #
-       # The result may point to the data at `self` or
-       # it may make a copy in Nit controlled memory.
-       # This method should only be used when `self` was allocated by the Nit GC,
-       # or when manually controlling the deallocation of `self`.
-       fun to_s_with_length(length: Int): String is abstract
-
-       # Get a `String` from the raw `length` bytes at `self`
+       # The default behavior is the safest and equivalent to `to_s`.
        #
-       # The default value of `length` is the number of bytes before
-       # the first null character.
+       # Options:
        #
-       # The created `String` points to the data at `self`.
-       # This method should be used when `self` was allocated by the Nit GC,
-       # or when manually controlling the deallocation of `self`.
+       # * Set `byte_length` to the number of bytes to use as data.
+       #   Otherwise, this method searches for a terminating null byte.
        #
-       # /!\: This service does not clean the items for compliance with UTF-8,
-       # use only when the data has already been verified as valid UTF-8.
-       fun to_s_unsafe(length: nullable Int): String is abstract
-
-       # Get a `String` from the raw `byte_length` bytes at `self` with `unilen` Unicode characters
+       # * Set `char_length` to the number of Unicode character in the string.
+       #   Otherwise, the data is read to count the characters.
+       #   Ignored if `clean == true`.
        #
-       # The created `String` points to the data at `self`.
-       # This method should be used when `self` was allocated by the Nit GC,
-       # or when manually controlling the deallocation of `self`.
+       # * If `copy == true`, the default, copies the data at `self` in the
+       #   Nit GC allocated memory. Otherwise, the return may still point to
+       #   the data at `self`.
        #
-       # /!\: This service does not clean the items for compliance with UTF-8,
-       # use only when the data has already been verified as valid UTF-8.
+       # * If `clean == true`, the default, the string is cleaned of invalid UTF-8
+       #   characters. If cleaning is necessary, the data is copied into Nit GC
+       #   managed memory, whether or not `copy == true`.
+       #   Don't clean only when the data has already been verified as valid UTF-8,
+       #   other library services rely on UTF-8 compliant characters.
+       fun to_s_unsafe(byte_length, char_length: nullable Int, copy, clean: nullable Bool): String is abstract
+
+       # Retro-compatibility service use by execution engines
        #
-       # SEE: `abstract_text::Text` for more info on the difference
-       # between `Text::byte_length` and `Text::length`.
-       fun to_s_full(byte_length, unilen: Int): String is abstract
+       # TODO remove this method at the next c_src regen.
+       private fun to_s_full(byte_length, char_length: Int): String do return to_s_unsafe(byte_length, char_length, false, false)
 
        # Copies the content of `src` to `self`
        #
-       # NOTE: `self` must be large enough to withold `self.byte_length` bytes
+       # NOTE: `self` must be large enough to contain `self.byte_length` bytes
        fun fill_from(src: Text) do src.copy_to_native(self, src.byte_length, 0, 0)
 end