mirror of https://github.com/PCSX2/pcsx2.git
2671 lines
97 KiB
C++
2671 lines
97 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.
|
|
*/
|
|
|
|
#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
|
|
|
|
#if defined(__clang__)
|
|
#define C4_NO_UBSAN_IOVRFLW __attribute__((no_sanitize("signed-integer-overflow")))
|
|
#elif defined(__GNUC__)
|
|
#if __GNUC__ > 7
|
|
#define C4_NO_UBSAN_IOVRFLW __attribute__((no_sanitize("signed-integer-overflow")))
|
|
#else
|
|
#define C4_NO_UBSAN_IOVRFLW
|
|
#endif
|
|
#else
|
|
#define C4_NO_UBSAN_IOVRFLW
|
|
#endif
|
|
|
|
|
|
namespace c4 {
|
|
|
|
/** @defgroup doc_charconv Charconv utilities
|
|
*
|
|
* Lightweight, very fast generic type-safe wrappers for converting
|
|
* individual values to/from strings. These are the main generic
|
|
* functions:
|
|
* - @ref doc_to_chars and its alias @ref doc_xtoa: implemented by calling @ref itoa()/@ref utoa()/@ref ftoa()/@ref dtoa() (or generically @ref xtoa())
|
|
* - @ref doc_from_chars and its alias @ref doc_atox: implemented by calling @ref atoi()/@ref atou()/@ref atof()/@ref atod() (or generically @ref atox())
|
|
* - @ref to_chars_sub()
|
|
* - @ref from_chars_first()
|
|
* - @ref xtoa()/@ref atox() are implemented in terms @ref write_dec()/@ref read_dec() et al (see @ref doc_write/@ref doc_read())
|
|
*
|
|
* And also some modest brag is in order: these functions are really
|
|
* fast: faster even than C++17 `std::to_chars()` and
|
|
* `std::to_chars()`, and many dozens of times faster than the
|
|
* iostream abominations.
|
|
*
|
|
* For example, here are some benchmark comparisons for @ref
|
|
* doc_from_chars (link leads to the main project README, where these
|
|
* results are shown more systematically).
|
|
*
|
|
* <table>
|
|
* <caption id="atox-i64-results">atox,int64_t</caption>
|
|
* <tr><th>g++12, linux <th>Visual Studio 2019
|
|
* <tr><td> \image html linux-x86_64-gxx12.1-Release-c4core-bm-charconv-atox-mega_bytes_per_second-i64.png <td> \image html windows-x86_64-vs2019-Release-c4core-bm-charconv-atox-mega_bytes_per_second-i64.png
|
|
* </table>
|
|
*
|
|
* <table>
|
|
* <caption id="xtoa-i64-results">xtoa,int64_t</caption>
|
|
* <tr><th>g++12, linux <th>Visual Studio 2019
|
|
* <tr><td> \image html linux-x86_64-gxx12.1-Release-c4core-bm-charconv-xtoa-mega_bytes_per_second-i64.png <td> \image html windows-x86_64-vs2019-Release-c4core-bm-charconv-xtoa-mega_bytes_per_second-i64.png
|
|
* </table>
|
|
*
|
|
* To parse floating point, c4core uses
|
|
* [fastfloat](https://github.com/fastfloat/fast_float), which is
|
|
* extremely fast, by an even larger factor:
|
|
*
|
|
* <table>
|
|
* <caption id="atox-float-results">atox,float</caption>
|
|
* <tr><th>g++12, linux <th>Visual Studio 2019
|
|
* <tr><td> \image html linux-x86_64-gxx12.1-Release-c4core-bm-charconv-atof-mega_bytes_per_second-float.png <td> \image html windows-x86_64-vs2019-Release-c4core-bm-charconv-atof-mega_bytes_per_second-float.png
|
|
* </table>
|
|
*
|
|
* @{
|
|
*/
|
|
|
|
#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
|
|
|
|
/** @cond dev */
|
|
/** 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
|
|
};
|
|
};
|
|
/** @endcond */
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#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
|
|
|
|
/** @cond dev */
|
|
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; } }
|
|
|
|
/** @endcond */
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
/** @defgroup doc_digits Get number of digits
|
|
*
|
|
* @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
|
|
*
|
|
* @{
|
|
*/
|
|
|
|
/** decimal digits for 8 bit integers */
|
|
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));
|
|
}
|
|
|
|
/** decimal digits for 16 bit integers */
|
|
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);
|
|
}
|
|
|
|
/** decimal digits for 32 bit integers */
|
|
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);
|
|
}
|
|
|
|
/** decimal digits for 64 bit integers */
|
|
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;
|
|
}
|
|
|
|
|
|
/** return the number of digits required to encode an hexadecimal number. */
|
|
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;
|
|
}
|
|
|
|
/** return the number of digits required to encode a binary number. */
|
|
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;
|
|
}
|
|
|
|
/** return the number of digits required to encode an octal number. */
|
|
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;
|
|
}
|
|
}
|
|
|
|
/** @} */
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
|
|
/** @cond dev */
|
|
namespace detail {
|
|
C4_INLINE_CONSTEXPR const char hexchars[] = "0123456789abcdef";
|
|
C4_INLINE_CONSTEXPR const char digits0099[] =
|
|
"0001020304050607080910111213141516171819"
|
|
"2021222324252627282930313233343536373839"
|
|
"4041424344454647484950515253545556575859"
|
|
"6061626364656667686970717273747576777879"
|
|
"8081828384858687888990919293949596979899";
|
|
} // namespace detail
|
|
/** @endcond */
|
|
|
|
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
|
|
|
|
/** @defgroup doc_write_unchecked Write with known number of digits
|
|
*
|
|
* Writes a value without checking the buffer length with regards to
|
|
* the required number of digits to encode the value. It is the
|
|
* responsibility of the caller to ensure that the provided number of
|
|
* digits is enough to write the given value. Notwithstanding the
|
|
* name, assertions are liberally performed, so this code is safe.
|
|
*
|
|
* @{ */
|
|
|
|
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_unchecked
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
|
|
/** @defgroup doc_write Write a value
|
|
*
|
|
* Writes a value without checking the buffer length
|
|
* decimal number -- but asserting.
|
|
*
|
|
* @{ */
|
|
|
|
/** 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;
|
|
}
|
|
|
|
|
|
/** @cond dev */
|
|
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
|
|
/** @endcond */
|
|
|
|
|
|
/** 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);
|
|
}
|
|
|
|
/** @} */ // write
|
|
|
|
C4_SUPPRESS_WARNING_GCC_POP
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
C4_SUPPRESS_WARNING_MSVC_PUSH
|
|
C4_SUPPRESS_WARNING_MSVC(4365) // '=': conversion from 'int' to 'I', signed/unsigned mismatch
|
|
|
|
/** @defgroup doc_read Read a value
|
|
*
|
|
* @{ */
|
|
|
|
/** 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_NO_UBSAN_IOVRFLW
|
|
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_NO_UBSAN_IOVRFLW
|
|
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_NO_UBSAN_IOVRFLW
|
|
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_NO_UBSAN_IOVRFLW
|
|
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")
|
|
|
|
/** @cond dev */
|
|
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
|
|
/** @endcond */
|
|
|
|
|
|
/** @defgroup doc_itoa itoa: signed to chars
|
|
*
|
|
* @{ */
|
|
|
|
/** 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);
|
|
}
|
|
|
|
/** @} */
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
|
|
/** @defgroup doc_utoa utoa: unsigned to chars
|
|
*
|
|
* @{ */
|
|
|
|
/** 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
|
|
|
|
/** @} */
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
|
|
/** @defgroup doc_atoi atoi: chars to signed
|
|
*
|
|
* @{ */
|
|
|
|
/** 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 a positive sign is not accepted. ie, the string must not
|
|
* start with '+'
|
|
*
|
|
* @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. See @ref doc_overflows and @ref
|
|
* doc_overflow_checked for overflow checking utilities.
|
|
*
|
|
* @see atoi_first() if the string is not trimmed to the value to read. */
|
|
template<class T>
|
|
C4_NO_UBSAN_IOVRFLW
|
|
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;
|
|
}
|
|
|
|
/** @} */
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
|
|
/** @defgroup doc_atou atou: chars to unsigned
|
|
*
|
|
* @{ */
|
|
|
|
/** 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. See @ref
|
|
* doc_overflows and @ref doc_overflow_checked for overflow checking
|
|
* utilities.
|
|
*
|
|
* @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
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
|
|
/** @cond dev */
|
|
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
|
|
/** @endcond */
|
|
|
|
|
|
/** @defgroup doc_overflows overflows: does a number string overflow a type
|
|
*
|
|
* @{ */
|
|
|
|
/** Test if the following string would overflow when converted to
|
|
* associated integral types; this function is dispatched with SFINAE
|
|
* to handle differently signed and unsigned types.
|
|
* @return true if number will overflow, false if it fits (or doesn't parse)
|
|
* @see doc_overflow_checked for format specifiers to enforce no-overflow reads
|
|
*/
|
|
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 integral types; this function is dispatched with SFINAE
|
|
* to handle differently signed and unsigned types.
|
|
*
|
|
* @return true if number will overflow, false if it fits (or doesn't parse)
|
|
* @see doc_overflow_checked for format specifiers to enforce no-overflow reads
|
|
*/
|
|
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());
|
|
}
|
|
|
|
/** @} */
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
|
|
/** @cond dev */
|
|
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
|
|
/** @endcond */
|
|
|
|
|
|
#undef _c4appendhex
|
|
#undef _c4append
|
|
|
|
|
|
/** @defgroup doc_ftoa ftoa: float32 to chars
|
|
*
|
|
* @{ */
|
|
|
|
/** 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
|
|
}
|
|
|
|
/** @} */
|
|
|
|
|
|
/** @defgroup doc_dtoa dtoa: float64 to chars
|
|
*
|
|
* @{ */
|
|
|
|
/** 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
|
|
}
|
|
|
|
/** @} */
|
|
|
|
|
|
/** @defgroup doc_atof atof: chars to float32
|
|
*
|
|
* @{ */
|
|
|
|
/** 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 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;
|
|
}
|
|
|
|
/** @} */
|
|
|
|
|
|
/** @defgroup doc_atod atod: chars to float64
|
|
*
|
|
* @{ */
|
|
|
|
/** 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 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
|
|
|
|
/** @cond dev */
|
|
// 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>
|
|
/** @endcond*/
|
|
|
|
|
|
/** @defgroup doc_xtoa xtoa: generic value to chars
|
|
*
|
|
* Dispatches to the most appropriate and efficient conversion
|
|
* function
|
|
*
|
|
* @{ */
|
|
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); }
|
|
|
|
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_ALWAYS_INLINE size_t xtoa(substr s, T *v) noexcept { return itoa(s, (intptr_t)v, (intptr_t)16); }
|
|
|
|
/** @} */
|
|
|
|
/** @defgroup doc_atox atox: generic chars to value
|
|
*
|
|
* Dispatches to the most appropriate and efficient conversion
|
|
* function
|
|
*
|
|
* @{ */
|
|
|
|
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); }
|
|
|
|
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_ALWAYS_INLINE bool atox(csubstr s, T **v) noexcept { intptr_t tmp; bool ret = atox(s, &tmp); if(ret) { *v = (T*)tmp; } return ret; }
|
|
|
|
/** @} */
|
|
|
|
|
|
/** @defgroup doc_to_chars to_chars: generalized chars to value
|
|
*
|
|
* 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.
|
|
*
|
|
* Dispatches to the most appropriate and efficient conversion
|
|
* function.
|
|
*
|
|
* @see write_dec, doc_utoa, doc_itoa, doc_ftoa, doc_dtoa
|
|
*
|
|
* @warning When serializing floating point values (float or double),
|
|
* be aware that because it uses defaults, to_chars() may cause a
|
|
* truncation of the precision. To enforce a particular precision, use
|
|
* for example @ref c4::fmt::real, or call directly @ref c4::ftoa or
|
|
* @ref c4::dtoa.
|
|
*
|
|
* @{ */
|
|
|
|
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); }
|
|
|
|
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_ALWAYS_INLINE size_t to_chars(substr s, T *v) noexcept { return itoa(s, (intptr_t)v, (intptr_t)16); }
|
|
|
|
/** @} */
|
|
|
|
|
|
/** @defgroup doc_from_chars from_chars: generalized chars to value
|
|
*
|
|
* 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. See @ref doc_overflows and @ref
|
|
* doc_overflow_checked for facilities enforcing no-overflow.
|
|
*
|
|
* Dispatches to the most appropriate and efficient conversion
|
|
* function
|
|
*
|
|
* @see doc_from_chars_first, atou, atoi, atof, atod
|
|
* @{ */
|
|
|
|
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); }
|
|
|
|
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_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; }
|
|
|
|
/** @defgroup doc_from_chars_first from_chars_first: generalized chars to value
|
|
*
|
|
* Read the first valid sequence of characters from the string,
|
|
* skipping leading whitespace, and convert it using @ref doc_from_chars .
|
|
* Return the number of characters read for converting.
|
|
*
|
|
* Dispatches to the most appropriate and efficient conversion
|
|
* function.
|
|
*
|
|
* @see atou_first, atoi_first, atof_first, atod_first
|
|
* @{ */
|
|
|
|
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); }
|
|
|
|
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); }
|
|
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; }
|
|
|
|
/** @} */
|
|
|
|
/** @} */
|
|
|
|
#undef _C4_IF_NOT_FIXED_LENGTH_I
|
|
#undef _C4_IF_NOT_FIXED_LENGTH_U
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
/** 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.
|
|
* Convert the given value to a string using to_chars(), and
|
|
* return the resulting string, up to and including the last
|
|
* written character.
|
|
* @ingroup doc_to_chars
|
|
* @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
|
|
|
|
/** @ingroup doc_to_chars */
|
|
C4_ALWAYS_INLINE size_t to_chars(substr buf, bool v) noexcept
|
|
{
|
|
int val = v;
|
|
return to_chars(buf, val);
|
|
}
|
|
|
|
/** @ingroup doc_from_chars */
|
|
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;
|
|
}
|
|
|
|
/** @ingroup doc_from_chars_first */
|
|
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
|
|
|
|
/** @ingroup doc_to_chars */
|
|
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
|
|
* @ingroup doc_from_chars
|
|
* */
|
|
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;
|
|
}
|
|
|
|
/** @ingroup doc_from_chars_first */
|
|
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
|
|
|
|
/** @ingroup doc_to_chars */
|
|
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;
|
|
}
|
|
|
|
/** @ingroup doc_from_chars */
|
|
inline bool from_chars(csubstr buf, csubstr *C4_RESTRICT v) noexcept
|
|
{
|
|
*v = buf;
|
|
return true;
|
|
}
|
|
|
|
/** @ingroup doc_from_chars_first */
|
|
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
|
|
|
|
/** @ingroup doc_to_chars */
|
|
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;
|
|
}
|
|
|
|
/** @ingroup doc_from_chars */
|
|
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;
|
|
}
|
|
|
|
/** @ingroup doc_from_chars_first */
|
|
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());
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
/** @ingroup doc_to_chars */
|
|
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);
|
|
}
|
|
|
|
/** @ingroup doc_to_chars */
|
|
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_ */
|