mirror of
				https://github.com/RetroDECK/Duckstation.git
				synced 2025-04-10 19:15:14 +00:00 
			
		
		
		
	
		
			
	
	
		
			2429 lines
		
	
	
		
			90 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
		
		
			
		
	
	
			2429 lines
		
	
	
		
			90 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
|   | #ifndef _C4_CHARCONV_HPP_
 | ||
|  | #define _C4_CHARCONV_HPP_
 | ||
|  | 
 | ||
|  | /** @file charconv.hpp Lightweight generic type-safe wrappers for
 | ||
|  |  * converting individual values to/from strings. | ||
|  |  * | ||
|  |  * These are the main functions: | ||
|  |  * | ||
|  |  * @code{.cpp} | ||
|  |  * // Convert the given value, writing into the string.
 | ||
|  |  * // The resulting string will NOT be null-terminated.
 | ||
|  |  * // Return the number of characters needed.
 | ||
|  |  * // This function is safe to call when the string is too small -
 | ||
|  |  * // no writes will occur beyond the string's last character.
 | ||
|  |  * template<class T> size_t c4::to_chars(substr buf, T const& C4_RESTRICT val); | ||
|  |  * | ||
|  |  * | ||
|  |  * // Convert the given value to a string using to_chars(), and
 | ||
|  |  * // return the resulting string, up to and including the last
 | ||
|  |  * // written character.
 | ||
|  |  * template<class T> substr c4::to_chars_sub(substr buf, T const& C4_RESTRICT val); | ||
|  |  * | ||
|  |  * | ||
|  |  * // Read a value from the string, which must be
 | ||
|  |  * // trimmed to the value (ie, no leading/trailing whitespace).
 | ||
|  |  * // return true if the conversion succeeded.
 | ||
|  |  * // There is no check for overflow; the value wraps around in a way similar
 | ||
|  |  * // to the standard C/C++ overflow behavior. For example,
 | ||
|  |  * // from_chars<int8_t>("128", &val) returns true and val will be
 | ||
|  |  * // set tot 0.
 | ||
|  |  * template<class T> bool c4::from_chars(csubstr buf, T * C4_RESTRICT val); | ||
|  |  * | ||
|  |  * | ||
|  |  * // Read the first valid sequence of characters from the string,
 | ||
|  |  * // skipping leading whitespace, and convert it using from_chars().
 | ||
|  |  * // Return the number of characters read for converting.
 | ||
|  |  * template<class T> size_t c4::from_chars_first(csubstr buf, T * C4_RESTRICT val); | ||
|  |  * @endcode | ||
|  |  */ | ||
|  | 
 | ||
|  | #include "c4/language.hpp"
 | ||
|  | #include <inttypes.h>
 | ||
|  | #include <type_traits>
 | ||
|  | #include <climits>
 | ||
|  | #include <limits>
 | ||
|  | #include <utility>
 | ||
|  | 
 | ||
|  | #include "c4/config.hpp"
 | ||
|  | #include "c4/substr.hpp"
 | ||
|  | #include "c4/std/std_fwd.hpp"
 | ||
|  | #include "c4/memory_util.hpp"
 | ||
|  | #include "c4/szconv.hpp"
 | ||
|  | 
 | ||
|  | #ifndef C4CORE_NO_FAST_FLOAT
 | ||
|  | #   if (C4_CPP >= 17)
 | ||
|  | #       if defined(_MSC_VER)
 | ||
|  | #           if (C4_MSVC_VERSION >= C4_MSVC_VERSION_2019) // VS2017 and lower do not have these macros
 | ||
|  | #               include <charconv>
 | ||
|  | #               define C4CORE_HAVE_STD_TOCHARS 1
 | ||
|  | #               define C4CORE_HAVE_STD_FROMCHARS 0 // prefer fast_float with MSVC
 | ||
|  | #               define C4CORE_HAVE_FAST_FLOAT 1
 | ||
|  | #           else
 | ||
|  | #               define C4CORE_HAVE_STD_TOCHARS 0
 | ||
|  | #               define C4CORE_HAVE_STD_FROMCHARS 0
 | ||
|  | #               define C4CORE_HAVE_FAST_FLOAT 1
 | ||
|  | #           endif
 | ||
|  | #       else
 | ||
|  | #           if __has_include(<charconv>)
 | ||
|  | #               include <charconv>
 | ||
|  | #               if defined(__cpp_lib_to_chars)
 | ||
|  | #                   define C4CORE_HAVE_STD_TOCHARS 1
 | ||
|  | #                   define C4CORE_HAVE_STD_FROMCHARS 0 // glibc uses fast_float internally
 | ||
|  | #                   define C4CORE_HAVE_FAST_FLOAT 1
 | ||
|  | #               else
 | ||
|  | #                   define C4CORE_HAVE_STD_TOCHARS 0
 | ||
|  | #                   define C4CORE_HAVE_STD_FROMCHARS 0
 | ||
|  | #                   define C4CORE_HAVE_FAST_FLOAT 1
 | ||
|  | #               endif
 | ||
|  | #           else
 | ||
|  | #               define C4CORE_HAVE_STD_TOCHARS 0
 | ||
|  | #               define C4CORE_HAVE_STD_FROMCHARS 0
 | ||
|  | #               define C4CORE_HAVE_FAST_FLOAT 1
 | ||
|  | #           endif
 | ||
|  | #       endif
 | ||
|  | #   else
 | ||
|  | #       define C4CORE_HAVE_STD_TOCHARS 0
 | ||
|  | #       define C4CORE_HAVE_STD_FROMCHARS 0
 | ||
|  | #       define C4CORE_HAVE_FAST_FLOAT 1
 | ||
|  | #   endif
 | ||
|  | #   if C4CORE_HAVE_FAST_FLOAT
 | ||
|  |         C4_SUPPRESS_WARNING_GCC_WITH_PUSH("-Wsign-conversion") | ||
|  |         C4_SUPPRESS_WARNING_GCC("-Warray-bounds") | ||
|  | #       if defined(__GNUC__) && __GNUC__ >= 5
 | ||
|  |             C4_SUPPRESS_WARNING_GCC("-Wshift-count-overflow") | ||
|  | #       endif
 | ||
|  | //#       include "c4/ext/fast_float.hpp"
 | ||
|  | #include "fast_float/fast_float.h"
 | ||
|  |         C4_SUPPRESS_WARNING_GCC_POP | ||
|  | #   endif
 | ||
|  | #elif (C4_CPP >= 17)
 | ||
|  | #   define C4CORE_HAVE_FAST_FLOAT 0
 | ||
|  | #   if defined(_MSC_VER)
 | ||
|  | #       if (C4_MSVC_VERSION >= C4_MSVC_VERSION_2019) // VS2017 and lower do not have these macros
 | ||
|  | #           include <charconv>
 | ||
|  | #           define C4CORE_HAVE_STD_TOCHARS 1
 | ||
|  | #           define C4CORE_HAVE_STD_FROMCHARS 1
 | ||
|  | #       else
 | ||
|  | #           define C4CORE_HAVE_STD_TOCHARS 0
 | ||
|  | #           define C4CORE_HAVE_STD_FROMCHARS 0
 | ||
|  | #       endif
 | ||
|  | #   else
 | ||
|  | #       if __has_include(<charconv>)
 | ||
|  | #           include <charconv>
 | ||
|  | #           if defined(__cpp_lib_to_chars)
 | ||
|  | #               define C4CORE_HAVE_STD_TOCHARS 1
 | ||
|  | #               define C4CORE_HAVE_STD_FROMCHARS 1 // glibc uses fast_float internally
 | ||
|  | #           else
 | ||
|  | #               define C4CORE_HAVE_STD_TOCHARS 0
 | ||
|  | #               define C4CORE_HAVE_STD_FROMCHARS 0
 | ||
|  | #           endif
 | ||
|  | #       else
 | ||
|  | #           define C4CORE_HAVE_STD_TOCHARS 0
 | ||
|  | #           define C4CORE_HAVE_STD_FROMCHARS 0
 | ||
|  | #       endif
 | ||
|  | #   endif
 | ||
|  | #else
 | ||
|  | #   define C4CORE_HAVE_STD_TOCHARS 0
 | ||
|  | #   define C4CORE_HAVE_STD_FROMCHARS 0
 | ||
|  | #   define C4CORE_HAVE_FAST_FLOAT 0
 | ||
|  | #endif
 | ||
|  | 
 | ||
|  | 
 | ||
|  | #if !C4CORE_HAVE_STD_FROMCHARS
 | ||
|  | #include <cstdio>
 | ||
|  | #endif
 | ||
|  | 
 | ||
|  | 
 | ||
|  | #if defined(_MSC_VER)
 | ||
|  | #   pragma warning(push)
 | ||
|  | #   pragma warning(disable: 4996) // snprintf/scanf: this function or variable may be unsafe
 | ||
|  | #   if C4_MSVC_VERSION != C4_MSVC_VERSION_2017
 | ||
|  | #       pragma warning(disable: 4800) //'int': forcing value to bool 'true' or 'false' (performance warning)
 | ||
|  | #   endif
 | ||
|  | #endif
 | ||
|  | 
 | ||
|  | #if defined(__clang__)
 | ||
|  | #   pragma clang diagnostic push
 | ||
|  | #   pragma clang diagnostic ignored "-Wtautological-constant-out-of-range-compare"
 | ||
|  | #   pragma clang diagnostic ignored "-Wformat-nonliteral"
 | ||
|  | #   pragma clang diagnostic ignored "-Wdouble-promotion" // implicit conversion increases floating-point precision
 | ||
|  | #   pragma clang diagnostic ignored "-Wold-style-cast"
 | ||
|  | #elif defined(__GNUC__)
 | ||
|  | #   pragma GCC diagnostic push
 | ||
|  | #   pragma GCC diagnostic ignored "-Wformat-nonliteral"
 | ||
|  | #   pragma GCC diagnostic ignored "-Wdouble-promotion" // implicit conversion increases floating-point precision
 | ||
|  | #   pragma GCC diagnostic ignored "-Wuseless-cast"
 | ||
|  | #   pragma GCC diagnostic ignored "-Wold-style-cast"
 | ||
|  | #endif
 | ||
|  | 
 | ||
|  | 
 | ||
|  | namespace c4 { | ||
|  | 
 | ||
|  | #if C4CORE_HAVE_STD_TOCHARS
 | ||
|  | /** @warning Use only the symbol. Do not rely on the type or naked value of this enum. */ | ||
|  | typedef enum : std::underlying_type<std::chars_format>::type { | ||
|  |     /** print the real number in floating point format (like %f) */ | ||
|  |     FTOA_FLOAT = static_cast<std::underlying_type<std::chars_format>::type>(std::chars_format::fixed), | ||
|  |     /** print the real number in scientific format (like %e) */ | ||
|  |     FTOA_SCIENT = static_cast<std::underlying_type<std::chars_format>::type>(std::chars_format::scientific), | ||
|  |     /** print the real number in flexible format (like %g) */ | ||
|  |     FTOA_FLEX = static_cast<std::underlying_type<std::chars_format>::type>(std::chars_format::general), | ||
|  |     /** print the real number in hexadecimal format (like %a) */ | ||
|  |     FTOA_HEXA = static_cast<std::underlying_type<std::chars_format>::type>(std::chars_format::hex), | ||
|  | } RealFormat_e; | ||
|  | #else
 | ||
|  | /** @warning Use only the symbol. Do not rely on the type or naked value of this enum. */ | ||
|  | typedef enum : char { | ||
|  |     /** print the real number in floating point format (like %f) */ | ||
|  |     FTOA_FLOAT = 'f', | ||
|  |     /** print the real number in scientific format (like %e) */ | ||
|  |     FTOA_SCIENT = 'e', | ||
|  |     /** print the real number in flexible format (like %g) */ | ||
|  |     FTOA_FLEX = 'g', | ||
|  |     /** print the real number in hexadecimal format (like %a) */ | ||
|  |     FTOA_HEXA = 'a', | ||
|  | } RealFormat_e; | ||
|  | #endif
 | ||
|  | 
 | ||
|  | 
 | ||
|  | /** in some platforms, int,unsigned int
 | ||
|  |  *  are not any of int8_t...int64_t and | ||
|  |  *  long,unsigned long are not any of uint8_t...uint64_t */ | ||
|  | template<class T> | ||
|  | struct is_fixed_length | ||
|  | { | ||
|  |     enum : bool { | ||
|  |         /** true if T is one of the fixed length signed types */ | ||
|  |         value_i = (std::is_integral<T>::value | ||
|  |                    && (std::is_same<T, int8_t>::value | ||
|  |                        || std::is_same<T, int16_t>::value | ||
|  |                        || std::is_same<T, int32_t>::value | ||
|  |                        || std::is_same<T, int64_t>::value)), | ||
|  |         /** true if T is one of the fixed length unsigned types */ | ||
|  |         value_u = (std::is_integral<T>::value | ||
|  |                    && (std::is_same<T, uint8_t>::value | ||
|  |                        || std::is_same<T, uint16_t>::value | ||
|  |                        || std::is_same<T, uint32_t>::value | ||
|  |                        || std::is_same<T, uint64_t>::value)), | ||
|  |         /** true if T is one of the fixed length signed or unsigned types */ | ||
|  |         value = value_i || value_u | ||
|  |     }; | ||
|  | }; | ||
|  | 
 | ||
|  | 
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | 
 | ||
|  | #ifdef _MSC_VER
 | ||
|  | #   pragma warning(push)
 | ||
|  | #elif defined(__clang__)
 | ||
|  | #   pragma clang diagnostic push
 | ||
|  | #elif defined(__GNUC__)
 | ||
|  | #   pragma GCC diagnostic push
 | ||
|  | #   pragma GCC diagnostic ignored "-Wconversion"
 | ||
|  | #   if __GNUC__ >= 6
 | ||
|  | #       pragma GCC diagnostic ignored "-Wnull-dereference"
 | ||
|  | #   endif
 | ||
|  | #endif
 | ||
|  | 
 | ||
|  | namespace detail { | ||
|  | 
 | ||
|  | /* python command to get the values below:
 | ||
|  | def dec(v): | ||
|  |     return str(v) | ||
|  | for bits in (8, 16, 32, 64): | ||
|  |     imin, imax, umax = (-(1 << (bits - 1))), (1 << (bits - 1)) - 1, (1 << bits) - 1 | ||
|  |     for vname, v in (("imin", imin), ("imax", imax), ("umax", umax)): | ||
|  |         for f in (bin, oct, dec, hex): | ||
|  |             print(f"{bits}b: {vname}={v} {f.__name__}: len={len(f(v)):2d}: {v} {f(v)}") | ||
|  | */ | ||
|  | 
 | ||
|  | // do not use the type as the template argument because in some
 | ||
|  | // platforms long!=int32 and long!=int64. Just use the numbytes
 | ||
|  | // which is more generic and spares lengthy SFINAE code.
 | ||
|  | template<size_t num_bytes, bool is_signed> struct charconv_digits_; | ||
|  | template<class T> using charconv_digits = charconv_digits_<sizeof(T), std::is_signed<T>::value>; | ||
|  | 
 | ||
|  | template<> struct charconv_digits_<1u, true> // int8_t
 | ||
|  | { | ||
|  |     enum : size_t { | ||
|  |         maxdigits_bin       = 1 + 2 + 8, // -128==-0b10000000
 | ||
|  |         maxdigits_oct       = 1 + 2 + 3, // -128==-0o200
 | ||
|  |         maxdigits_dec       = 1     + 3, // -128
 | ||
|  |         maxdigits_hex       = 1 + 2 + 2, // -128==-0x80
 | ||
|  |         maxdigits_bin_nopfx =         8, // -128==-0b10000000
 | ||
|  |         maxdigits_oct_nopfx =         3, // -128==-0o200
 | ||
|  |         maxdigits_dec_nopfx =         3, // -128
 | ||
|  |         maxdigits_hex_nopfx =         2, // -128==-0x80
 | ||
|  |     }; | ||
|  |     // min values without sign!
 | ||
|  |     static constexpr csubstr min_value_dec() noexcept { return csubstr("128"); } | ||
|  |     static constexpr csubstr min_value_hex() noexcept { return csubstr("80"); } | ||
|  |     static constexpr csubstr min_value_oct() noexcept { return csubstr("200"); } | ||
|  |     static constexpr csubstr min_value_bin() noexcept { return csubstr("10000000"); } | ||
|  |     static constexpr csubstr max_value_dec() noexcept { return csubstr("127"); } | ||
|  |     static constexpr bool    is_oct_overflow(csubstr str) noexcept { return !((str.len < 3) || (str.len == 3 && str[0] <= '1')); } | ||
|  | }; | ||
|  | template<> struct charconv_digits_<1u, false> // uint8_t
 | ||
|  | { | ||
|  |     enum : size_t { | ||
|  |         maxdigits_bin       = 2 + 8, // 255 0b11111111
 | ||
|  |         maxdigits_oct       = 2 + 3, // 255 0o377
 | ||
|  |         maxdigits_dec       =     3, // 255
 | ||
|  |         maxdigits_hex       = 2 + 2, // 255 0xff
 | ||
|  |         maxdigits_bin_nopfx =     8, // 255 0b11111111
 | ||
|  |         maxdigits_oct_nopfx =     3, // 255 0o377
 | ||
|  |         maxdigits_dec_nopfx =     3, // 255
 | ||
|  |         maxdigits_hex_nopfx =     2, // 255 0xff
 | ||
|  |     }; | ||
|  |     static constexpr csubstr max_value_dec() noexcept { return csubstr("255"); } | ||
|  |     static constexpr bool    is_oct_overflow(csubstr str) noexcept { return !((str.len < 3) || (str.len == 3 && str[0] <= '3')); } | ||
|  | }; | ||
|  | template<> struct charconv_digits_<2u, true> // int16_t
 | ||
|  | { | ||
|  |     enum : size_t { | ||
|  |         maxdigits_bin       = 1 + 2 + 16, // -32768 -0b1000000000000000
 | ||
|  |         maxdigits_oct       = 1 + 2 +  6, // -32768 -0o100000
 | ||
|  |         maxdigits_dec       = 1     +  5, // -32768 -32768
 | ||
|  |         maxdigits_hex       = 1 + 2 +  4, // -32768 -0x8000
 | ||
|  |         maxdigits_bin_nopfx =         16, // -32768 -0b1000000000000000
 | ||
|  |         maxdigits_oct_nopfx =          6, // -32768 -0o100000
 | ||
|  |         maxdigits_dec_nopfx =          5, // -32768 -32768
 | ||
|  |         maxdigits_hex_nopfx =          4, // -32768 -0x8000
 | ||
|  |     }; | ||
|  |     // min values without sign!
 | ||
|  |     static constexpr csubstr min_value_dec() noexcept { return csubstr("32768"); } | ||
|  |     static constexpr csubstr min_value_hex() noexcept { return csubstr("8000"); } | ||
|  |     static constexpr csubstr min_value_oct() noexcept { return csubstr("100000"); } | ||
|  |     static constexpr csubstr min_value_bin() noexcept { return csubstr("1000000000000000"); } | ||
|  |     static constexpr csubstr max_value_dec() noexcept { return csubstr("32767"); } | ||
|  |     static constexpr bool    is_oct_overflow(csubstr str) noexcept { return !((str.len < 6)); } | ||
|  | }; | ||
|  | template<> struct charconv_digits_<2u, false> // uint16_t
 | ||
|  | { | ||
|  |     enum : size_t { | ||
|  |         maxdigits_bin       = 2 + 16, // 65535 0b1111111111111111
 | ||
|  |         maxdigits_oct       = 2 +  6, // 65535 0o177777
 | ||
|  |         maxdigits_dec       =      6, // 65535 65535
 | ||
|  |         maxdigits_hex       = 2 +  4, // 65535 0xffff
 | ||
|  |         maxdigits_bin_nopfx =     16, // 65535 0b1111111111111111
 | ||
|  |         maxdigits_oct_nopfx =      6, // 65535 0o177777
 | ||
|  |         maxdigits_dec_nopfx =      6, // 65535 65535
 | ||
|  |         maxdigits_hex_nopfx =      4, // 65535 0xffff
 | ||
|  |     }; | ||
|  |     static constexpr csubstr max_value_dec() noexcept { return csubstr("65535"); } | ||
|  |     static constexpr bool    is_oct_overflow(csubstr str) noexcept { return !((str.len < 6) || (str.len == 6 && str[0] <= '1')); } | ||
|  | }; | ||
|  | template<> struct charconv_digits_<4u, true> // int32_t
 | ||
|  | { | ||
|  |     enum : size_t { | ||
|  |         maxdigits_bin       = 1 + 2 + 32, // len=35: -2147483648 -0b10000000000000000000000000000000
 | ||
|  |         maxdigits_oct       = 1 + 2 + 11, // len=14: -2147483648 -0o20000000000
 | ||
|  |         maxdigits_dec       = 1     + 10, // len=11: -2147483648 -2147483648
 | ||
|  |         maxdigits_hex       = 1 + 2 +  8, // len=11: -2147483648 -0x80000000
 | ||
|  |         maxdigits_bin_nopfx =         32, // len=35: -2147483648 -0b10000000000000000000000000000000
 | ||
|  |         maxdigits_oct_nopfx =         11, // len=14: -2147483648 -0o20000000000
 | ||
|  |         maxdigits_dec_nopfx =         10, // len=11: -2147483648 -2147483648
 | ||
|  |         maxdigits_hex_nopfx =          8, // len=11: -2147483648 -0x80000000
 | ||
|  |     }; | ||
|  |     // min values without sign!
 | ||
|  |     static constexpr csubstr min_value_dec() noexcept { return csubstr("2147483648"); } | ||
|  |     static constexpr csubstr min_value_hex() noexcept { return csubstr("80000000"); } | ||
|  |     static constexpr csubstr min_value_oct() noexcept { return csubstr("20000000000"); } | ||
|  |     static constexpr csubstr min_value_bin() noexcept { return csubstr("10000000000000000000000000000000"); } | ||
|  |     static constexpr csubstr max_value_dec() noexcept { return csubstr("2147483647"); } | ||
|  |     static constexpr bool    is_oct_overflow(csubstr str) noexcept { return !((str.len < 11) || (str.len == 11 && str[0] <= '1')); } | ||
|  | }; | ||
|  | template<> struct charconv_digits_<4u, false> // uint32_t
 | ||
|  | { | ||
|  |     enum : size_t { | ||
|  |         maxdigits_bin       = 2 + 32, // len=34: 4294967295 0b11111111111111111111111111111111
 | ||
|  |         maxdigits_oct       = 2 + 11, // len=13: 4294967295 0o37777777777
 | ||
|  |         maxdigits_dec       =     10, // len=10: 4294967295 4294967295
 | ||
|  |         maxdigits_hex       = 2 +  8, // len=10: 4294967295 0xffffffff
 | ||
|  |         maxdigits_bin_nopfx =     32, // len=34: 4294967295 0b11111111111111111111111111111111
 | ||
|  |         maxdigits_oct_nopfx =     11, // len=13: 4294967295 0o37777777777
 | ||
|  |         maxdigits_dec_nopfx =     10, // len=10: 4294967295 4294967295
 | ||
|  |         maxdigits_hex_nopfx =      8, // len=10: 4294967295 0xffffffff
 | ||
|  |     }; | ||
|  |     static constexpr csubstr max_value_dec() noexcept { return csubstr("4294967295"); } | ||
|  |     static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 11) || (str.len == 11 && str[0] <= '3')); } | ||
|  | }; | ||
|  | template<> struct charconv_digits_<8u, true> // int32_t
 | ||
|  | { | ||
|  |     enum : size_t { | ||
|  |         maxdigits_bin       = 1 + 2 + 64, // len=67: -9223372036854775808 -0b1000000000000000000000000000000000000000000000000000000000000000
 | ||
|  |         maxdigits_oct       = 1 + 2 + 22, // len=25: -9223372036854775808 -0o1000000000000000000000
 | ||
|  |         maxdigits_dec       = 1     + 19, // len=20: -9223372036854775808 -9223372036854775808
 | ||
|  |         maxdigits_hex       = 1 + 2 + 16, // len=19: -9223372036854775808 -0x8000000000000000
 | ||
|  |         maxdigits_bin_nopfx =         64, // len=67: -9223372036854775808 -0b1000000000000000000000000000000000000000000000000000000000000000
 | ||
|  |         maxdigits_oct_nopfx =         22, // len=25: -9223372036854775808 -0o1000000000000000000000
 | ||
|  |         maxdigits_dec_nopfx =         19, // len=20: -9223372036854775808 -9223372036854775808
 | ||
|  |         maxdigits_hex_nopfx =         16, // len=19: -9223372036854775808 -0x8000000000000000
 | ||
|  |     }; | ||
|  |     static constexpr csubstr min_value_dec() noexcept { return csubstr("9223372036854775808"); } | ||
|  |     static constexpr csubstr min_value_hex() noexcept { return csubstr("8000000000000000"); } | ||
|  |     static constexpr csubstr min_value_oct() noexcept { return csubstr("1000000000000000000000"); } | ||
|  |     static constexpr csubstr min_value_bin() noexcept { return csubstr("1000000000000000000000000000000000000000000000000000000000000000"); } | ||
|  |     static constexpr csubstr max_value_dec() noexcept { return csubstr("9223372036854775807"); } | ||
|  |     static constexpr bool    is_oct_overflow(csubstr str) noexcept { return !((str.len < 22)); } | ||
|  | }; | ||
|  | template<> struct charconv_digits_<8u, false> | ||
|  | { | ||
|  |     enum : size_t { | ||
|  |         maxdigits_bin       = 2 + 64, // len=66: 18446744073709551615 0b1111111111111111111111111111111111111111111111111111111111111111
 | ||
|  |         maxdigits_oct       = 2 + 22, // len=24: 18446744073709551615 0o1777777777777777777777
 | ||
|  |         maxdigits_dec       =     20, // len=20: 18446744073709551615 18446744073709551615
 | ||
|  |         maxdigits_hex       = 2 + 16, // len=18: 18446744073709551615 0xffffffffffffffff
 | ||
|  |         maxdigits_bin_nopfx =     64, // len=66: 18446744073709551615 0b1111111111111111111111111111111111111111111111111111111111111111
 | ||
|  |         maxdigits_oct_nopfx =     22, // len=24: 18446744073709551615 0o1777777777777777777777
 | ||
|  |         maxdigits_dec_nopfx =     20, // len=20: 18446744073709551615 18446744073709551615
 | ||
|  |         maxdigits_hex_nopfx =     16, // len=18: 18446744073709551615 0xffffffffffffffff
 | ||
|  |     }; | ||
|  |     static constexpr csubstr max_value_dec() noexcept { return csubstr("18446744073709551615"); } | ||
|  |     static constexpr bool    is_oct_overflow(csubstr str) noexcept { return !((str.len < 22) || (str.len == 22 && str[0] <= '1')); } | ||
|  | }; | ||
|  | } // namespace detail
 | ||
|  | 
 | ||
|  | 
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | 
 | ||
|  | // Helper macros, undefined below
 | ||
|  | #define _c4append(c) { if(C4_LIKELY(pos < buf.len)) { buf.str[pos++] = static_cast<char>(c); } else { ++pos; } }
 | ||
|  | #define _c4appendhex(i) { if(C4_LIKELY(pos < buf.len)) { buf.str[pos++] = hexchars[i]; } else { ++pos; } }
 | ||
|  | 
 | ||
|  | /** @name digits_dec return the number of digits required to encode a
 | ||
|  |  * decimal number. | ||
|  |  * | ||
|  |  * @note At first sight this code may look heavily branchy and | ||
|  |  * therefore inefficient. However, measurements revealed this to be | ||
|  |  * the fastest among the alternatives. | ||
|  |  * | ||
|  |  * @see https://github.com/biojppm/c4core/pull/77 */
 | ||
|  | /** @{ */ | ||
|  | 
 | ||
|  | template<class T> | ||
|  | C4_CONSTEXPR14 C4_ALWAYS_INLINE | ||
|  | auto digits_dec(T v) noexcept | ||
|  |     -> typename std::enable_if<sizeof(T) == 1u, unsigned>::type | ||
|  | { | ||
|  |     C4_STATIC_ASSERT(std::is_integral<T>::value); | ||
|  |     C4_ASSERT(v >= 0); | ||
|  |     return ((v >= 100) ? 3u : ((v >= 10) ? 2u : 1u)); | ||
|  | } | ||
|  | 
 | ||
|  | template<class T> | ||
|  | C4_CONSTEXPR14 C4_ALWAYS_INLINE | ||
|  | auto digits_dec(T v) noexcept | ||
|  |     -> typename std::enable_if<sizeof(T) == 2u, unsigned>::type | ||
|  | { | ||
|  |     C4_STATIC_ASSERT(std::is_integral<T>::value); | ||
|  |     C4_ASSERT(v >= 0); | ||
|  |     return ((v >= 10000) ? 5u : (v >= 1000) ? 4u : (v >= 100) ? 3u : (v >= 10) ? 2u : 1u); | ||
|  | } | ||
|  | 
 | ||
|  | template<class T> | ||
|  | C4_CONSTEXPR14 C4_ALWAYS_INLINE | ||
|  | auto digits_dec(T v) noexcept | ||
|  |     -> typename std::enable_if<sizeof(T) == 4u, unsigned>::type | ||
|  | { | ||
|  |     C4_STATIC_ASSERT(std::is_integral<T>::value); | ||
|  |     C4_ASSERT(v >= 0); | ||
|  |     return ((v >= 1000000000) ? 10u : (v >= 100000000) ? 9u : (v >= 10000000) ? 8u : | ||
|  |             (v >= 1000000) ? 7u : (v >= 100000) ? 6u : (v >= 10000) ? 5u : | ||
|  |             (v >= 1000) ? 4u : (v >= 100) ? 3u : (v >= 10) ? 2u : 1u); | ||
|  | } | ||
|  | 
 | ||
|  | template<class T> | ||
|  | C4_CONSTEXPR14 C4_ALWAYS_INLINE | ||
|  | auto digits_dec(T v) noexcept | ||
|  |     -> typename std::enable_if<sizeof(T) == 8u, unsigned>::type | ||
|  | { | ||
|  |     // thanks @fargies!!!
 | ||
|  |     // https://github.com/biojppm/c4core/pull/77#issuecomment-1063753568
 | ||
|  |     C4_STATIC_ASSERT(std::is_integral<T>::value); | ||
|  |     C4_ASSERT(v >= 0); | ||
|  |     if(v >= 1000000000) // 10
 | ||
|  |     { | ||
|  |         if(v >= 100000000000000) // 15 [15-20] range
 | ||
|  |         { | ||
|  |             if(v >= 100000000000000000) // 18 (15 + (20 - 15) / 2)
 | ||
|  |             { | ||
|  |                 if((typename std::make_unsigned<T>::type)v >= 10000000000000000000u) // 20
 | ||
|  |                     return 20u; | ||
|  |                 else | ||
|  |                     return (v >= 1000000000000000000) ? 19u : 18u; | ||
|  |             } | ||
|  |             else if(v >= 10000000000000000) // 17
 | ||
|  |                 return 17u; | ||
|  |             else | ||
|  |                 return(v >= 1000000000000000) ? 16u : 15u; | ||
|  |         } | ||
|  |         else if(v >= 1000000000000) // 13
 | ||
|  |             return (v >= 10000000000000) ? 14u : 13u; | ||
|  |         else if(v >= 100000000000) // 12
 | ||
|  |             return 12; | ||
|  |         else | ||
|  |             return(v >= 10000000000) ? 11u : 10u; | ||
|  |     } | ||
|  |     else if(v >= 10000) // 5 [5-9] range
 | ||
|  |     { | ||
|  |         if(v >= 10000000) // 8
 | ||
|  |             return (v >= 100000000) ? 9u : 8u; | ||
|  |         else if(v >= 1000000) // 7
 | ||
|  |             return 7; | ||
|  |         else | ||
|  |             return (v >= 100000) ? 6u : 5u; | ||
|  |     } | ||
|  |     else if(v >= 100) | ||
|  |         return (v >= 1000) ? 4u : 3u; | ||
|  |     else | ||
|  |         return (v >= 10) ? 2u : 1u; | ||
|  | } | ||
|  | 
 | ||
|  | /** @} */ | ||
|  | 
 | ||
|  | 
 | ||
|  | template<class T> | ||
|  | C4_CONSTEXPR14 C4_ALWAYS_INLINE unsigned digits_hex(T v) noexcept | ||
|  | { | ||
|  |     C4_STATIC_ASSERT(std::is_integral<T>::value); | ||
|  |     C4_ASSERT(v >= 0); | ||
|  |     return v ? 1u + (msb((typename std::make_unsigned<T>::type)v) >> 2u) : 1u; | ||
|  | } | ||
|  | 
 | ||
|  | template<class T> | ||
|  | C4_CONSTEXPR14 C4_ALWAYS_INLINE unsigned digits_bin(T v) noexcept | ||
|  | { | ||
|  |     C4_STATIC_ASSERT(std::is_integral<T>::value); | ||
|  |     C4_ASSERT(v >= 0); | ||
|  |     return v ? 1u + msb((typename std::make_unsigned<T>::type)v) : 1u; | ||
|  | } | ||
|  | 
 | ||
|  | template<class T> | ||
|  | C4_CONSTEXPR14 C4_ALWAYS_INLINE unsigned digits_oct(T v_) noexcept | ||
|  | { | ||
|  |     // TODO: is there a better way?
 | ||
|  |     C4_STATIC_ASSERT(std::is_integral<T>::value); | ||
|  |     C4_ASSERT(v_ >= 0); | ||
|  |     using U = typename | ||
|  |         std::conditional<sizeof(T) <= sizeof(unsigned), | ||
|  |                          unsigned, | ||
|  |                          typename std::make_unsigned<T>::type>::type; | ||
|  |     U v = (U) v_;  // safe because we require v_ >= 0
 | ||
|  |     unsigned __n = 1; | ||
|  |     const unsigned __b2 = 64u; | ||
|  |     const unsigned __b3 = __b2 * 8u; | ||
|  |     const unsigned long __b4 = __b3 * 8u; | ||
|  |     while(true) | ||
|  | 	{ | ||
|  |         if(v < 8u) | ||
|  |             return __n; | ||
|  |         if(v < __b2) | ||
|  |             return __n + 1; | ||
|  |         if(v < __b3) | ||
|  |             return __n + 2; | ||
|  |         if(v < __b4) | ||
|  |             return __n + 3; | ||
|  |         v /= (U) __b4; | ||
|  |         __n += 4; | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | 
 | ||
|  | namespace detail { | ||
|  | C4_INLINE_CONSTEXPR const char hexchars[] = "0123456789abcdef"; | ||
|  | C4_INLINE_CONSTEXPR const char digits0099[] = | ||
|  |     "0001020304050607080910111213141516171819" | ||
|  |     "2021222324252627282930313233343536373839" | ||
|  |     "4041424344454647484950515253545556575859" | ||
|  |     "6061626364656667686970717273747576777879" | ||
|  |     "8081828384858687888990919293949596979899"; | ||
|  | } // namespace detail
 | ||
|  | 
 | ||
|  | C4_SUPPRESS_WARNING_GCC_PUSH | ||
|  | C4_SUPPRESS_WARNING_GCC("-Warray-bounds")  // gcc has false positives here
 | ||
|  | #if (defined(__GNUC__) && (__GNUC__ >= 7))
 | ||
|  | C4_SUPPRESS_WARNING_GCC("-Wstringop-overflow")  // gcc has false positives here
 | ||
|  | #endif
 | ||
|  | 
 | ||
|  | template<class T> | ||
|  | C4_HOT C4_ALWAYS_INLINE | ||
|  | void write_dec_unchecked(substr buf, T v, unsigned digits_v) noexcept | ||
|  | { | ||
|  |     C4_STATIC_ASSERT(std::is_integral<T>::value); | ||
|  |     C4_ASSERT(v >= 0); | ||
|  |     C4_ASSERT(buf.len >= digits_v); | ||
|  |     C4_XASSERT(digits_v == digits_dec(v)); | ||
|  |     // in bm_xtoa: checkoncelog_singlediv_write2
 | ||
|  |     while(v >= T(100)) | ||
|  |     { | ||
|  |         T quo = v; | ||
|  |         quo /= T(100); | ||
|  |         const auto num = (v - quo * T(100)) << 1u; | ||
|  |         v = quo; | ||
|  |         buf.str[--digits_v] = detail::digits0099[num + 1]; | ||
|  |         buf.str[--digits_v] = detail::digits0099[num]; | ||
|  |     } | ||
|  |     if(v >= T(10)) | ||
|  |     { | ||
|  |         C4_ASSERT(digits_v == 2); | ||
|  |         const auto num = v << 1u; | ||
|  |         buf.str[1] = detail::digits0099[num + 1]; | ||
|  |         buf.str[0] = detail::digits0099[num]; | ||
|  |     } | ||
|  |     else | ||
|  |     { | ||
|  |         C4_ASSERT(digits_v == 1); | ||
|  |         buf.str[0] = (char)('0' + v); | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | template<class T> | ||
|  | C4_HOT C4_ALWAYS_INLINE | ||
|  | void write_hex_unchecked(substr buf, T v, unsigned digits_v) noexcept | ||
|  | { | ||
|  |     C4_STATIC_ASSERT(std::is_integral<T>::value); | ||
|  |     C4_ASSERT(v >= 0); | ||
|  |     C4_ASSERT(buf.len >= digits_v); | ||
|  |     C4_XASSERT(digits_v == digits_hex(v)); | ||
|  |     do { | ||
|  |         buf.str[--digits_v] = detail::hexchars[v & T(15)]; | ||
|  |         v >>= 4; | ||
|  |     } while(v); | ||
|  |     C4_ASSERT(digits_v == 0); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | template<class T> | ||
|  | C4_HOT C4_ALWAYS_INLINE | ||
|  | void write_oct_unchecked(substr buf, T v, unsigned digits_v) noexcept | ||
|  | { | ||
|  |     C4_STATIC_ASSERT(std::is_integral<T>::value); | ||
|  |     C4_ASSERT(v >= 0); | ||
|  |     C4_ASSERT(buf.len >= digits_v); | ||
|  |     C4_XASSERT(digits_v == digits_oct(v)); | ||
|  |     do { | ||
|  |         buf.str[--digits_v] = (char)('0' + (v & T(7))); | ||
|  |         v >>= 3; | ||
|  |     } while(v); | ||
|  |     C4_ASSERT(digits_v == 0); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | template<class T> | ||
|  | C4_HOT C4_ALWAYS_INLINE | ||
|  | void write_bin_unchecked(substr buf, T v, unsigned digits_v) noexcept | ||
|  | { | ||
|  |     C4_STATIC_ASSERT(std::is_integral<T>::value); | ||
|  |     C4_ASSERT(v >= 0); | ||
|  |     C4_ASSERT(buf.len >= digits_v); | ||
|  |     C4_XASSERT(digits_v == digits_bin(v)); | ||
|  |     do { | ||
|  |         buf.str[--digits_v] = (char)('0' + (v & T(1))); | ||
|  |         v >>= 1; | ||
|  |     } while(v); | ||
|  |     C4_ASSERT(digits_v == 0); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /** write an integer to a string in decimal format. This is the
 | ||
|  |  * lowest level (and the fastest) function to do this task. | ||
|  |  * @note does not accept negative numbers | ||
|  |  * @note the resulting string is NOT zero-terminated. | ||
|  |  * @note it is ok to call this with an empty or too-small buffer; | ||
|  |  * no writes will occur, and the required size will be returned | ||
|  |  * @return the number of characters required for the buffer. */ | ||
|  | template<class T> | ||
|  | C4_ALWAYS_INLINE size_t write_dec(substr buf, T v) noexcept | ||
|  | { | ||
|  |     C4_STATIC_ASSERT(std::is_integral<T>::value); | ||
|  |     C4_ASSERT(v >= 0); | ||
|  |     unsigned digits = digits_dec(v); | ||
|  |     if(C4_LIKELY(buf.len >= digits)) | ||
|  |         write_dec_unchecked(buf, v, digits); | ||
|  |     return digits; | ||
|  | } | ||
|  | 
 | ||
|  | /** write an integer to a string in hexadecimal format. This is the
 | ||
|  |  * lowest level (and the fastest) function to do this task. | ||
|  |  * @note does not accept negative numbers | ||
|  |  * @note does not prefix with 0x | ||
|  |  * @note the resulting string is NOT zero-terminated. | ||
|  |  * @note it is ok to call this with an empty or too-small buffer; | ||
|  |  * no writes will occur, and the required size will be returned | ||
|  |  * @return the number of characters required for the buffer. */ | ||
|  | template<class T> | ||
|  | C4_ALWAYS_INLINE size_t write_hex(substr buf, T v) noexcept | ||
|  | { | ||
|  |     C4_STATIC_ASSERT(std::is_integral<T>::value); | ||
|  |     C4_ASSERT(v >= 0); | ||
|  |     unsigned digits = digits_hex(v); | ||
|  |     if(C4_LIKELY(buf.len >= digits)) | ||
|  |         write_hex_unchecked(buf, v, digits); | ||
|  |     return digits; | ||
|  | } | ||
|  | 
 | ||
|  | /** write an integer to a string in octal format. This is the
 | ||
|  |  * lowest level (and the fastest) function to do this task. | ||
|  |  * @note does not accept negative numbers | ||
|  |  * @note does not prefix with 0o | ||
|  |  * @note the resulting string is NOT zero-terminated. | ||
|  |  * @note it is ok to call this with an empty or too-small buffer; | ||
|  |  * no writes will occur, and the required size will be returned | ||
|  |  * @return the number of characters required for the buffer. */ | ||
|  | template<class T> | ||
|  | C4_ALWAYS_INLINE size_t write_oct(substr buf, T v) noexcept | ||
|  | { | ||
|  |     C4_STATIC_ASSERT(std::is_integral<T>::value); | ||
|  |     C4_ASSERT(v >= 0); | ||
|  |     unsigned digits = digits_oct(v); | ||
|  |     if(C4_LIKELY(buf.len >= digits)) | ||
|  |         write_oct_unchecked(buf, v, digits); | ||
|  |     return digits; | ||
|  | } | ||
|  | 
 | ||
|  | /** write an integer to a string in binary format. This is the
 | ||
|  |  * lowest level (and the fastest) function to do this task. | ||
|  |  * @note does not accept negative numbers | ||
|  |  * @note does not prefix with 0b | ||
|  |  * @note the resulting string is NOT zero-terminated. | ||
|  |  * @note it is ok to call this with an empty or too-small buffer; | ||
|  |  * no writes will occur, and the required size will be returned | ||
|  |  * @return the number of characters required for the buffer. */ | ||
|  | template<class T> | ||
|  | C4_ALWAYS_INLINE size_t write_bin(substr buf, T v) noexcept | ||
|  | { | ||
|  |     C4_STATIC_ASSERT(std::is_integral<T>::value); | ||
|  |     C4_ASSERT(v >= 0); | ||
|  |     unsigned digits = digits_bin(v); | ||
|  |     C4_ASSERT(digits > 0); | ||
|  |     if(C4_LIKELY(buf.len >= digits)) | ||
|  |         write_bin_unchecked(buf, v, digits); | ||
|  |     return digits; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | namespace detail { | ||
|  | template<class U> using NumberWriter = size_t (*)(substr, U); | ||
|  | template<class T, NumberWriter<T> writer> | ||
|  | size_t write_num_digits(substr buf, T v, size_t num_digits) noexcept | ||
|  | { | ||
|  |     C4_STATIC_ASSERT(std::is_integral<T>::value); | ||
|  |     size_t ret = writer(buf, v); | ||
|  |     if(ret >= num_digits) | ||
|  |         return ret; | ||
|  |     else if(ret >= buf.len || num_digits > buf.len) | ||
|  |         return num_digits; | ||
|  |     C4_ASSERT(num_digits >= ret); | ||
|  |     size_t delta = static_cast<size_t>(num_digits - ret); | ||
|  |     memmove(buf.str + delta, buf.str, ret); | ||
|  |     memset(buf.str, '0', delta); | ||
|  |     return num_digits; | ||
|  | } | ||
|  | } // namespace detail
 | ||
|  | 
 | ||
|  | 
 | ||
|  | /** same as c4::write_dec(), but pad with zeroes on the left
 | ||
|  |  * such that the resulting string is @p num_digits wide. | ||
|  |  * If the given number is requires more than num_digits, then the number prevails. */ | ||
|  | template<class T> | ||
|  | C4_ALWAYS_INLINE size_t write_dec(substr buf, T val, size_t num_digits) noexcept | ||
|  | { | ||
|  |     return detail::write_num_digits<T, &write_dec<T>>(buf, val, num_digits); | ||
|  | } | ||
|  | 
 | ||
|  | /** same as c4::write_hex(), but pad with zeroes on the left
 | ||
|  |  * such that the resulting string is @p num_digits wide. | ||
|  |  * If the given number is requires more than num_digits, then the number prevails. */ | ||
|  | template<class T> | ||
|  | C4_ALWAYS_INLINE size_t write_hex(substr buf, T val, size_t num_digits) noexcept | ||
|  | { | ||
|  |     return detail::write_num_digits<T, &write_hex<T>>(buf, val, num_digits); | ||
|  | } | ||
|  | 
 | ||
|  | /** same as c4::write_bin(), but pad with zeroes on the left
 | ||
|  |  * such that the resulting string is @p num_digits wide. | ||
|  |  * If the given number is requires more than num_digits, then the number prevails. */ | ||
|  | template<class T> | ||
|  | C4_ALWAYS_INLINE size_t write_bin(substr buf, T val, size_t num_digits) noexcept | ||
|  | { | ||
|  |     return detail::write_num_digits<T, &write_bin<T>>(buf, val, num_digits); | ||
|  | } | ||
|  | 
 | ||
|  | /** same as c4::write_oct(), but pad with zeroes on the left
 | ||
|  |  * such that the resulting string is @p num_digits wide. | ||
|  |  * If the given number is requires more than num_digits, then the number prevails. */ | ||
|  | template<class T> | ||
|  | C4_ALWAYS_INLINE size_t write_oct(substr buf, T val, size_t num_digits) noexcept | ||
|  | { | ||
|  |     return detail::write_num_digits<T, &write_oct<T>>(buf, val, num_digits); | ||
|  | } | ||
|  | 
 | ||
|  | C4_SUPPRESS_WARNING_GCC_POP | ||
|  | 
 | ||
|  | 
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | 
 | ||
|  | 
 | ||
|  | C4_SUPPRESS_WARNING_MSVC_PUSH | ||
|  | C4_SUPPRESS_WARNING_MSVC(4365) // '=': conversion from 'int' to 'I', signed/unsigned mismatch
 | ||
|  | 
 | ||
|  | /** read a decimal integer from a string. This is the
 | ||
|  |  * lowest level (and the fastest) function to do this task. | ||
|  |  * @note does not accept negative numbers | ||
|  |  * @note The string must be trimmed. Whitespace is not accepted. | ||
|  |  * @note the string must not be empty | ||
|  |  * @note there is no check for overflow; the value wraps around | ||
|  |  * in a way similar to the standard C/C++ overflow behavior. | ||
|  |  * For example, `read_dec<int8_t>("128", &val)` returns true | ||
|  |  * and val will be set to 0 because 127 is the max i8 value. | ||
|  |  * @see overflows<T>() to find out if a number string overflows a type range | ||
|  |  * @return true if the conversion was successful (no overflow check) */ | ||
|  | template<class I> | ||
|  | C4_ALWAYS_INLINE bool read_dec(csubstr s, I *C4_RESTRICT v) noexcept | ||
|  | { | ||
|  |     C4_STATIC_ASSERT(std::is_integral<I>::value); | ||
|  |     C4_ASSERT(!s.empty()); | ||
|  |     *v = 0; | ||
|  |     for(char c : s) | ||
|  |     { | ||
|  |         if(C4_UNLIKELY(c < '0' || c > '9')) | ||
|  |             return false; | ||
|  |         *v = (*v) * I(10) + (I(c) - I('0')); | ||
|  |     } | ||
|  |     return true; | ||
|  | } | ||
|  | 
 | ||
|  | /** read an hexadecimal integer from a string. This is the
 | ||
|  |  * lowest level (and the fastest) function to do this task. | ||
|  |  * @note does not accept negative numbers | ||
|  |  * @note does not accept leading 0x or 0X | ||
|  |  * @note the string must not be empty | ||
|  |  * @note the string must be trimmed. Whitespace is not accepted. | ||
|  |  * @note there is no check for overflow; the value wraps around | ||
|  |  * in a way similar to the standard C/C++ overflow behavior. | ||
|  |  * For example, `read_hex<int8_t>("80", &val)` returns true | ||
|  |  * and val will be set to 0 because 7f is the max i8 value. | ||
|  |  * @see overflows<T>() to find out if a number string overflows a type range | ||
|  |  * @return true if the conversion was successful (no overflow check) */ | ||
|  | template<class I> | ||
|  | C4_ALWAYS_INLINE bool read_hex(csubstr s, I *C4_RESTRICT v) noexcept | ||
|  | { | ||
|  |     C4_STATIC_ASSERT(std::is_integral<I>::value); | ||
|  |     C4_ASSERT(!s.empty()); | ||
|  |     *v = 0; | ||
|  |     for(char c : s) | ||
|  |     { | ||
|  |         I cv; | ||
|  |         if(c >= '0' && c <= '9') | ||
|  |             cv = I(c) - I('0'); | ||
|  |         else if(c >= 'a' && c <= 'f') | ||
|  |             cv = I(10) + (I(c) - I('a')); | ||
|  |         else if(c >= 'A' && c <= 'F') | ||
|  |             cv = I(10) + (I(c) - I('A')); | ||
|  |         else | ||
|  |             return false; | ||
|  |         *v = (*v) * I(16) + cv; | ||
|  |     } | ||
|  |     return true; | ||
|  | } | ||
|  | 
 | ||
|  | /** read a binary integer from a string. This is the
 | ||
|  |  * lowest level (and the fastest) function to do this task. | ||
|  |  * @note does not accept negative numbers | ||
|  |  * @note does not accept leading 0b or 0B | ||
|  |  * @note the string must not be empty | ||
|  |  * @note the string must be trimmed. Whitespace is not accepted. | ||
|  |  * @note there is no check for overflow; the value wraps around | ||
|  |  * in a way similar to the standard C/C++ overflow behavior. | ||
|  |  * For example, `read_bin<int8_t>("10000000", &val)` returns true | ||
|  |  * and val will be set to 0 because 1111111 is the max i8 value. | ||
|  |  * @see overflows<T>() to find out if a number string overflows a type range | ||
|  |  * @return true if the conversion was successful (no overflow check) */ | ||
|  | template<class I> | ||
|  | C4_ALWAYS_INLINE bool read_bin(csubstr s, I *C4_RESTRICT v) noexcept | ||
|  | { | ||
|  |     C4_STATIC_ASSERT(std::is_integral<I>::value); | ||
|  |     C4_ASSERT(!s.empty()); | ||
|  |     *v = 0; | ||
|  |     for(char c : s) | ||
|  |     { | ||
|  |         *v <<= 1; | ||
|  |         if(c == '1') | ||
|  |             *v |= 1; | ||
|  |         else if(c != '0') | ||
|  |             return false; | ||
|  |     } | ||
|  |     return true; | ||
|  | } | ||
|  | 
 | ||
|  | /** read an octal integer from a string. This is the
 | ||
|  |  * lowest level (and the fastest) function to do this task. | ||
|  |  * @note does not accept negative numbers | ||
|  |  * @note does not accept leading 0o or 0O | ||
|  |  * @note the string must not be empty | ||
|  |  * @note the string must be trimmed. Whitespace is not accepted. | ||
|  |  * @note there is no check for overflow; the value wraps around | ||
|  |  * in a way similar to the standard C/C++ overflow behavior. | ||
|  |  * For example, `read_oct<int8_t>("200", &val)` returns true | ||
|  |  * and val will be set to 0 because 177 is the max i8 value. | ||
|  |  * @see overflows<T>() to find out if a number string overflows a type range | ||
|  |  * @return true if the conversion was successful (no overflow check) */ | ||
|  | template<class I> | ||
|  | C4_ALWAYS_INLINE bool read_oct(csubstr s, I *C4_RESTRICT v) noexcept | ||
|  | { | ||
|  |     C4_STATIC_ASSERT(std::is_integral<I>::value); | ||
|  |     C4_ASSERT(!s.empty()); | ||
|  |     *v = 0; | ||
|  |     for(char c : s) | ||
|  |     { | ||
|  |         if(C4_UNLIKELY(c < '0' || c > '7')) | ||
|  |             return false; | ||
|  |         *v = (*v) * I(8) + (I(c) - I('0')); | ||
|  |     } | ||
|  |     return true; | ||
|  | } | ||
|  | 
 | ||
|  | C4_SUPPRESS_WARNING_MSVC_POP | ||
|  | 
 | ||
|  | 
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | 
 | ||
|  | C4_SUPPRESS_WARNING_GCC_WITH_PUSH("-Wswitch-default") | ||
|  | 
 | ||
|  | namespace detail { | ||
|  | inline size_t _itoa2buf(substr buf, size_t pos, csubstr val) noexcept | ||
|  | { | ||
|  |     C4_ASSERT(pos + val.len <= buf.len); | ||
|  |     memcpy(buf.str + pos, val.str, val.len); | ||
|  |     return pos + val.len; | ||
|  | } | ||
|  | inline size_t _itoa2bufwithdigits(substr buf, size_t pos, size_t num_digits, csubstr val) noexcept | ||
|  | { | ||
|  |     num_digits = num_digits > val.len ? num_digits - val.len : 0; | ||
|  |     C4_ASSERT(num_digits + val.len <= buf.len); | ||
|  |     for(size_t i = 0; i < num_digits; ++i) | ||
|  |         _c4append('0'); | ||
|  |     return detail::_itoa2buf(buf, pos, val); | ||
|  | } | ||
|  | template<class I> | ||
|  | C4_NO_INLINE size_t _itoadec2buf(substr buf) noexcept | ||
|  | { | ||
|  |     using digits_type = detail::charconv_digits<I>; | ||
|  |     if(C4_UNLIKELY(buf.len < digits_type::maxdigits_dec)) | ||
|  |         return digits_type::maxdigits_dec; | ||
|  |     buf.str[0] = '-'; | ||
|  |     return detail::_itoa2buf(buf, 1, digits_type::min_value_dec()); | ||
|  | } | ||
|  | template<class I> | ||
|  | C4_NO_INLINE size_t _itoa2buf(substr buf, I radix) noexcept | ||
|  | { | ||
|  |     using digits_type = detail::charconv_digits<I>; | ||
|  |     size_t pos = 0; | ||
|  |     if(C4_LIKELY(buf.len > 0)) | ||
|  |         buf.str[pos++] = '-'; | ||
|  |     switch(radix) | ||
|  |     { | ||
|  |     case I(10): | ||
|  |         if(C4_UNLIKELY(buf.len < digits_type::maxdigits_dec)) | ||
|  |             return digits_type::maxdigits_dec; | ||
|  |         pos =_itoa2buf(buf, pos, digits_type::min_value_dec()); | ||
|  |         break; | ||
|  |     case I(16): | ||
|  |         if(C4_UNLIKELY(buf.len < digits_type::maxdigits_hex)) | ||
|  |             return digits_type::maxdigits_hex; | ||
|  |         buf.str[pos++] = '0'; | ||
|  |         buf.str[pos++] = 'x'; | ||
|  |         pos = _itoa2buf(buf, pos, digits_type::min_value_hex()); | ||
|  |         break; | ||
|  |     case I( 2): | ||
|  |         if(C4_UNLIKELY(buf.len < digits_type::maxdigits_bin)) | ||
|  |             return digits_type::maxdigits_bin; | ||
|  |         buf.str[pos++] = '0'; | ||
|  |         buf.str[pos++] = 'b'; | ||
|  |         pos = _itoa2buf(buf, pos, digits_type::min_value_bin()); | ||
|  |         break; | ||
|  |     case I( 8): | ||
|  |         if(C4_UNLIKELY(buf.len < digits_type::maxdigits_oct)) | ||
|  |             return digits_type::maxdigits_oct; | ||
|  |         buf.str[pos++] = '0'; | ||
|  |         buf.str[pos++] = 'o'; | ||
|  |         pos = _itoa2buf(buf, pos, digits_type::min_value_oct()); | ||
|  |         break; | ||
|  |     } | ||
|  |     return pos; | ||
|  | } | ||
|  | template<class I> | ||
|  | C4_NO_INLINE size_t _itoa2buf(substr buf, I radix, size_t num_digits) noexcept | ||
|  | { | ||
|  |     using digits_type = detail::charconv_digits<I>; | ||
|  |     size_t pos = 0; | ||
|  |     size_t needed_digits = 0; | ||
|  |     if(C4_LIKELY(buf.len > 0)) | ||
|  |         buf.str[pos++] = '-'; | ||
|  |     switch(radix) | ||
|  |     { | ||
|  |     case I(10): | ||
|  |         // add 1 to account for -
 | ||
|  |         needed_digits = num_digits+1 > digits_type::maxdigits_dec ? num_digits+1 : digits_type::maxdigits_dec; | ||
|  |         if(C4_UNLIKELY(buf.len < needed_digits)) | ||
|  |             return needed_digits; | ||
|  |         pos = _itoa2bufwithdigits(buf, pos, num_digits, digits_type::min_value_dec()); | ||
|  |         break; | ||
|  |     case I(16): | ||
|  |         // add 3 to account for -0x
 | ||
|  |         needed_digits = num_digits+3 > digits_type::maxdigits_hex ? num_digits+3 : digits_type::maxdigits_hex; | ||
|  |         if(C4_UNLIKELY(buf.len < needed_digits)) | ||
|  |             return needed_digits; | ||
|  |         buf.str[pos++] = '0'; | ||
|  |         buf.str[pos++] = 'x'; | ||
|  |         pos = _itoa2bufwithdigits(buf, pos, num_digits, digits_type::min_value_hex()); | ||
|  |         break; | ||
|  |     case I(2): | ||
|  |         // add 3 to account for -0b
 | ||
|  |         needed_digits = num_digits+3 > digits_type::maxdigits_bin ? num_digits+3 : digits_type::maxdigits_bin; | ||
|  |         if(C4_UNLIKELY(buf.len < needed_digits)) | ||
|  |             return needed_digits; | ||
|  |         C4_ASSERT(buf.len >= digits_type::maxdigits_bin); | ||
|  |         buf.str[pos++] = '0'; | ||
|  |         buf.str[pos++] = 'b'; | ||
|  |         pos = _itoa2bufwithdigits(buf, pos, num_digits, digits_type::min_value_bin()); | ||
|  |         break; | ||
|  |     case I(8): | ||
|  |         // add 3 to account for -0o
 | ||
|  |         needed_digits = num_digits+3 > digits_type::maxdigits_oct ? num_digits+3 : digits_type::maxdigits_oct; | ||
|  |         if(C4_UNLIKELY(buf.len < needed_digits)) | ||
|  |             return needed_digits; | ||
|  |         C4_ASSERT(buf.len >= digits_type::maxdigits_oct); | ||
|  |         buf.str[pos++] = '0'; | ||
|  |         buf.str[pos++] = 'o'; | ||
|  |         pos = _itoa2bufwithdigits(buf, pos, num_digits, digits_type::min_value_oct()); | ||
|  |         break; | ||
|  |     } | ||
|  |     return pos; | ||
|  | } | ||
|  | } // namespace detail
 | ||
|  | 
 | ||
|  | 
 | ||
|  | /** convert an integral signed decimal to a string.
 | ||
|  |  * @note the resulting string is NOT zero-terminated. | ||
|  |  * @note it is ok to call this with an empty or too-small buffer; | ||
|  |  * no writes will occur, and the needed size will be returned | ||
|  |  * @return the number of characters required for the buffer. */ | ||
|  | template<class T> | ||
|  | C4_ALWAYS_INLINE size_t itoa(substr buf, T v) noexcept | ||
|  | { | ||
|  |     C4_STATIC_ASSERT(std::is_signed<T>::value); | ||
|  |     if(v >= T(0)) | ||
|  |     { | ||
|  |         // write_dec() checks the buffer size, so no need to check here
 | ||
|  |         return write_dec(buf, v); | ||
|  |     } | ||
|  |     // when T is the min value (eg i8: -128), negating it
 | ||
|  |     // will overflow, so treat the min as a special case
 | ||
|  |     else if(C4_LIKELY(v != std::numeric_limits<T>::min())) | ||
|  |     { | ||
|  |         v = -v; | ||
|  |         unsigned digits = digits_dec(v); | ||
|  |         if(C4_LIKELY(buf.len >= digits + 1u)) | ||
|  |         { | ||
|  |             buf.str[0] = '-'; | ||
|  |             write_dec_unchecked(buf.sub(1), v, digits); | ||
|  |         } | ||
|  |         return digits + 1u; | ||
|  |     } | ||
|  |     return detail::_itoadec2buf<T>(buf); | ||
|  | } | ||
|  | 
 | ||
|  | /** convert an integral signed integer to a string, using a specific
 | ||
|  |  * radix. The radix must be 2, 8, 10 or 16. | ||
|  |  * | ||
|  |  * @note the resulting string is NOT zero-terminated. | ||
|  |  * @note it is ok to call this with an empty or too-small buffer; | ||
|  |  * no writes will occur, and the needed size will be returned | ||
|  |  * @return the number of characters required for the buffer. */ | ||
|  | template<class T> | ||
|  | C4_ALWAYS_INLINE size_t itoa(substr buf, T v, T radix) noexcept | ||
|  | { | ||
|  |     C4_STATIC_ASSERT(std::is_signed<T>::value); | ||
|  |     C4_ASSERT(radix == 2 || radix == 8 || radix == 10 || radix == 16); | ||
|  |     C4_SUPPRESS_WARNING_GCC_PUSH | ||
|  |     #if (defined(__GNUC__) && (__GNUC__ >= 7))
 | ||
|  |         C4_SUPPRESS_WARNING_GCC("-Wstringop-overflow")  // gcc has a false positive here
 | ||
|  |     #endif
 | ||
|  |     // when T is the min value (eg i8: -128), negating it
 | ||
|  |     // will overflow, so treat the min as a special case
 | ||
|  |     if(C4_LIKELY(v != std::numeric_limits<T>::min())) | ||
|  |     { | ||
|  |         unsigned pos = 0; | ||
|  |         if(v < 0) | ||
|  |         { | ||
|  |             v = -v; | ||
|  |             if(C4_LIKELY(buf.len > 0)) | ||
|  |                 buf.str[pos] = '-'; | ||
|  |             ++pos; | ||
|  |         } | ||
|  |         unsigned digits = 0; | ||
|  |         switch(radix) | ||
|  |         { | ||
|  |         case T(10): | ||
|  |             digits = digits_dec(v); | ||
|  |             if(C4_LIKELY(buf.len >= pos + digits)) | ||
|  |                 write_dec_unchecked(buf.sub(pos), v, digits); | ||
|  |             break; | ||
|  |         case T(16): | ||
|  |             digits = digits_hex(v); | ||
|  |             if(C4_LIKELY(buf.len >= pos + 2u + digits)) | ||
|  |             { | ||
|  |                 buf.str[pos + 0] = '0'; | ||
|  |                 buf.str[pos + 1] = 'x'; | ||
|  |                 write_hex_unchecked(buf.sub(pos + 2), v, digits); | ||
|  |             } | ||
|  |             digits += 2u; | ||
|  |             break; | ||
|  |         case T(2): | ||
|  |             digits = digits_bin(v); | ||
|  |             if(C4_LIKELY(buf.len >= pos + 2u + digits)) | ||
|  |             { | ||
|  |                 buf.str[pos + 0] = '0'; | ||
|  |                 buf.str[pos + 1] = 'b'; | ||
|  |                 write_bin_unchecked(buf.sub(pos + 2), v, digits); | ||
|  |             } | ||
|  |             digits += 2u; | ||
|  |             break; | ||
|  |         case T(8): | ||
|  |             digits = digits_oct(v); | ||
|  |             if(C4_LIKELY(buf.len >= pos + 2u + digits)) | ||
|  |             { | ||
|  |                 buf.str[pos + 0] = '0'; | ||
|  |                 buf.str[pos + 1] = 'o'; | ||
|  |                 write_oct_unchecked(buf.sub(pos + 2), v, digits); | ||
|  |             } | ||
|  |             digits += 2u; | ||
|  |             break; | ||
|  |         } | ||
|  |         return pos + digits; | ||
|  |     } | ||
|  |     C4_SUPPRESS_WARNING_GCC_POP | ||
|  |     // when T is the min value (eg i8: -128), negating it
 | ||
|  |     // will overflow
 | ||
|  |     return detail::_itoa2buf<T>(buf, radix); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /** same as c4::itoa(), but pad with zeroes on the left such that the
 | ||
|  |  * resulting string is @p num_digits wide, not accounting for radix | ||
|  |  * prefix (0x,0o,0b). The @p radix must be 2, 8, 10 or 16. | ||
|  |  * | ||
|  |  * @note the resulting string is NOT zero-terminated. | ||
|  |  * @note it is ok to call this with an empty or too-small buffer; | ||
|  |  * no writes will occur, and the needed size will be returned | ||
|  |  * @return the number of characters required for the buffer. */ | ||
|  | template<class T> | ||
|  | C4_ALWAYS_INLINE size_t itoa(substr buf, T v, T radix, size_t num_digits) noexcept | ||
|  | { | ||
|  |     C4_STATIC_ASSERT(std::is_signed<T>::value); | ||
|  |     C4_ASSERT(radix == 2 || radix == 8 || radix == 10 || radix == 16); | ||
|  |     C4_SUPPRESS_WARNING_GCC_PUSH | ||
|  |     #if (defined(__GNUC__) && (__GNUC__ >= 7))
 | ||
|  |         C4_SUPPRESS_WARNING_GCC("-Wstringop-overflow")  // gcc has a false positive here
 | ||
|  |     #endif
 | ||
|  |     // when T is the min value (eg i8: -128), negating it
 | ||
|  |     // will overflow, so treat the min as a special case
 | ||
|  |     if(C4_LIKELY(v != std::numeric_limits<T>::min())) | ||
|  |     { | ||
|  |         unsigned pos = 0; | ||
|  |         if(v < 0) | ||
|  |         { | ||
|  |             v = -v; | ||
|  |             if(C4_LIKELY(buf.len > 0)) | ||
|  |                 buf.str[pos] = '-'; | ||
|  |             ++pos; | ||
|  |         } | ||
|  |         unsigned total_digits = 0; | ||
|  |         switch(radix) | ||
|  |         { | ||
|  |         case T(10): | ||
|  |             total_digits = digits_dec(v); | ||
|  |             total_digits = pos + (unsigned)(num_digits > total_digits ? num_digits : total_digits); | ||
|  |             if(C4_LIKELY(buf.len >= total_digits)) | ||
|  |                 write_dec(buf.sub(pos), v, num_digits); | ||
|  |             break; | ||
|  |         case T(16): | ||
|  |             total_digits = digits_hex(v); | ||
|  |             total_digits = pos + 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits); | ||
|  |             if(C4_LIKELY(buf.len >= total_digits)) | ||
|  |             { | ||
|  |                 buf.str[pos + 0] = '0'; | ||
|  |                 buf.str[pos + 1] = 'x'; | ||
|  |                 write_hex(buf.sub(pos + 2), v, num_digits); | ||
|  |             } | ||
|  |             break; | ||
|  |         case T(2): | ||
|  |             total_digits = digits_bin(v); | ||
|  |             total_digits = pos + 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits); | ||
|  |             if(C4_LIKELY(buf.len >= total_digits)) | ||
|  |             { | ||
|  |                 buf.str[pos + 0] = '0'; | ||
|  |                 buf.str[pos + 1] = 'b'; | ||
|  |                 write_bin(buf.sub(pos + 2), v, num_digits); | ||
|  |             } | ||
|  |             break; | ||
|  |         case T(8): | ||
|  |             total_digits = digits_oct(v); | ||
|  |             total_digits = pos + 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits); | ||
|  |             if(C4_LIKELY(buf.len >= total_digits)) | ||
|  |             { | ||
|  |                 buf.str[pos + 0] = '0'; | ||
|  |                 buf.str[pos + 1] = 'o'; | ||
|  |                 write_oct(buf.sub(pos + 2), v, num_digits); | ||
|  |             } | ||
|  |             break; | ||
|  |         } | ||
|  |         return total_digits; | ||
|  |     } | ||
|  |     C4_SUPPRESS_WARNING_GCC_POP | ||
|  |     // when T is the min value (eg i8: -128), negating it
 | ||
|  |     // will overflow
 | ||
|  |     return detail::_itoa2buf<T>(buf, radix, num_digits); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | 
 | ||
|  | /** convert an integral unsigned decimal to a string.
 | ||
|  |  * | ||
|  |  * @note the resulting string is NOT zero-terminated. | ||
|  |  * @note it is ok to call this with an empty or too-small buffer; | ||
|  |  * no writes will occur, and the needed size will be returned | ||
|  |  * @return the number of characters required for the buffer. */ | ||
|  | template<class T> | ||
|  | C4_ALWAYS_INLINE size_t utoa(substr buf, T v) noexcept | ||
|  | { | ||
|  |     C4_STATIC_ASSERT(std::is_unsigned<T>::value); | ||
|  |     // write_dec() does the buffer length check, so no need to check here
 | ||
|  |     return write_dec(buf, v); | ||
|  | } | ||
|  | 
 | ||
|  | /** convert an integral unsigned integer to a string, using a specific
 | ||
|  |  * radix. The radix must be 2, 8, 10 or 16. | ||
|  |  * | ||
|  |  * @note the resulting string is NOT zero-terminated. | ||
|  |  * @note it is ok to call this with an empty or too-small buffer; | ||
|  |  * no writes will occur, and the needed size will be returned | ||
|  |  * @return the number of characters required for the buffer. */ | ||
|  | template<class T> | ||
|  | C4_ALWAYS_INLINE size_t utoa(substr buf, T v, T radix) noexcept | ||
|  | { | ||
|  |     C4_STATIC_ASSERT(std::is_unsigned<T>::value); | ||
|  |     C4_ASSERT(radix == 10 || radix == 16 || radix == 2 || radix == 8); | ||
|  |     unsigned digits = 0; | ||
|  |     switch(radix) | ||
|  |     { | ||
|  |     case T(10): | ||
|  |         digits = digits_dec(v); | ||
|  |         if(C4_LIKELY(buf.len >= digits)) | ||
|  |             write_dec_unchecked(buf, v, digits); | ||
|  |         break; | ||
|  |     case T(16): | ||
|  |         digits = digits_hex(v); | ||
|  |         if(C4_LIKELY(buf.len >= digits+2u)) | ||
|  |         { | ||
|  |             buf.str[0] = '0'; | ||
|  |             buf.str[1] = 'x'; | ||
|  |             write_hex_unchecked(buf.sub(2), v, digits); | ||
|  |         } | ||
|  |         digits += 2u; | ||
|  |         break; | ||
|  |     case T(2): | ||
|  |         digits = digits_bin(v); | ||
|  |         if(C4_LIKELY(buf.len >= digits+2u)) | ||
|  |         { | ||
|  |             buf.str[0] = '0'; | ||
|  |             buf.str[1] = 'b'; | ||
|  |             write_bin_unchecked(buf.sub(2), v, digits); | ||
|  |         } | ||
|  |         digits += 2u; | ||
|  |         break; | ||
|  |     case T(8): | ||
|  |         digits = digits_oct(v); | ||
|  |         if(C4_LIKELY(buf.len >= digits+2u)) | ||
|  |         { | ||
|  |             buf.str[0] = '0'; | ||
|  |             buf.str[1] = 'o'; | ||
|  |             write_oct_unchecked(buf.sub(2), v, digits); | ||
|  |         } | ||
|  |         digits += 2u; | ||
|  |         break; | ||
|  |     } | ||
|  |     return digits; | ||
|  | } | ||
|  | 
 | ||
|  | /** same as c4::utoa(), but pad with zeroes on the left such that the
 | ||
|  |  * resulting string is @p num_digits wide. The @p radix must be 2, | ||
|  |  * 8, 10 or 16. | ||
|  |  * | ||
|  |  * @note the resulting string is NOT zero-terminated. | ||
|  |  * @note it is ok to call this with an empty or too-small buffer; | ||
|  |  * no writes will occur, and the needed size will be returned | ||
|  |  * @return the number of characters required for the buffer. */ | ||
|  | template<class T> | ||
|  | C4_ALWAYS_INLINE size_t utoa(substr buf, T v, T radix, size_t num_digits) noexcept | ||
|  | { | ||
|  |     C4_STATIC_ASSERT(std::is_unsigned<T>::value); | ||
|  |     C4_ASSERT(radix == 10 || radix == 16 || radix == 2 || radix == 8); | ||
|  |     unsigned total_digits = 0; | ||
|  |     switch(radix) | ||
|  |     { | ||
|  |     case T(10): | ||
|  |         total_digits = digits_dec(v); | ||
|  |         total_digits = (unsigned)(num_digits > total_digits ? num_digits : total_digits); | ||
|  |         if(C4_LIKELY(buf.len >= total_digits)) | ||
|  |             write_dec(buf, v, num_digits); | ||
|  |         break; | ||
|  |     case T(16): | ||
|  |         total_digits = digits_hex(v); | ||
|  |         total_digits = 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits); | ||
|  |         if(C4_LIKELY(buf.len >= total_digits)) | ||
|  |         { | ||
|  |             buf.str[0] = '0'; | ||
|  |             buf.str[1] = 'x'; | ||
|  |             write_hex(buf.sub(2), v, num_digits); | ||
|  |         } | ||
|  |         break; | ||
|  |     case T(2): | ||
|  |         total_digits = digits_bin(v); | ||
|  |         total_digits = 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits); | ||
|  |         if(C4_LIKELY(buf.len >= total_digits)) | ||
|  |         { | ||
|  |             buf.str[0] = '0'; | ||
|  |             buf.str[1] = 'b'; | ||
|  |             write_bin(buf.sub(2), v, num_digits); | ||
|  |         } | ||
|  |         break; | ||
|  |     case T(8): | ||
|  |         total_digits = digits_oct(v); | ||
|  |         total_digits = 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits); | ||
|  |         if(C4_LIKELY(buf.len >= total_digits)) | ||
|  |         { | ||
|  |             buf.str[0] = '0'; | ||
|  |             buf.str[1] = 'o'; | ||
|  |             write_oct(buf.sub(2), v, num_digits); | ||
|  |         } | ||
|  |         break; | ||
|  |     } | ||
|  |     return total_digits; | ||
|  | } | ||
|  | C4_SUPPRESS_WARNING_GCC_POP | ||
|  | 
 | ||
|  | 
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | 
 | ||
|  | /** Convert a trimmed string to a signed integral value. The input
 | ||
|  |  * string can be formatted as decimal, binary (prefix 0b or 0B), octal | ||
|  |  * (prefix 0o or 0O) or hexadecimal (prefix 0x or 0X). Strings with | ||
|  |  * leading zeroes are considered as decimal and not octal (unlike the | ||
|  |  * C/C++ convention). Every character in the input string is read for | ||
|  |  * the conversion; the input string must not contain any leading or | ||
|  |  * trailing whitespace. | ||
|  |  * | ||
|  |  * @return true if the conversion was successful. | ||
|  |  * | ||
|  |  * @note overflow is not detected: the return status is true even if | ||
|  |  * the conversion would return a value outside of the type's range, in | ||
|  |  * which case the result will wrap around the type's range. | ||
|  |  * This is similar to native behavior. | ||
|  |  * | ||
|  |  * @note a positive sign is not accepted. ie, the string must not | ||
|  |  * start with '+' | ||
|  |  * | ||
|  |  * @see atoi_first() if the string is not trimmed to the value to read. */ | ||
|  | template<class T> | ||
|  | C4_ALWAYS_INLINE bool atoi(csubstr str, T * C4_RESTRICT v) noexcept | ||
|  | { | ||
|  |     C4_STATIC_ASSERT(std::is_integral<T>::value); | ||
|  |     C4_STATIC_ASSERT(std::is_signed<T>::value); | ||
|  | 
 | ||
|  |     if(C4_UNLIKELY(str.len == 0)) | ||
|  |         return false; | ||
|  | 
 | ||
|  |     C4_ASSERT(str.str[0] != '+'); | ||
|  | 
 | ||
|  |     T sign = 1; | ||
|  |     size_t start = 0; | ||
|  |     if(str.str[0] == '-') | ||
|  |     { | ||
|  |         if(C4_UNLIKELY(str.len == ++start)) | ||
|  |             return false; | ||
|  |         sign = -1; | ||
|  |     } | ||
|  | 
 | ||
|  |     bool parsed_ok = true; | ||
|  |     if(str.str[start] != '0') // this should be the common case, so put it first
 | ||
|  |     { | ||
|  |         parsed_ok = read_dec(str.sub(start), v); | ||
|  |     } | ||
|  |     else if(str.len > start + 1) | ||
|  |     { | ||
|  |         // starts with 0: is it 0x, 0o, 0b?
 | ||
|  |         const char pfx = str.str[start + 1]; | ||
|  |         if(pfx == 'x' || pfx == 'X') | ||
|  |             parsed_ok = str.len > start + 2 && read_hex(str.sub(start + 2), v); | ||
|  |         else if(pfx == 'b' || pfx == 'B') | ||
|  |             parsed_ok = str.len > start + 2 && read_bin(str.sub(start + 2), v); | ||
|  |         else if(pfx == 'o' || pfx == 'O') | ||
|  |             parsed_ok = str.len > start + 2 && read_oct(str.sub(start + 2), v); | ||
|  |         else | ||
|  |             parsed_ok = read_dec(str.sub(start + 1), v); | ||
|  |     } | ||
|  |     else | ||
|  |     { | ||
|  |         parsed_ok = read_dec(str.sub(start), v); | ||
|  |     } | ||
|  |     if(C4_LIKELY(parsed_ok)) | ||
|  |         *v *= sign; | ||
|  |     return parsed_ok; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /** Select the next range of characters in the string that can be parsed
 | ||
|  |  * as a signed integral value, and convert it using atoi(). Leading | ||
|  |  * whitespace (space, newline, tabs) is skipped. | ||
|  |  * @return the number of characters read for conversion, or csubstr::npos if the conversion failed | ||
|  |  * @see atoi() if the string is already trimmed to the value to read. | ||
|  |  * @see csubstr::first_int_span() */ | ||
|  | template<class T> | ||
|  | C4_ALWAYS_INLINE size_t atoi_first(csubstr str, T * C4_RESTRICT v) | ||
|  | { | ||
|  |     csubstr trimmed = str.first_int_span(); | ||
|  |     if(trimmed.len == 0) | ||
|  |         return csubstr::npos; | ||
|  |     if(atoi(trimmed, v)) | ||
|  |         return static_cast<size_t>(trimmed.end() - str.begin()); | ||
|  |     return csubstr::npos; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | 
 | ||
|  | /** Convert a trimmed string to an unsigned integral value. The string can be
 | ||
|  |  * formatted as decimal, binary (prefix 0b or 0B), octal (prefix 0o or 0O) | ||
|  |  * or hexadecimal (prefix 0x or 0X). Every character in the input string is read | ||
|  |  * for the conversion; it must not contain any leading or trailing whitespace. | ||
|  |  * | ||
|  |  * @return true if the conversion was successful. | ||
|  |  * | ||
|  |  * @note overflow is not detected: the return status is true even if | ||
|  |  * the conversion would return a value outside of the type's range, in | ||
|  |  * which case the result will wrap around the type's range. | ||
|  |  * | ||
|  |  * @note If the string has a minus character, the return status | ||
|  |  * will be false. | ||
|  |  * | ||
|  |  * @see atou_first() if the string is not trimmed to the value to read. */ | ||
|  | template<class T> | ||
|  | bool atou(csubstr str, T * C4_RESTRICT v) noexcept | ||
|  | { | ||
|  |     C4_STATIC_ASSERT(std::is_integral<T>::value); | ||
|  | 
 | ||
|  |     if(C4_UNLIKELY(str.len == 0 || str.front() == '-')) | ||
|  |         return false; | ||
|  | 
 | ||
|  |     bool parsed_ok = true; | ||
|  |     if(str.str[0] != '0') | ||
|  |     { | ||
|  |         parsed_ok = read_dec(str, v); | ||
|  |     } | ||
|  |     else | ||
|  |     { | ||
|  |         if(str.len > 1) | ||
|  |         { | ||
|  |             const char pfx = str.str[1]; | ||
|  |             if(pfx == 'x' || pfx == 'X') | ||
|  |                 parsed_ok = str.len > 2 && read_hex(str.sub(2), v); | ||
|  |             else if(pfx == 'b' || pfx == 'B') | ||
|  |                 parsed_ok = str.len > 2 && read_bin(str.sub(2), v); | ||
|  |             else if(pfx == 'o' || pfx == 'O') | ||
|  |                 parsed_ok = str.len > 2 && read_oct(str.sub(2), v); | ||
|  |             else | ||
|  |                 parsed_ok = read_dec(str, v); | ||
|  |         } | ||
|  |         else | ||
|  |         { | ||
|  |             *v = 0; // we know the first character is 0
 | ||
|  |         } | ||
|  |     } | ||
|  |     return parsed_ok; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /** Select the next range of characters in the string that can be parsed
 | ||
|  |  * as an unsigned integral value, and convert it using atou(). Leading | ||
|  |  * whitespace (space, newline, tabs) is skipped. | ||
|  |  * @return the number of characters read for conversion, or csubstr::npos if the conversion faileds | ||
|  |  * @see atou() if the string is already trimmed to the value to read. | ||
|  |  * @see csubstr::first_uint_span() */ | ||
|  | template<class T> | ||
|  | C4_ALWAYS_INLINE size_t atou_first(csubstr str, T *v) | ||
|  | { | ||
|  |     csubstr trimmed = str.first_uint_span(); | ||
|  |     if(trimmed.len == 0) | ||
|  |         return csubstr::npos; | ||
|  |     if(atou(trimmed, v)) | ||
|  |         return static_cast<size_t>(trimmed.end() - str.begin()); | ||
|  |     return csubstr::npos; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | #ifdef _MSC_VER
 | ||
|  | #   pragma warning(pop)
 | ||
|  | #elif defined(__clang__)
 | ||
|  | #   pragma clang diagnostic pop
 | ||
|  | #elif defined(__GNUC__)
 | ||
|  | #   pragma GCC diagnostic pop
 | ||
|  | #endif
 | ||
|  | 
 | ||
|  | 
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | namespace detail { | ||
|  | inline bool check_overflow(csubstr str, csubstr limit) noexcept | ||
|  | { | ||
|  |     if(str.len == limit.len) | ||
|  |     { | ||
|  |         for(size_t i = 0; i < limit.len; ++i) | ||
|  |         { | ||
|  |             if(str[i] < limit[i]) | ||
|  |                 return false; | ||
|  |             else if(str[i] > limit[i]) | ||
|  |                 return true; | ||
|  |         } | ||
|  |         return false; | ||
|  |     } | ||
|  |     else | ||
|  |         return str.len > limit.len; | ||
|  | } | ||
|  | } // namespace detail
 | ||
|  | 
 | ||
|  | 
 | ||
|  | /** Test if the following string would overflow when converted to associated
 | ||
|  |  * types. | ||
|  |  * @return true if number will overflow, false if it fits (or doesn't parse) | ||
|  |  */ | ||
|  | template<class T> | ||
|  | auto overflows(csubstr str) noexcept | ||
|  |     -> typename std::enable_if<std::is_unsigned<T>::value, bool>::type  | ||
|  | { | ||
|  |     C4_STATIC_ASSERT(std::is_integral<T>::value); | ||
|  | 
 | ||
|  |     if(C4_UNLIKELY(str.len == 0)) | ||
|  |     { | ||
|  |         return false; | ||
|  |     } | ||
|  |     else if(str.str[0] == '0') | ||
|  |     { | ||
|  |         if (str.len == 1) | ||
|  |             return false; | ||
|  |         switch (str.str[1]) | ||
|  |         { | ||
|  |             case 'x': | ||
|  |             case 'X': | ||
|  |             { | ||
|  |                 size_t fno = str.first_not_of('0', 2); | ||
|  |                 if (fno == csubstr::npos) | ||
|  |                     return false; | ||
|  |                 return !(str.len <= fno + (sizeof(T) * 2)); | ||
|  |             } | ||
|  |             case 'b': | ||
|  |             case 'B': | ||
|  |             { | ||
|  |                 size_t fno = str.first_not_of('0', 2); | ||
|  |                 if (fno == csubstr::npos) | ||
|  |                     return false; | ||
|  |                 return !(str.len <= fno +(sizeof(T) * 8)); | ||
|  |             } | ||
|  |             case 'o': | ||
|  |             case 'O': | ||
|  |             { | ||
|  |                 size_t fno = str.first_not_of('0', 2); | ||
|  |                 if(fno == csubstr::npos) | ||
|  |                     return false; | ||
|  |                 return detail::charconv_digits<T>::is_oct_overflow(str.sub(fno)); | ||
|  |             } | ||
|  |             default: | ||
|  |             { | ||
|  |                 size_t fno = str.first_not_of('0', 1); | ||
|  |                 if(fno == csubstr::npos) | ||
|  |                     return false; | ||
|  |                 return detail::check_overflow(str.sub(fno), detail::charconv_digits<T>::max_value_dec()); | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  |     else if(C4_UNLIKELY(str[0] == '-')) | ||
|  |     { | ||
|  |         return true; | ||
|  |     } | ||
|  |     else | ||
|  |     { | ||
|  |         return detail::check_overflow(str, detail::charconv_digits<T>::max_value_dec()); | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /** Test if the following string would overflow when converted to associated
 | ||
|  |  * types. | ||
|  |  * @return true if number will overflow, false if it fits (or doesn't parse) | ||
|  |  */ | ||
|  | template<class T> | ||
|  | auto overflows(csubstr str) | ||
|  |     -> typename std::enable_if<std::is_signed<T>::value, bool>::type  | ||
|  | { | ||
|  |     C4_STATIC_ASSERT(std::is_integral<T>::value); | ||
|  |     if(C4_UNLIKELY(str.len == 0)) | ||
|  |         return false; | ||
|  |     if(str.str[0] == '-') | ||
|  |     { | ||
|  |         if(str.str[1] == '0') | ||
|  |         { | ||
|  |             if(str.len == 2) | ||
|  |                 return false; | ||
|  |             switch(str.str[2]) | ||
|  |             { | ||
|  |                 case 'x': | ||
|  |                 case 'X': | ||
|  |                 { | ||
|  |                     size_t fno = str.first_not_of('0', 3); | ||
|  |                     if (fno == csubstr::npos) | ||
|  |                         return false; | ||
|  |                     return detail::check_overflow(str.sub(fno), detail::charconv_digits<T>::min_value_hex()); | ||
|  |                 } | ||
|  |                 case 'b': | ||
|  |                 case 'B': | ||
|  |                 { | ||
|  |                     size_t fno = str.first_not_of('0', 3); | ||
|  |                     if (fno == csubstr::npos) | ||
|  |                         return false; | ||
|  |                     return detail::check_overflow(str.sub(fno), detail::charconv_digits<T>::min_value_bin()); | ||
|  |                 } | ||
|  |                 case 'o': | ||
|  |                 case 'O': | ||
|  |                 { | ||
|  |                     size_t fno = str.first_not_of('0', 3); | ||
|  |                     if(fno == csubstr::npos) | ||
|  |                         return false; | ||
|  |                     return detail::check_overflow(str.sub(fno), detail::charconv_digits<T>::min_value_oct()); | ||
|  |                 } | ||
|  |                 default: | ||
|  |                 { | ||
|  |                     size_t fno = str.first_not_of('0', 2); | ||
|  |                     if(fno == csubstr::npos) | ||
|  |                         return false; | ||
|  |                     return detail::check_overflow(str.sub(fno), detail::charconv_digits<T>::min_value_dec()); | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  |         else | ||
|  |             return detail::check_overflow(str.sub(1), detail::charconv_digits<T>::min_value_dec()); | ||
|  |     } | ||
|  |     else if(str.str[0] == '0') | ||
|  |     { | ||
|  |         if (str.len == 1) | ||
|  |             return false; | ||
|  |         switch(str.str[1]) | ||
|  |         { | ||
|  |             case 'x': | ||
|  |             case 'X': | ||
|  |             { | ||
|  |                 size_t fno = str.first_not_of('0', 2); | ||
|  |                 if (fno == csubstr::npos) | ||
|  |                     return false; | ||
|  |                 const size_t len = str.len - fno; | ||
|  |                 return !((len < sizeof (T) * 2) || (len == sizeof(T) * 2 && str[fno] <= '7')); | ||
|  |             } | ||
|  |             case 'b': | ||
|  |             case 'B': | ||
|  |             { | ||
|  |                 size_t fno = str.first_not_of('0', 2); | ||
|  |                 if (fno == csubstr::npos) | ||
|  |                     return false; | ||
|  |                 return !(str.len <= fno + (sizeof(T) * 8 - 1)); | ||
|  |             } | ||
|  |             case 'o': | ||
|  |             case 'O': | ||
|  |             { | ||
|  |                 size_t fno = str.first_not_of('0', 2); | ||
|  |                 if(fno == csubstr::npos) | ||
|  |                     return false; | ||
|  |                 return detail::charconv_digits<T>::is_oct_overflow(str.sub(fno)); | ||
|  |             } | ||
|  |             default: | ||
|  |             { | ||
|  |                 size_t fno = str.first_not_of('0', 1); | ||
|  |                 if(fno == csubstr::npos) | ||
|  |                     return false; | ||
|  |                 return detail::check_overflow(str.sub(fno), detail::charconv_digits<T>::max_value_dec()); | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  |     else | ||
|  |         return detail::check_overflow(str, detail::charconv_digits<T>::max_value_dec()); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | 
 | ||
|  | namespace detail { | ||
|  | 
 | ||
|  | 
 | ||
|  | #if (!C4CORE_HAVE_STD_FROMCHARS)
 | ||
|  | /** @see http://www.exploringbinary.com/ for many good examples on float-str conversion */ | ||
|  | template<size_t N> | ||
|  | void get_real_format_str(char (& C4_RESTRICT fmt)[N], int precision, RealFormat_e formatting, const char* length_modifier="") | ||
|  | { | ||
|  |     int iret; | ||
|  |     if(precision == -1) | ||
|  |         iret = snprintf(fmt, sizeof(fmt), "%%%s%c", length_modifier, formatting); | ||
|  |     else if(precision == 0) | ||
|  |         iret = snprintf(fmt, sizeof(fmt), "%%.%s%c", length_modifier, formatting); | ||
|  |     else | ||
|  |         iret = snprintf(fmt, sizeof(fmt), "%%.%d%s%c", precision, length_modifier, formatting); | ||
|  |     C4_ASSERT(iret >= 2 && size_t(iret) < sizeof(fmt)); | ||
|  |     C4_UNUSED(iret); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /** @todo we're depending on snprintf()/sscanf() for converting to/from
 | ||
|  |  * floating point numbers. Apparently, this increases the binary size | ||
|  |  * by a considerable amount. There are some lightweight printf | ||
|  |  * implementations: | ||
|  |  * | ||
|  |  * @see http://www.sparetimelabs.com/tinyprintf/tinyprintf.php (BSD)
 | ||
|  |  * @see https://github.com/weiss/c99-snprintf
 | ||
|  |  * @see https://github.com/nothings/stb/blob/master/stb_sprintf.h
 | ||
|  |  * @see http://www.exploringbinary.com/
 | ||
|  |  * @see https://blog.benoitblanchon.fr/lightweight-float-to-string/
 | ||
|  |  * @see http://www.ryanjuckett.com/programming/printing-floating-point-numbers/
 | ||
|  |  */ | ||
|  | template<class T> | ||
|  | size_t print_one(substr str, const char* full_fmt, T v) | ||
|  | { | ||
|  | #ifdef _MSC_VER
 | ||
|  |     /** use _snprintf() to prevent early termination of the output
 | ||
|  |      * for writing the null character at the last position | ||
|  |      * @see https://msdn.microsoft.com/en-us/library/2ts7cx93.aspx */
 | ||
|  |     int iret = _snprintf(str.str, str.len, full_fmt, v); | ||
|  |     if(iret < 0) | ||
|  |     { | ||
|  |         /* when buf.len is not enough, VS returns a negative value.
 | ||
|  |          * so call it again with a negative value for getting an | ||
|  |          * actual length of the string */ | ||
|  |         iret = snprintf(nullptr, 0, full_fmt, v); | ||
|  |         C4_ASSERT(iret > 0); | ||
|  |     } | ||
|  |     size_t ret = (size_t) iret; | ||
|  |     return ret; | ||
|  | #else
 | ||
|  |     int iret = snprintf(str.str, str.len, full_fmt, v); | ||
|  |     C4_ASSERT(iret >= 0); | ||
|  |     size_t ret = (size_t) iret; | ||
|  |     if(ret >= str.len) | ||
|  |         ++ret; /* snprintf() reserves the last character to write \0 */ | ||
|  |     return ret; | ||
|  | #endif
 | ||
|  | } | ||
|  | #endif // (!C4CORE_HAVE_STD_FROMCHARS)
 | ||
|  | 
 | ||
|  | 
 | ||
|  | #if (!C4CORE_HAVE_STD_FROMCHARS) && (!C4CORE_HAVE_FAST_FLOAT)
 | ||
|  | /** scans a string using the given type format, while at the same time
 | ||
|  |  * allowing non-null-terminated strings AND guaranteeing that the given | ||
|  |  * string length is strictly respected, so that no buffer overflows | ||
|  |  * might occur. */ | ||
|  | template<typename T> | ||
|  | inline size_t scan_one(csubstr str, const char *type_fmt, T *v) | ||
|  | { | ||
|  |     /* snscanf() is absolutely needed here as we must be sure that
 | ||
|  |      * str.len is strictly respected, because substr is | ||
|  |      * generally not null-terminated. | ||
|  |      * | ||
|  |      * Alas, there is no snscanf(). | ||
|  |      * | ||
|  |      * So we fake it by using a dynamic format with an explicit | ||
|  |      * field size set to the length of the given span. | ||
|  |      * This trick is taken from: | ||
|  |      * https://stackoverflow.com/a/18368910/5875572 */
 | ||
|  | 
 | ||
|  |     /* this is the actual format we'll use for scanning */ | ||
|  |     char fmt[16]; | ||
|  | 
 | ||
|  |     /* write the length into it. Eg "%12f".
 | ||
|  |      * Also, get the number of characters read from the string. | ||
|  |      * So the final format ends up as "%12f%n"*/ | ||
|  |     int iret = std::snprintf(fmt, sizeof(fmt), "%%" "%zu" "%s" "%%n", str.len, type_fmt); | ||
|  |     /* no nasty surprises, please! */ | ||
|  |     C4_ASSERT(iret >= 0 && size_t(iret) < C4_COUNTOF(fmt)); | ||
|  | 
 | ||
|  |     /* now we scan with confidence that the span length is respected */ | ||
|  |     int num_chars; | ||
|  |     iret = std::sscanf(str.str, fmt, v, &num_chars); | ||
|  |     /* scanf returns the number of successful conversions */ | ||
|  |     if(iret != 1) return csubstr::npos; | ||
|  |     C4_ASSERT(num_chars >= 0); | ||
|  |     return (size_t)(num_chars); | ||
|  | } | ||
|  | #endif // (!C4CORE_HAVE_STD_FROMCHARS) && (!C4CORE_HAVE_FAST_FLOAT)
 | ||
|  | 
 | ||
|  | 
 | ||
|  | #if C4CORE_HAVE_STD_TOCHARS
 | ||
|  | template<class T> | ||
|  | C4_ALWAYS_INLINE size_t rtoa(substr buf, T v, int precision=-1, RealFormat_e formatting=FTOA_FLEX) noexcept | ||
|  | { | ||
|  |     std::to_chars_result result; | ||
|  |     size_t pos = 0; | ||
|  |     if(formatting == FTOA_HEXA) | ||
|  |     { | ||
|  |         if(buf.len > size_t(2)) | ||
|  |         { | ||
|  |             buf.str[0] = '0'; | ||
|  |             buf.str[1] = 'x'; | ||
|  |         } | ||
|  |         pos += size_t(2); | ||
|  |     } | ||
|  |     if(precision == -1) | ||
|  |         result = std::to_chars(buf.str + pos, buf.str + buf.len, v, (std::chars_format)formatting); | ||
|  |     else | ||
|  |         result = std::to_chars(buf.str + pos, buf.str + buf.len, v, (std::chars_format)formatting, precision); | ||
|  |     if(result.ec == std::errc()) | ||
|  |     { | ||
|  |         // all good, no errors.
 | ||
|  |         C4_ASSERT(result.ptr >= buf.str); | ||
|  |         ptrdiff_t delta = result.ptr - buf.str; | ||
|  |         return static_cast<size_t>(delta); | ||
|  |     } | ||
|  |     C4_ASSERT(result.ec == std::errc::value_too_large); | ||
|  |     // This is unfortunate.
 | ||
|  |     //
 | ||
|  |     // When the result can't fit in the given buffer,
 | ||
|  |     // std::to_chars() returns the end pointer it was originally
 | ||
|  |     // given, which is useless because here we would like to know
 | ||
|  |     // _exactly_ how many characters the buffer must have to fit
 | ||
|  |     // the result.
 | ||
|  |     //
 | ||
|  |     // So we take the pessimistic view, and assume as many digits
 | ||
|  |     // as could ever be required:
 | ||
|  |     size_t ret = static_cast<size_t>(std::numeric_limits<T>::max_digits10); | ||
|  |     return ret > buf.len ? ret : buf.len + 1; | ||
|  | } | ||
|  | #endif // C4CORE_HAVE_STD_TOCHARS
 | ||
|  | 
 | ||
|  | 
 | ||
|  | #if C4CORE_HAVE_FAST_FLOAT
 | ||
|  | template<class T> | ||
|  | C4_ALWAYS_INLINE bool scan_rhex(csubstr s, T *C4_RESTRICT val) noexcept | ||
|  | { | ||
|  |     C4_ASSERT(s.len > 0); | ||
|  |     C4_ASSERT(s.str[0] != '-'); | ||
|  |     C4_ASSERT(s.str[0] != '+'); | ||
|  |     C4_ASSERT(!s.begins_with("0x")); | ||
|  |     C4_ASSERT(!s.begins_with("0X")); | ||
|  |     size_t pos = 0; | ||
|  |     // integer part
 | ||
|  |     for( ; pos < s.len; ++pos) | ||
|  |     { | ||
|  |         const char c = s.str[pos]; | ||
|  |         if(c >= '0' && c <= '9') | ||
|  |             *val = *val * T(16) + T(c - '0'); | ||
|  |         else if(c >= 'a' && c <= 'f') | ||
|  |             *val = *val * T(16) + T(c - 'a'); | ||
|  |         else if(c >= 'A' && c <= 'F') | ||
|  |             *val = *val * T(16) + T(c - 'A'); | ||
|  |         else if(c == '.') | ||
|  |         { | ||
|  |             ++pos; | ||
|  |             break; // follow on to mantissa
 | ||
|  |         } | ||
|  |         else if(c == 'p' || c == 'P') | ||
|  |         { | ||
|  |             ++pos; | ||
|  |             goto power; // no mantissa given, jump to power
 | ||
|  |         } | ||
|  |         else | ||
|  |         { | ||
|  |             return false; | ||
|  |         } | ||
|  |     } | ||
|  |     // mantissa
 | ||
|  |     { | ||
|  |         // 0.0625 == 1/16 == value of first digit after the comma
 | ||
|  |         for(T digit = T(0.0625); pos < s.len; ++pos, digit /= T(16)) | ||
|  |         { | ||
|  |             const char c = s.str[pos]; | ||
|  |             if(c >= '0' && c <= '9') | ||
|  |                 *val += digit * T(c - '0'); | ||
|  |             else if(c >= 'a' && c <= 'f') | ||
|  |                 *val += digit * T(c - 'a'); | ||
|  |             else if(c >= 'A' && c <= 'F') | ||
|  |                 *val += digit * T(c - 'A'); | ||
|  |             else if(c == 'p' || c == 'P') | ||
|  |             { | ||
|  |                 ++pos; | ||
|  |                 goto power; // mantissa finished, jump to power
 | ||
|  |             } | ||
|  |             else | ||
|  |             { | ||
|  |                 return false; | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  |     return true; | ||
|  | power: | ||
|  |     if(C4_LIKELY(pos < s.len)) | ||
|  |     { | ||
|  |         if(s.str[pos] == '+') // atoi() cannot handle a leading '+'
 | ||
|  |             ++pos; | ||
|  |         if(C4_LIKELY(pos < s.len)) | ||
|  |         { | ||
|  |             int16_t powval = {}; | ||
|  |             if(C4_LIKELY(atoi(s.sub(pos), &powval))) | ||
|  |             { | ||
|  |                 *val *= ipow<T, int16_t, 16>(powval); | ||
|  |                 return true; | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  |     return false; | ||
|  | } | ||
|  | #endif
 | ||
|  | 
 | ||
|  | } // namespace detail
 | ||
|  | 
 | ||
|  | 
 | ||
|  | #undef _c4appendhex
 | ||
|  | #undef _c4append
 | ||
|  | 
 | ||
|  | 
 | ||
|  | /** Convert a single-precision real number to string.  The string will
 | ||
|  |  * in general be NOT null-terminated.  For FTOA_FLEX, \p precision is | ||
|  |  * the number of significand digits. Otherwise \p precision is the | ||
|  |  * number of decimals. It is safe to call this function with an empty | ||
|  |  * or too-small buffer. | ||
|  |  * | ||
|  |  * @return the size of the buffer needed to write the number | ||
|  |  */ | ||
|  | C4_ALWAYS_INLINE size_t ftoa(substr str, float v, int precision=-1, RealFormat_e formatting=FTOA_FLEX) noexcept | ||
|  | { | ||
|  | #if C4CORE_HAVE_STD_TOCHARS
 | ||
|  |     return detail::rtoa(str, v, precision, formatting); | ||
|  | #else
 | ||
|  |     char fmt[16]; | ||
|  |     detail::get_real_format_str(fmt, precision, formatting, /*length_modifier*/""); | ||
|  |     return detail::print_one(str, fmt, v); | ||
|  | #endif
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /** Convert a double-precision real number to string.  The string will
 | ||
|  |  * in general be NOT null-terminated.  For FTOA_FLEX, \p precision is | ||
|  |  * the number of significand digits. Otherwise \p precision is the | ||
|  |  * number of decimals. It is safe to call this function with an empty | ||
|  |  * or too-small buffer. | ||
|  |  * | ||
|  |  * @return the size of the buffer needed to write the number | ||
|  |  */ | ||
|  | C4_ALWAYS_INLINE size_t dtoa(substr str, double v, int precision=-1, RealFormat_e formatting=FTOA_FLEX) noexcept | ||
|  | { | ||
|  | #if C4CORE_HAVE_STD_TOCHARS
 | ||
|  |     return detail::rtoa(str, v, precision, formatting); | ||
|  | #else
 | ||
|  |     char fmt[16]; | ||
|  |     detail::get_real_format_str(fmt, precision, formatting, /*length_modifier*/"l"); | ||
|  |     return detail::print_one(str, fmt, v); | ||
|  | #endif
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /** Convert a string to a single precision real number.
 | ||
|  |  * The input string must be trimmed to the value, ie | ||
|  |  * no leading or trailing whitespace can be present. | ||
|  |  * @return true iff the conversion succeeded | ||
|  |  * @see atof_first() if the string is not trimmed | ||
|  |  */ | ||
|  | C4_ALWAYS_INLINE bool atof(csubstr str, float * C4_RESTRICT v) noexcept | ||
|  | { | ||
|  |     C4_ASSERT(str.len > 0); | ||
|  |     C4_ASSERT(str.triml(" \r\t\n").len == str.len); | ||
|  | #if C4CORE_HAVE_FAST_FLOAT
 | ||
|  |     // fastfloat cannot parse hexadecimal floats
 | ||
|  |     bool isneg = (str.str[0] == '-'); | ||
|  |     csubstr rem = str.sub(isneg || str.str[0] == '+'); | ||
|  |     if(!(rem.len >= 2 && (rem.str[0] == '0' && (rem.str[1] == 'x' || rem.str[1] == 'X')))) | ||
|  |     { | ||
|  |         fast_float::from_chars_result result; | ||
|  |         result = fast_float::from_chars(str.str, str.str + str.len, *v); | ||
|  |         return result.ec == std::errc(); | ||
|  |     } | ||
|  |     else if(detail::scan_rhex(rem.sub(2), v)) | ||
|  |     { | ||
|  |         *v *= isneg ? -1.f : 1.f; | ||
|  |         return true; | ||
|  |     } | ||
|  |     return false; | ||
|  | #elif C4CORE_HAVE_STD_FROMCHARS
 | ||
|  |     std::from_chars_result result; | ||
|  |     result = std::from_chars(str.str, str.str + str.len, *v); | ||
|  |     return result.ec == std::errc(); | ||
|  | #else
 | ||
|  |     csubstr rem = str.sub(str.str[0] == '-' || str.str[0] == '+'); | ||
|  |     if(!(rem.len >= 2 && (rem.str[0] == '0' && (rem.str[1] == 'x' || rem.str[1] == 'X')))) | ||
|  |         return detail::scan_one(str, "f", v) != csubstr::npos; | ||
|  |     else | ||
|  |         return detail::scan_one(str, "a", v) != csubstr::npos; | ||
|  | #endif
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /** Convert a string to a double precision real number.
 | ||
|  |  * The input string must be trimmed to the value, ie | ||
|  |  * no leading or trailing whitespace can be present. | ||
|  |  * @return true iff the conversion succeeded | ||
|  |  * @see atod_first() if the string is not trimmed | ||
|  |  */ | ||
|  | C4_ALWAYS_INLINE bool atod(csubstr str, double * C4_RESTRICT v) noexcept | ||
|  | { | ||
|  |     C4_ASSERT(str.triml(" \r\t\n").len == str.len); | ||
|  | #if C4CORE_HAVE_FAST_FLOAT
 | ||
|  |     // fastfloat cannot parse hexadecimal floats
 | ||
|  |     bool isneg = (str.str[0] == '-'); | ||
|  |     csubstr rem = str.sub(isneg || str.str[0] == '+'); | ||
|  |     if(!(rem.len >= 2 && (rem.str[0] == '0' && (rem.str[1] == 'x' || rem.str[1] == 'X')))) | ||
|  |     { | ||
|  |         fast_float::from_chars_result result; | ||
|  |         result = fast_float::from_chars(str.str, str.str + str.len, *v); | ||
|  |         return result.ec == std::errc(); | ||
|  |     } | ||
|  |     else if(detail::scan_rhex(rem.sub(2), v)) | ||
|  |     { | ||
|  |         *v *= isneg ? -1. : 1.; | ||
|  |         return true; | ||
|  |     } | ||
|  |     return false; | ||
|  | #elif C4CORE_HAVE_STD_FROMCHARS
 | ||
|  |     std::from_chars_result result; | ||
|  |     result = std::from_chars(str.str, str.str + str.len, *v); | ||
|  |     return result.ec == std::errc(); | ||
|  | #else
 | ||
|  |     csubstr rem = str.sub(str.str[0] == '-' || str.str[0] == '+'); | ||
|  |     if(!(rem.len >= 2 && (rem.str[0] == '0' && (rem.str[1] == 'x' || rem.str[1] == 'X')))) | ||
|  |         return detail::scan_one(str, "lf", v) != csubstr::npos; | ||
|  |     else | ||
|  |         return detail::scan_one(str, "la", v) != csubstr::npos; | ||
|  | #endif
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /** Convert a string to a single precision real number.
 | ||
|  |  * Leading whitespace is skipped until valid characters are found. | ||
|  |  * @return the number of characters read from the string, or npos if | ||
|  |  * conversion was not successful or if the string was empty */ | ||
|  | inline size_t atof_first(csubstr str, float * C4_RESTRICT v) noexcept | ||
|  | { | ||
|  |     csubstr trimmed = str.first_real_span(); | ||
|  |     if(trimmed.len == 0) | ||
|  |         return csubstr::npos; | ||
|  |     if(atof(trimmed, v)) | ||
|  |         return static_cast<size_t>(trimmed.end() - str.begin()); | ||
|  |     return csubstr::npos; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /** Convert a string to a double precision real number.
 | ||
|  |  * Leading whitespace is skipped until valid characters are found. | ||
|  |  * @return the number of characters read from the string, or npos if | ||
|  |  * conversion was not successful or if the string was empty */ | ||
|  | inline size_t atod_first(csubstr str, double * C4_RESTRICT v) noexcept | ||
|  | { | ||
|  |     csubstr trimmed = str.first_real_span(); | ||
|  |     if(trimmed.len == 0) | ||
|  |         return csubstr::npos; | ||
|  |     if(atod(trimmed, v)) | ||
|  |         return static_cast<size_t>(trimmed.end() - str.begin()); | ||
|  |     return csubstr::npos; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | // generic versions
 | ||
|  | 
 | ||
|  | C4_ALWAYS_INLINE size_t xtoa(substr s,  uint8_t v) noexcept { return write_dec(s, v); } | ||
|  | C4_ALWAYS_INLINE size_t xtoa(substr s, uint16_t v) noexcept { return write_dec(s, v); } | ||
|  | C4_ALWAYS_INLINE size_t xtoa(substr s, uint32_t v) noexcept { return write_dec(s, v); } | ||
|  | C4_ALWAYS_INLINE size_t xtoa(substr s, uint64_t v) noexcept { return write_dec(s, v); } | ||
|  | C4_ALWAYS_INLINE size_t xtoa(substr s,   int8_t v) noexcept { return itoa(s, v); } | ||
|  | C4_ALWAYS_INLINE size_t xtoa(substr s,  int16_t v) noexcept { return itoa(s, v); } | ||
|  | C4_ALWAYS_INLINE size_t xtoa(substr s,  int32_t v) noexcept { return itoa(s, v); } | ||
|  | C4_ALWAYS_INLINE size_t xtoa(substr s,  int64_t v) noexcept { return itoa(s, v); } | ||
|  | C4_ALWAYS_INLINE size_t xtoa(substr s,    float v) noexcept { return ftoa(s, v); } | ||
|  | C4_ALWAYS_INLINE size_t xtoa(substr s,   double v) noexcept { return dtoa(s, v); } | ||
|  | 
 | ||
|  | C4_ALWAYS_INLINE size_t xtoa(substr s,  uint8_t v,  uint8_t radix) noexcept { return utoa(s, v, radix); } | ||
|  | C4_ALWAYS_INLINE size_t xtoa(substr s, uint16_t v, uint16_t radix) noexcept { return utoa(s, v, radix); } | ||
|  | C4_ALWAYS_INLINE size_t xtoa(substr s, uint32_t v, uint32_t radix) noexcept { return utoa(s, v, radix); } | ||
|  | C4_ALWAYS_INLINE size_t xtoa(substr s, uint64_t v, uint64_t radix) noexcept { return utoa(s, v, radix); } | ||
|  | C4_ALWAYS_INLINE size_t xtoa(substr s,   int8_t v,   int8_t radix) noexcept { return itoa(s, v, radix); } | ||
|  | C4_ALWAYS_INLINE size_t xtoa(substr s,  int16_t v,  int16_t radix) noexcept { return itoa(s, v, radix); } | ||
|  | C4_ALWAYS_INLINE size_t xtoa(substr s,  int32_t v,  int32_t radix) noexcept { return itoa(s, v, radix); } | ||
|  | C4_ALWAYS_INLINE size_t xtoa(substr s,  int64_t v,  int64_t radix) noexcept { return itoa(s, v, radix); } | ||
|  | 
 | ||
|  | C4_ALWAYS_INLINE size_t xtoa(substr s,  uint8_t v,  uint8_t radix, size_t num_digits) noexcept { return utoa(s, v, radix, num_digits); } | ||
|  | C4_ALWAYS_INLINE size_t xtoa(substr s, uint16_t v, uint16_t radix, size_t num_digits) noexcept { return utoa(s, v, radix, num_digits); } | ||
|  | C4_ALWAYS_INLINE size_t xtoa(substr s, uint32_t v, uint32_t radix, size_t num_digits) noexcept { return utoa(s, v, radix, num_digits); } | ||
|  | C4_ALWAYS_INLINE size_t xtoa(substr s, uint64_t v, uint64_t radix, size_t num_digits) noexcept { return utoa(s, v, radix, num_digits); } | ||
|  | C4_ALWAYS_INLINE size_t xtoa(substr s,   int8_t v,   int8_t radix, size_t num_digits) noexcept { return itoa(s, v, radix, num_digits); } | ||
|  | C4_ALWAYS_INLINE size_t xtoa(substr s,  int16_t v,  int16_t radix, size_t num_digits) noexcept { return itoa(s, v, radix, num_digits); } | ||
|  | C4_ALWAYS_INLINE size_t xtoa(substr s,  int32_t v,  int32_t radix, size_t num_digits) noexcept { return itoa(s, v, radix, num_digits); } | ||
|  | C4_ALWAYS_INLINE size_t xtoa(substr s,  int64_t v,  int64_t radix, size_t num_digits) noexcept { return itoa(s, v, radix, num_digits); } | ||
|  | 
 | ||
|  | C4_ALWAYS_INLINE size_t xtoa(substr s,  float v, int precision, RealFormat_e formatting=FTOA_FLEX) noexcept { return ftoa(s, v, precision, formatting); } | ||
|  | C4_ALWAYS_INLINE size_t xtoa(substr s, double v, int precision, RealFormat_e formatting=FTOA_FLEX) noexcept { return dtoa(s, v, precision, formatting); } | ||
|  | 
 | ||
|  | C4_ALWAYS_INLINE bool atox(csubstr s,  uint8_t *C4_RESTRICT v) noexcept { return atou(s, v); } | ||
|  | C4_ALWAYS_INLINE bool atox(csubstr s, uint16_t *C4_RESTRICT v) noexcept { return atou(s, v); } | ||
|  | C4_ALWAYS_INLINE bool atox(csubstr s, uint32_t *C4_RESTRICT v) noexcept { return atou(s, v); } | ||
|  | C4_ALWAYS_INLINE bool atox(csubstr s, uint64_t *C4_RESTRICT v) noexcept { return atou(s, v); } | ||
|  | C4_ALWAYS_INLINE bool atox(csubstr s,   int8_t *C4_RESTRICT v) noexcept { return atoi(s, v); } | ||
|  | C4_ALWAYS_INLINE bool atox(csubstr s,  int16_t *C4_RESTRICT v) noexcept { return atoi(s, v); } | ||
|  | C4_ALWAYS_INLINE bool atox(csubstr s,  int32_t *C4_RESTRICT v) noexcept { return atoi(s, v); } | ||
|  | C4_ALWAYS_INLINE bool atox(csubstr s,  int64_t *C4_RESTRICT v) noexcept { return atoi(s, v); } | ||
|  | C4_ALWAYS_INLINE bool atox(csubstr s,    float *C4_RESTRICT v) noexcept { return atof(s, v); } | ||
|  | C4_ALWAYS_INLINE bool atox(csubstr s,   double *C4_RESTRICT v) noexcept { return atod(s, v); } | ||
|  | 
 | ||
|  | C4_ALWAYS_INLINE size_t to_chars(substr buf,  uint8_t v) noexcept { return write_dec(buf, v); } | ||
|  | C4_ALWAYS_INLINE size_t to_chars(substr buf, uint16_t v) noexcept { return write_dec(buf, v); } | ||
|  | C4_ALWAYS_INLINE size_t to_chars(substr buf, uint32_t v) noexcept { return write_dec(buf, v); } | ||
|  | C4_ALWAYS_INLINE size_t to_chars(substr buf, uint64_t v) noexcept { return write_dec(buf, v); } | ||
|  | C4_ALWAYS_INLINE size_t to_chars(substr buf,   int8_t v) noexcept { return itoa(buf, v); } | ||
|  | C4_ALWAYS_INLINE size_t to_chars(substr buf,  int16_t v) noexcept { return itoa(buf, v); } | ||
|  | C4_ALWAYS_INLINE size_t to_chars(substr buf,  int32_t v) noexcept { return itoa(buf, v); } | ||
|  | C4_ALWAYS_INLINE size_t to_chars(substr buf,  int64_t v) noexcept { return itoa(buf, v); } | ||
|  | C4_ALWAYS_INLINE size_t to_chars(substr buf,    float v) noexcept { return ftoa(buf, v); } | ||
|  | C4_ALWAYS_INLINE size_t to_chars(substr buf,   double v) noexcept { return dtoa(buf, v); } | ||
|  | 
 | ||
|  | C4_ALWAYS_INLINE bool from_chars(csubstr buf,  uint8_t *C4_RESTRICT v) noexcept { return atou(buf, v); } | ||
|  | C4_ALWAYS_INLINE bool from_chars(csubstr buf, uint16_t *C4_RESTRICT v) noexcept { return atou(buf, v); } | ||
|  | C4_ALWAYS_INLINE bool from_chars(csubstr buf, uint32_t *C4_RESTRICT v) noexcept { return atou(buf, v); } | ||
|  | C4_ALWAYS_INLINE bool from_chars(csubstr buf, uint64_t *C4_RESTRICT v) noexcept { return atou(buf, v); } | ||
|  | C4_ALWAYS_INLINE bool from_chars(csubstr buf,   int8_t *C4_RESTRICT v) noexcept { return atoi(buf, v); } | ||
|  | C4_ALWAYS_INLINE bool from_chars(csubstr buf,  int16_t *C4_RESTRICT v) noexcept { return atoi(buf, v); } | ||
|  | C4_ALWAYS_INLINE bool from_chars(csubstr buf,  int32_t *C4_RESTRICT v) noexcept { return atoi(buf, v); } | ||
|  | C4_ALWAYS_INLINE bool from_chars(csubstr buf,  int64_t *C4_RESTRICT v) noexcept { return atoi(buf, v); } | ||
|  | C4_ALWAYS_INLINE bool from_chars(csubstr buf,    float *C4_RESTRICT v) noexcept { return atof(buf, v); } | ||
|  | C4_ALWAYS_INLINE bool from_chars(csubstr buf,   double *C4_RESTRICT v) noexcept { return atod(buf, v); } | ||
|  | 
 | ||
|  | C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf,  uint8_t *C4_RESTRICT v) noexcept { return atou_first(buf, v); } | ||
|  | C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, uint16_t *C4_RESTRICT v) noexcept { return atou_first(buf, v); } | ||
|  | C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, uint32_t *C4_RESTRICT v) noexcept { return atou_first(buf, v); } | ||
|  | C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, uint64_t *C4_RESTRICT v) noexcept { return atou_first(buf, v); } | ||
|  | C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf,   int8_t *C4_RESTRICT v) noexcept { return atoi_first(buf, v); } | ||
|  | C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf,  int16_t *C4_RESTRICT v) noexcept { return atoi_first(buf, v); } | ||
|  | C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf,  int32_t *C4_RESTRICT v) noexcept { return atoi_first(buf, v); } | ||
|  | C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf,  int64_t *C4_RESTRICT v) noexcept { return atoi_first(buf, v); } | ||
|  | C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf,    float *C4_RESTRICT v) noexcept { return atof_first(buf, v); } | ||
|  | C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf,   double *C4_RESTRICT v) noexcept { return atod_first(buf, v); } | ||
|  | 
 | ||
|  | 
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | // on some platforms, (unsigned) int and (unsigned) long
 | ||
|  | // are not any of the fixed length types above
 | ||
|  | 
 | ||
|  | #define _C4_IF_NOT_FIXED_LENGTH_I(T, ty) C4_ALWAYS_INLINE typename std::enable_if<std::  is_signed<T>::value && !is_fixed_length<T>::value_i, ty>
 | ||
|  | #define _C4_IF_NOT_FIXED_LENGTH_U(T, ty) C4_ALWAYS_INLINE typename std::enable_if<std::is_unsigned<T>::value && !is_fixed_length<T>::value_u, ty>
 | ||
|  | 
 | ||
|  | template <class T> _C4_IF_NOT_FIXED_LENGTH_I(T, size_t)::type xtoa(substr buf, T v) noexcept { return itoa(buf, v); } | ||
|  | template <class T> _C4_IF_NOT_FIXED_LENGTH_U(T, size_t)::type xtoa(substr buf, T v) noexcept { return write_dec(buf, v); } | ||
|  | 
 | ||
|  | template <class T> _C4_IF_NOT_FIXED_LENGTH_I(T, bool  )::type atox(csubstr buf, T *C4_RESTRICT v) noexcept { return atoi(buf, v); } | ||
|  | template <class T> _C4_IF_NOT_FIXED_LENGTH_U(T, bool  )::type atox(csubstr buf, T *C4_RESTRICT v) noexcept { return atou(buf, v); } | ||
|  | 
 | ||
|  | template <class T> _C4_IF_NOT_FIXED_LENGTH_I(T, size_t)::type to_chars(substr buf, T v) noexcept { return itoa(buf, v); } | ||
|  | template <class T> _C4_IF_NOT_FIXED_LENGTH_U(T, size_t)::type to_chars(substr buf, T v) noexcept { return write_dec(buf, v); } | ||
|  | 
 | ||
|  | template <class T> _C4_IF_NOT_FIXED_LENGTH_I(T, bool  )::type from_chars(csubstr buf, T *C4_RESTRICT v) noexcept { return atoi(buf, v); } | ||
|  | template <class T> _C4_IF_NOT_FIXED_LENGTH_U(T, bool  )::type from_chars(csubstr buf, T *C4_RESTRICT v) noexcept { return atou(buf, v); } | ||
|  | 
 | ||
|  | template <class T> _C4_IF_NOT_FIXED_LENGTH_I(T, size_t)::type from_chars_first(csubstr buf, T *C4_RESTRICT v) noexcept { return atoi_first(buf, v); } | ||
|  | template <class T> _C4_IF_NOT_FIXED_LENGTH_U(T, size_t)::type from_chars_first(csubstr buf, T *C4_RESTRICT v) noexcept { return atou_first(buf, v); } | ||
|  | 
 | ||
|  | #undef _C4_IF_NOT_FIXED_LENGTH_I
 | ||
|  | #undef _C4_IF_NOT_FIXED_LENGTH_U
 | ||
|  | 
 | ||
|  | 
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | // for pointers
 | ||
|  | 
 | ||
|  | template <class T> C4_ALWAYS_INLINE size_t xtoa(substr s, T *v) noexcept { return itoa(s, (intptr_t)v, (intptr_t)16); } | ||
|  | template <class T> C4_ALWAYS_INLINE bool   atox(csubstr s, T **v) noexcept { intptr_t tmp; bool ret = atox(s, &tmp); if(ret) { *v = (T*)tmp; } return ret; } | ||
|  | template <class T> C4_ALWAYS_INLINE size_t to_chars(substr s, T *v) noexcept { return itoa(s, (intptr_t)v, (intptr_t)16); } | ||
|  | template <class T> C4_ALWAYS_INLINE bool   from_chars(csubstr buf, T **v) noexcept { intptr_t tmp; bool ret = from_chars(buf, &tmp); if(ret) { *v = (T*)tmp; } return ret; } | ||
|  | template <class T> C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, T **v) noexcept { intptr_t tmp; bool ret = from_chars_first(buf, &tmp); if(ret) { *v = (T*)tmp; } return ret; } | ||
|  | 
 | ||
|  | 
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | /** call to_chars() and return a substr consisting of the
 | ||
|  |  * written portion of the input buffer. Ie, same as to_chars(), | ||
|  |  * but return a substr instead of a size_t. | ||
|  |  * | ||
|  |  * @see to_chars() */ | ||
|  | template<class T> | ||
|  | C4_ALWAYS_INLINE substr to_chars_sub(substr buf, T const& C4_RESTRICT v) noexcept | ||
|  | { | ||
|  |     size_t sz = to_chars(buf, v); | ||
|  |     return buf.left_of(sz <= buf.len ? sz : buf.len); | ||
|  | } | ||
|  | 
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | // bool implementation
 | ||
|  | 
 | ||
|  | C4_ALWAYS_INLINE size_t to_chars(substr buf, bool v) noexcept | ||
|  | { | ||
|  |     int val = v; | ||
|  |     return to_chars(buf, val); | ||
|  | } | ||
|  | 
 | ||
|  | inline bool from_chars(csubstr buf, bool * C4_RESTRICT v) noexcept | ||
|  | { | ||
|  |     if(buf == '0') | ||
|  |     { | ||
|  |         *v = false; return true; | ||
|  |     } | ||
|  |     else if(buf == '1') | ||
|  |     { | ||
|  |         *v = true; return true; | ||
|  |     } | ||
|  |     else if(buf == "false") | ||
|  |     { | ||
|  |         *v = false; return true; | ||
|  |     } | ||
|  |     else if(buf == "true") | ||
|  |     { | ||
|  |         *v = true; return true; | ||
|  |     } | ||
|  |     else if(buf == "False") | ||
|  |     { | ||
|  |         *v = false; return true; | ||
|  |     } | ||
|  |     else if(buf == "True") | ||
|  |     { | ||
|  |         *v = true; return true; | ||
|  |     } | ||
|  |     else if(buf == "FALSE") | ||
|  |     { | ||
|  |         *v = false; return true; | ||
|  |     } | ||
|  |     else if(buf == "TRUE") | ||
|  |     { | ||
|  |         *v = true; return true; | ||
|  |     } | ||
|  |     // fallback to c-style int bools
 | ||
|  |     int val = 0; | ||
|  |     bool ret = from_chars(buf, &val); | ||
|  |     if(C4_LIKELY(ret)) | ||
|  |     { | ||
|  |         *v = (val != 0); | ||
|  |     } | ||
|  |     return ret; | ||
|  | } | ||
|  | 
 | ||
|  | inline size_t from_chars_first(csubstr buf, bool * C4_RESTRICT v) noexcept | ||
|  | { | ||
|  |     csubstr trimmed = buf.first_non_empty_span(); | ||
|  |     if(trimmed.len == 0 || !from_chars(buf, v)) | ||
|  |         return csubstr::npos; | ||
|  |     return trimmed.len; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | // single-char implementation
 | ||
|  | 
 | ||
|  | inline size_t to_chars(substr buf, char v) noexcept | ||
|  | { | ||
|  |     if(buf.len > 0) | ||
|  |     { | ||
|  |         C4_XASSERT(buf.str); | ||
|  |         buf.str[0] = v; | ||
|  |     } | ||
|  |     return 1; | ||
|  | } | ||
|  | 
 | ||
|  | /** extract a single character from a substring
 | ||
|  |  * @note to extract a string instead and not just a single character, use the csubstr overload */ | ||
|  | inline bool from_chars(csubstr buf, char * C4_RESTRICT v) noexcept | ||
|  | { | ||
|  |     if(buf.len != 1) | ||
|  |         return false; | ||
|  |     C4_XASSERT(buf.str); | ||
|  |     *v = buf.str[0]; | ||
|  |     return true; | ||
|  | } | ||
|  | 
 | ||
|  | inline size_t from_chars_first(csubstr buf, char * C4_RESTRICT v) noexcept | ||
|  | { | ||
|  |     if(buf.len < 1) | ||
|  |         return csubstr::npos; | ||
|  |     *v = buf.str[0]; | ||
|  |     return 1; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | // csubstr implementation
 | ||
|  | 
 | ||
|  | inline size_t to_chars(substr buf, csubstr v) noexcept | ||
|  | { | ||
|  |     C4_ASSERT(!buf.overlaps(v)); | ||
|  |     size_t len = buf.len < v.len ? buf.len : v.len; | ||
|  |     // calling memcpy with null strings is undefined behavior
 | ||
|  |     // and will wreak havoc in calling code's branches.
 | ||
|  |     // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637
 | ||
|  |     if(len) | ||
|  |     { | ||
|  |         C4_ASSERT(buf.str != nullptr); | ||
|  |         C4_ASSERT(v.str != nullptr); | ||
|  |         memcpy(buf.str, v.str, len); | ||
|  |     } | ||
|  |     return v.len; | ||
|  | } | ||
|  | 
 | ||
|  | inline bool from_chars(csubstr buf, csubstr *C4_RESTRICT v) noexcept | ||
|  | { | ||
|  |     *v = buf; | ||
|  |     return true; | ||
|  | } | ||
|  | 
 | ||
|  | inline size_t from_chars_first(substr buf, csubstr * C4_RESTRICT v) noexcept | ||
|  | { | ||
|  |     csubstr trimmed = buf.first_non_empty_span(); | ||
|  |     if(trimmed.len == 0) | ||
|  |         return csubstr::npos; | ||
|  |     *v = trimmed; | ||
|  |     return static_cast<size_t>(trimmed.end() - buf.begin()); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | // substr
 | ||
|  | 
 | ||
|  | inline size_t to_chars(substr buf, substr v) noexcept | ||
|  | { | ||
|  |     C4_ASSERT(!buf.overlaps(v)); | ||
|  |     size_t len = buf.len < v.len ? buf.len : v.len; | ||
|  |     // calling memcpy with null strings is undefined behavior
 | ||
|  |     // and will wreak havoc in calling code's branches.
 | ||
|  |     // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637
 | ||
|  |     if(len) | ||
|  |     { | ||
|  |         C4_ASSERT(buf.str != nullptr); | ||
|  |         C4_ASSERT(v.str != nullptr); | ||
|  |         memcpy(buf.str, v.str, len); | ||
|  |     } | ||
|  |     return v.len; | ||
|  | } | ||
|  | 
 | ||
|  | inline bool from_chars(csubstr buf, substr * C4_RESTRICT v) noexcept | ||
|  | { | ||
|  |     C4_ASSERT(!buf.overlaps(*v)); | ||
|  |     // is the destination buffer wide enough?
 | ||
|  |     if(v->len >= buf.len) | ||
|  |     { | ||
|  |         // calling memcpy with null strings is undefined behavior
 | ||
|  |         // and will wreak havoc in calling code's branches.
 | ||
|  |         // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637
 | ||
|  |         if(buf.len) | ||
|  |         { | ||
|  |             C4_ASSERT(buf.str != nullptr); | ||
|  |             C4_ASSERT(v->str != nullptr); | ||
|  |             memcpy(v->str, buf.str, buf.len); | ||
|  |         } | ||
|  |         v->len = buf.len; | ||
|  |         return true; | ||
|  |     } | ||
|  |     return false; | ||
|  | } | ||
|  | 
 | ||
|  | inline size_t from_chars_first(csubstr buf, substr * C4_RESTRICT v) noexcept | ||
|  | { | ||
|  |     csubstr trimmed = buf.first_non_empty_span(); | ||
|  |     C4_ASSERT(!trimmed.overlaps(*v)); | ||
|  |     if(C4_UNLIKELY(trimmed.len == 0)) | ||
|  |         return csubstr::npos; | ||
|  |     size_t len = trimmed.len > v->len ? v->len : trimmed.len; | ||
|  |     // calling memcpy with null strings is undefined behavior
 | ||
|  |     // and will wreak havoc in calling code's branches.
 | ||
|  |     // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637
 | ||
|  |     if(len) | ||
|  |     { | ||
|  |         C4_ASSERT(buf.str != nullptr); | ||
|  |         C4_ASSERT(v->str != nullptr); | ||
|  |         memcpy(v->str, trimmed.str, len); | ||
|  |     } | ||
|  |     if(C4_UNLIKELY(trimmed.len > v->len)) | ||
|  |         return csubstr::npos; | ||
|  |     return static_cast<size_t>(trimmed.end() - buf.begin()); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | //-----------------------------------------------------------------------------
 | ||
|  | 
 | ||
|  | template<size_t N> | ||
|  | inline size_t to_chars(substr buf, const char (& C4_RESTRICT v)[N]) noexcept | ||
|  | { | ||
|  |     csubstr sp(v); | ||
|  |     return to_chars(buf, sp); | ||
|  | } | ||
|  | 
 | ||
|  | inline size_t to_chars(substr buf, const char * C4_RESTRICT v) noexcept | ||
|  | { | ||
|  |     return to_chars(buf, to_csubstr(v)); | ||
|  | } | ||
|  | 
 | ||
|  | } // namespace c4
 | ||
|  | 
 | ||
|  | #ifdef _MSC_VER
 | ||
|  | #   pragma warning(pop)
 | ||
|  | #endif
 | ||
|  | 
 | ||
|  | #if defined(__clang__)
 | ||
|  | #   pragma clang diagnostic pop
 | ||
|  | #elif defined(__GNUC__)
 | ||
|  | #   pragma GCC diagnostic pop
 | ||
|  | #endif
 | ||
|  | 
 | ||
|  | #endif /* _C4_CHARCONV_HPP_ */
 |