diff --git a/.circleci/config.yml b/.circleci/config.yml index 477d4fa..f59d9e4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,11 +2,15 @@ version: 2.1 jobs: - clang_build_with_dox: + debug_clang9_dox: docker: - image: marzer/misc_cpp17_dev:latest steps: - checkout + - run: + name: Initializing locales + command: | + sudo locale-gen 'en_US.utf8' 'ja_JP.utf8' 'de_DE.utf8' 'it_IT.utf8' 'tr_TR.utf8' 'fi_FI.utf8' 'fr_FR.utf8' 'zh_CN.utf8' - run: name: Checking toml.hpp command: | @@ -17,14 +21,10 @@ jobs: git submodule update --init extern/Catch2 git submodule update --init extern/tloptional - run: - name: Building with clang + name: Building and testing with clang 9 command: | - CXX=clang++-9 meson build-clang - cd build-clang && ninja -v -j 4 - - run: - name: Running tests - command: | - cd build-clang && ninja test + CXX=clang++-9 meson build --buildtype=debug + cd build && ninja -v && ninja test - run: name: Generating documentation command: | @@ -35,11 +35,15 @@ jobs: paths: html - clang_build: + debug_clang9: docker: - image: marzer/misc_cpp17_dev:latest steps: - checkout + - run: + name: Initializing locales + command: | + sudo locale-gen 'en_US.utf8' 'ja_JP.utf8' 'de_DE.utf8' 'it_IT.utf8' 'tr_TR.utf8' 'fi_FI.utf8' 'fr_FR.utf8' 'zh_CN.utf8' - run: name: Checking toml.hpp command: | @@ -50,35 +54,77 @@ jobs: git submodule update --init extern/Catch2 git submodule update --init extern/tloptional - run: - name: Building with clang + name: Building and testing with clang 9 command: | - CXX=clang++-9 meson build-clang - cd build-clang && ninja -v -j 4 - - run: - name: Running tests - command: | - cd build-clang && ninja test + CXX=clang++-9 meson build --buildtype=debug + cd build && ninja -v && ninja test - gcc_build: + release_clang9: docker: - image: marzer/misc_cpp17_dev:latest steps: - checkout + - run: + name: Initializing locales + command: | + sudo locale-gen 'en_US.utf8' 'ja_JP.utf8' 'de_DE.utf8' 'it_IT.utf8' 'tr_TR.utf8' 'fi_FI.utf8' 'fr_FR.utf8' 'zh_CN.utf8' + - run: + name: Checking toml.hpp + command: | + cd python && python3 ci_single_header_check.py - run: name: Pulling submodules command: | git submodule update --init extern/Catch2 git submodule update --init extern/tloptional - run: - name: Building with gcc + name: Building and testing with clang 9 command: | - CXX=g++-9 meson build-gcc - cd build-gcc && ninja -v -j 4 + CXX=clang++-9 meson build --buildtype=release + cd build && ninja -v && ninja test + + + debug_gcc9: + docker: + - image: marzer/misc_cpp17_dev:latest + steps: + - checkout - run: - name: Running tests + name: Initializing locales command: | - cd build-gcc && ninja test + sudo locale-gen 'en_US.utf8' 'ja_JP.utf8' 'de_DE.utf8' 'it_IT.utf8' 'tr_TR.utf8' 'fi_FI.utf8' 'fr_FR.utf8' 'zh_CN.utf8' + - run: + name: Pulling submodules + command: | + git submodule update --init extern/Catch2 + git submodule update --init extern/tloptional + - run: + name: Building and testing with gcc9 + command: | + CXX=g++-9 meson build --buildtype=debug + cd build && ninja -v -j 4 && ninja test + + + release_gcc9: + docker: + - image: marzer/misc_cpp17_dev:latest + steps: + - checkout + - run: + name: Initializing locales + command: | + sudo locale-gen 'en_US.utf8' 'ja_JP.utf8' 'de_DE.utf8' 'it_IT.utf8' 'tr_TR.utf8' 'fi_FI.utf8' 'fr_FR.utf8' 'zh_CN.utf8' + - run: + name: Pulling submodules + command: | + git submodule update --init extern/Catch2 + git submodule update --init extern/tloptional + - run: + name: Building and testing with gcc9 + command: | + CXX=g++-9 meson build --buildtype=release + cd build && ninja -v -j 4 && ninja test deploy_dox: @@ -110,16 +156,20 @@ workflows: version: 2 build: jobs: - - clang_build_with_dox: + - debug_clang9_dox: filters: branches: only: master - - clang_build: + - debug_clang9: filters: branches: ignore: master - - gcc_build + - release_clang9 + - debug_gcc9 + - release_gcc9 - deploy_dox: requires: - - clang_build_with_dox - - gcc_build + - debug_clang9_dox + - release_clang9 + - debug_gcc9 + - release_gcc9 diff --git a/docs/tomlplusplus.css b/docs/tomlplusplus.css index 7fa50b0..da24378 100644 --- a/docs/tomlplusplus.css +++ b/docs/tomlplusplus.css @@ -94,7 +94,7 @@ pre a.tpp-injected } } -@media screen and (max-width: 576px) +@media screen and (max-width: 575px) { nav .m-thin, nav .github { diff --git a/include/toml++/toml.h b/include/toml++/toml.h index 601c844..7b7a928 100644 --- a/include/toml++/toml.h +++ b/include/toml++/toml.h @@ -36,6 +36,7 @@ // macro hygiene #if TOML_UNDEF_MACROS + #undef TOML_INTEGER_CHARCONV #undef TOML_FLOATING_POINT_CHARCONV #undef TOML_GNU_ATTR #undef TOML_PUSH_WARNINGS diff --git a/include/toml++/toml_common.h b/include/toml++/toml_common.h index c28f2f6..b818aa5 100644 --- a/include/toml++/toml_common.h +++ b/include/toml++/toml_common.h @@ -192,9 +192,18 @@ #ifndef TOML_DISABLE_INIT_WARNINGS #define TOML_DISABLE_INIT_WARNINGS #endif +#ifndef TOML_INTEGER_CHARCONV + #define TOML_INTEGER_CHARCONV 1 +#endif #ifndef TOML_FLOATING_POINT_CHARCONV #define TOML_FLOATING_POINT_CHARCONV 1 #endif +#if (TOML_INTEGER_CHARCONV || TOML_FLOATING_POINT_CHARCONV) && !__has_include() + #undef TOML_INTEGER_CHARCONV + #undef TOML_FLOATING_POINT_CHARCONV + #define TOML_INTEGER_CHARCONV 0 + #define TOML_FLOATING_POINT_CHARCONV 0 +#endif #ifndef TOML_PUSH_WARNINGS #define TOML_PUSH_WARNINGS #endif @@ -298,7 +307,6 @@ TOML_PUSH_WARNINGS TOML_DISABLE_ALL_WARNINGS - #if __has_include() #include #endif @@ -310,22 +318,20 @@ TOML_DISABLE_ALL_WARNINGS #include #include #include -#include -#ifndef TOML_ASSERT - #if !defined(NDEBUG) || defined(_DEBUG) || defined(DEBUG) - #include - #define TOML_ASSERT(expr) assert(expr) - #else - #define TOML_ASSERT(expr) (void)0 - #endif -#endif #ifndef TOML_OPTIONAL_TYPE #include #endif #if TOML_EXCEPTIONS #include #endif - +#ifndef TOML_ASSERT + #ifdef NDEBUG + #define TOML_ASSERT(expr) (void)0 + #else + #include + #define TOML_ASSERT(expr) assert(expr) + #endif +#endif TOML_POP_WARNINGS #if TOML_CHAR_8_STRINGS @@ -893,9 +899,9 @@ namespace toml::impl "date-time"sv }; - #define TOML_P2S_DECL(linkage, type) \ + #define TOML_P2S_DECL(Linkage, Type) \ template \ - linkage void print_to_stream(type, std::basic_ostream&) + Linkage void print_to_stream(Type, std::basic_ostream&) TOML_P2S_DECL(TOML_ALWAYS_INLINE, int8_t); TOML_P2S_DECL(TOML_ALWAYS_INLINE, int16_t); diff --git a/include/toml++/toml_default_formatter.h b/include/toml++/toml_default_formatter.h index 1a33007..62b8fea 100644 --- a/include/toml++/toml_default_formatter.h +++ b/include/toml++/toml_default_formatter.h @@ -210,6 +210,7 @@ namespace toml size_t child_table_array_count{}; for (auto&& [child_k, child_v] : child_tbl) { + (void)child_k; const auto child_type = child_v.type(); switch (child_type) { diff --git a/include/toml++/toml_node_view.h b/include/toml++/toml_node_view.h index 4463be3..9b2b51b 100644 --- a/include/toml++/toml_node_view.h +++ b/include/toml++/toml_node_view.h @@ -167,6 +167,9 @@ namespace toml /// \brief Returns a pointer to the viewed node as a toml::value, if it is one. [[nodiscard]] auto as_date_time() const noexcept { return as(); } + TOML_PUSH_WARNINGS + TOML_DISABLE_INIT_WARNINGS + /// \brief Gets the raw value contained by the referenced node. /// /// \tparam U One of the TOML value types. Can also be a string_view. @@ -182,6 +185,8 @@ namespace toml return {}; } + TOML_POP_WARNINGS + /// \brief Gets the raw value contained by the referenced node, or a default. /// /// \tparam U Default value type. Must be (or be promotable to) one of the TOML value types. diff --git a/include/toml++/toml_parser_impl.h b/include/toml++/toml_parser_impl.h index 53c0809..3b301f9 100644 --- a/include/toml++/toml_parser_impl.h +++ b/include/toml++/toml_parser_impl.h @@ -8,12 +8,16 @@ #error This is an implementation-only header. #endif -#if !TOML_FLOATING_POINT_CHARCONV - TOML_PUSH_WARNINGS - TOML_DISABLE_ALL_WARNINGS - #include - TOML_POP_WARNINGS +TOML_PUSH_WARNINGS +TOML_DISABLE_ALL_WARNINGS +#include +#if TOML_INTEGER_CHARCONV || TOML_FLOATING_POINT_CHARCONV + #include #endif +#if !TOML_INTEGER_CHARCONV || !TOML_FLOATING_POINT_CHARCONV + #include +#endif +TOML_POP_WARNINGS namespace toml::impl { @@ -31,7 +35,7 @@ namespace toml::impl #define TOML_NORETURN [[noreturn]] #endif - template struct parse_integer_traits; + template struct parse_integer_traits; template <> struct parse_integer_traits<2> final { static constexpr auto qualifier = "binary"sv; @@ -74,6 +78,11 @@ namespace toml::impl inline namespace abi_impl_noex { #endif #endif + #ifdef NDEBUG + #define TOML_NOT_EOF TOML_ASSUME(cp != nullptr) + #else + #define TOML_NOT_EOF TOML_ASSERT(cp != nullptr) + #endif class parser final { @@ -151,6 +160,7 @@ namespace toml::impl #else { std::ostringstream ss; + ss.imbue(std::locale::classic()); ss.precision(std::numeric_limits::digits10 + 1); ss << arg; const auto str = std::move(ss).str(); @@ -161,8 +171,22 @@ namespace toml::impl } else if constexpr (std::is_integral_v) { - const auto result = std::to_chars(ptr, buf + N, arg); - ptr = result.ptr; + #if TOML_INTEGER_CHARCONV + { + const auto result = std::to_chars(ptr, buf + N, arg); + ptr = result.ptr; + } + #else + { + std::ostringstream ss; + ss.imbue(std::locale::classic()); + using cast_type = std::conditional_t, int64_t, uint64_t>; + ss << static_cast(arg); + const auto str = std::move(ss).str(); + std::memcpy(ptr, str.c_str(), str.length()); + ptr += str.length(); + } + #endif } } @@ -203,7 +227,7 @@ namespace toml::impl void advance() TOML_MAY_THROW { TOML_ERROR_CHECK(); - TOML_ASSERT(cp); + TOML_NOT_EOF; prev_pos = cp->position; cp = reader.read_next(); @@ -385,17 +409,18 @@ namespace toml::impl return true; } - template + template [[nodiscard]] TOML_NEVER_INLINE size_t consume_variable_length_digit_sequence(T(&buffer)[N]) TOML_MAY_THROW { TOML_ERROR_CHECK({}); + using traits = parse_integer_traits; size_t i = {}; for (; i < N; i++) { - if (!cp || !is_decimal_digit(*cp)) + if (!cp || !traits::is_digit(*cp)) break; buffer[i] = static_cast(*cp - U'0'); advance(); @@ -410,7 +435,8 @@ namespace toml::impl string parse_basic_string() TOML_MAY_THROW { TOML_ERROR_CHECK({}); - TOML_ASSERT(cp && *cp == U'"'); + TOML_NOT_EOF; + TOML_ASSERT(*cp == U'"'); const auto eof_check = [this]() TOML_MAY_THROW { @@ -429,7 +455,8 @@ namespace toml::impl TOML_ERROR_CHECK({}); string str; - bool escaped = false, skipping_whitespace = false; + bool escaped = false; + [[maybe_unused]] bool skipping_whitespace = false; while (cp) { if (escaped) @@ -451,8 +478,10 @@ namespace toml::impl // skip the escaped character const auto escaped_codepoint = cp->value; advance(); + eof_check(); TOML_ERROR_CHECK({}); + TOML_NOT_EOF; switch (escaped_codepoint) { // 'regular' escape codes @@ -484,6 +513,7 @@ namespace toml::impl eof_check(); TOML_ERROR_CHECK({}); + TOML_NOT_EOF; if (!is_hexadecimal_digit(*cp)) { abort_with_error( @@ -494,11 +524,7 @@ namespace toml::impl ); break; } - sequence_value += place_value * ( - *cp >= U'A' - ? 10u + static_cast(*cp - (*cp >= U'a' ? U'a' : U'A')) - : static_cast(*cp - U'0') - ); + sequence_value += place_value * hex_to_dec(*cp); place_value /= 16u; advance(); TOML_ERROR_CHECK({}); @@ -601,7 +627,7 @@ namespace toml::impl } // handle escapes - if (*cp == U'\\') + else if (*cp == U'\\') { advance(); // skip the '\' TOML_ERROR_CHECK({}); @@ -678,7 +704,8 @@ namespace toml::impl string parse_literal_string() TOML_MAY_THROW { TOML_ERROR_CHECK({}); - TOML_ASSERT(cp && *cp == U'\''); + TOML_NOT_EOF; + TOML_ASSERT(*cp == U'\''); const auto eof_check = [this]() TOML_MAY_THROW { @@ -694,6 +721,7 @@ namespace toml::impl // skip the delimiter advance(); eof_check(); + TOML_ERROR_CHECK({}); string str; while (cp) @@ -790,7 +818,8 @@ namespace toml::impl string parse_string() TOML_MAY_THROW { TOML_ERROR_CHECK({}); - TOML_ASSERT(cp && is_string_delimiter(*cp)); + TOML_NOT_EOF; + TOML_ASSERT(is_string_delimiter(*cp)); // get the first three characters to determine the string type const auto first = cp->value; @@ -846,7 +875,8 @@ namespace toml::impl string parse_bare_key_segment() TOML_MAY_THROW { TOML_ERROR_CHECK({}); - TOML_ASSERT(cp && is_bare_key_start_character(*cp)); + TOML_NOT_EOF; + TOML_ASSERT(is_bare_key_start_character(*cp)); string segment; @@ -868,10 +898,11 @@ namespace toml::impl bool parse_bool() TOML_MAY_THROW { TOML_ERROR_CHECK({}); - TOML_ASSERT(cp && (*cp == U't' || *cp == U'f')); + TOML_NOT_EOF; + TOML_ASSERT(is_match(*cp, U't', U'f', U'T', U'F')); start_recording(true); - auto result = *cp == U't'; + auto result = is_match(*cp, U't', U'T'); if (!consume_expected_sequence(result ? U"true"sv : U"false"sv)) { if (!cp) @@ -901,7 +932,8 @@ namespace toml::impl double parse_inf_or_nan() TOML_MAY_THROW { TOML_ERROR_CHECK({}); - TOML_ASSERT(cp && (*cp == U'i' || *cp == U'n' || *cp == U'+' || *cp == U'-')); + TOML_NOT_EOF; + TOML_ASSERT(is_match(*cp, U'i', U'n', U'I', U'N', U'+', U'-')); const auto eof_check = [this]() TOML_MAY_THROW { @@ -912,14 +944,14 @@ namespace toml::impl start_recording(true); const int sign = *cp == U'-' ? -1 : 1; - if (*cp == U'+' || *cp == U'-') + if (is_match(*cp, U'+', U'-')) { advance(); eof_check(); TOML_ERROR_CHECK({}); } - const bool inf = *cp == U'i'; + const bool inf = is_match(*cp, U'i', U'I'); if (!consume_expected_sequence(inf ? U"inf"sv : U"nan"sv)) { eof_check(); @@ -952,7 +984,8 @@ namespace toml::impl double parse_float() TOML_MAY_THROW { TOML_ERROR_CHECK({}); - TOML_ASSERT(cp && (*cp == U'+' || *cp == U'-' || is_decimal_digit(*cp))); + TOML_NOT_EOF; + TOML_ASSERT(is_match(*cp, U'+', U'-') || is_decimal_digit(*cp)); const auto eof_check = [this]() TOML_MAY_THROW { @@ -963,7 +996,7 @@ namespace toml::impl // sign const int sign = *cp == U'-' ? -1 : 1; - if (*cp == U'+' || *cp == U'-') + if (is_match(*cp, U'+', U'-')) { advance(); eof_check(); @@ -974,7 +1007,7 @@ namespace toml::impl char chars[64]; size_t length = {}; const utf8_codepoint* prev = {}; - bool seen_decimal = false, seen_exponent_sign = false, seen_exponent = false; + bool seen_decimal = false, seen_exponent = false; while (true) { if (!cp || is_value_terminator(*cp)) @@ -998,40 +1031,51 @@ namespace toml::impl if (*cp == U'.') { - if (seen_decimal) - abort_with_error( - "Encountered unexpected character while parsing "sv, node_type::floating_point, - "; decimal points may appear only once"sv - ); - else if (seen_exponent) - abort_with_error( - "Encountered unexpected character while parsing "sv, node_type::floating_point, - "; decimal points may not appear after exponents"sv - ); - seen_decimal = true; - } - else if (*cp == U'e' || *cp == U'E') - { + // 1.0e+.10 (exponent cannot have '.') if (seen_exponent) abort_with_error( "Encountered unexpected character while parsing "sv, node_type::floating_point, - "; exponents may appear only once"sv + "; expected exponent decimal digit or sign, saw '.'"sv ); + + // 1.0.e+.10 + // 1..0 + // (multiple '.') + else if (seen_decimal) + abort_with_error( + "Encountered unexpected character while parsing "sv, node_type::floating_point, + "; expected decimal digit or exponent, saw , saw '.'"sv + ); + + seen_decimal = true; + } + else if (is_match(*cp, U'e', U'E')) + { + // 1.0ee+10 (multiple 'e') + if (seen_exponent) + abort_with_error( + "Encountered unexpected character while parsing "sv, node_type::floating_point, + "; expected decimal digit, saw '"sv, *cp, '\'' + ); + + seen_decimal = true; // implied seen_exponent = true; } - else if (*cp == U'+' || *cp == U'-') + else if (is_match(*cp, U'+', U'-')) { - if (!seen_exponent || !(*prev == U'e' || *prev == U'E')) + // 1.-0 (sign in mantissa) + if (!seen_exponent) abort_with_error( - "Encountered unexpected character while parsing "sv, node_type::floating_point, - "; exponent signs must immediately follow 'e'"sv - ); - else if (seen_exponent_sign) + "Encountered unexpected character while parsing "sv, node_type::floating_point, + "; expected decimal digit or '.', saw '"sv, * cp, '\'' + ); + + // 1.0e1-0 (misplaced exponent sign) + else if (!is_match(*prev, U'e', U'E')) abort_with_error( - "Encountered unexpected character while parsing "sv, node_type::floating_point, - "; exponents signs may appear only once"sv + "Encountered unexpected character while parsing parsing "sv, node_type::floating_point, + "; expected exponent digit, saw '"sv, *cp, '\'' ); - seen_exponent_sign = true; } else if (!is_decimal_digit(*cp)) abort_with_error("Encountered unexpected character while parsing "sv, @@ -1051,16 +1095,26 @@ namespace toml::impl } TOML_ERROR_CHECK({}); - if (prev && *prev == U'_') + // sanity-check ending state + if (prev) { - eof_check(); - abort_with_error( - "Encountered unexpected character while parsing "sv, node_type::floating_point, - "; expected decimal digit, saw '"sv, *cp, '\'' - ); - TOML_ERROR_CHECK({}); - TOML_UNREACHABLE; + if (*prev == U'_') + { + eof_check(); + abort_with_error( + "Error parsing "sv, node_type::floating_point, "; trailing underscores are not allowed"sv + ); + } + else if (is_match(*prev, U'e', U'E', U'+', U'-')) + { + eof_check(); + abort_with_error( + "Encountered unexpected character while parsing parsing "sv, node_type::floating_point, + "; expected exponent digit, saw '"sv, *cp, '\'' + ); + } } + TOML_ERROR_CHECK({}); // convert to double double result; @@ -1097,6 +1151,7 @@ namespace toml::impl #else { std::stringstream ss; + ss.imbue(std::locale::classic()); ss.write(chars, static_cast(length)); if ((ss >> result)) return result * sign; @@ -1111,14 +1166,15 @@ namespace toml::impl TOML_UNREACHABLE; } - #if TOML_LANG_UNRELEASED // toml/issues/562 (hexfloats) - [[nodiscard]] TOML_NEVER_INLINE double parse_hex_float() TOML_MAY_THROW { TOML_ERROR_CHECK({}); - TOML_ASSERT(cp && *cp == U'0'); + TOML_NOT_EOF; + TOML_ASSERT(is_match(*cp, U'0', U'+', U'-')); + + #if TOML_LANG_UNRELEASED // toml/issues/562 (hexfloats) const auto eof_check = [this]() TOML_MAY_THROW { @@ -1127,6 +1183,15 @@ namespace toml::impl abort_with_error("Encountered EOF while parsing hexadecimal "sv, node_type::floating_point); }; + // sign + const int sign = *cp == U'-' ? -1 : 1; + if (is_match(*cp, U'+', U'-')) + { + advance(); + eof_check(); + TOML_ERROR_CHECK({}); + } + // '0' if (*cp != U'0') abort_with_error( @@ -1137,21 +1202,33 @@ namespace toml::impl eof_check(); // 'x' or 'X' - if (*cp != U'x' && *cp != U'X') + if (!is_match(*cp, U'x', U'X')) abort_with_error( "Encountered unexpected character while parsing hexadecimal "sv, node_type::floating_point, "; expected 'x' or 'X', saw '"sv, *cp, '\'' ); advance(); eof_check(); - TOML_ERROR_CHECK({}); - // consume value chars - char chars[23]; //23 = strlen("1.0123456789ABCDEFp+999") - size_t length = {}; + // ([.])? [pP] [+-]? + + // consume value fragments + struct fragment + { + char chars[24]; + size_t length; + double value; + }; + fragment fragments[] = + { + {}, // mantissa, whole part + {}, // mantissa, fractional part + {} // exponent + }; + fragment* current_fragment = fragments; const utf8_codepoint* prev = {}; - bool seen_decimal = false, seen_exponent_sign = false, seen_exponent = false; + int exponent_sign = 1; while (true) { if (!cp || is_value_terminator(*cp)) @@ -1176,152 +1253,171 @@ namespace toml::impl if (*cp == U'.') { - if (seen_decimal) + // 0x10.0p-.0 (exponent cannot have '.') + if (current_fragment == fragments + 2) abort_with_error( "Encountered unexpected character while parsing hexadecimal "sv, - node_type::floating_point, "; decimal points may appear only once"sv + node_type::floating_point, "; expected exponent digit or sign, , saw '.'"sv ); - else if (seen_exponent) + + // 0x10.0.p-0 (multiple '.') + else if (current_fragment == fragments + 1) abort_with_error( - "Encountered unexpected character while parsing "sv, node_type::floating_point, - "; decimal points may not appear after exponents"sv + "Encountered unexpected character while parsing hexadecimal "sv, + node_type::floating_point, "; expected hexadecimal digit or exponent, , saw '.'"sv ); - seen_decimal = true; + else + current_fragment++; } - else if (*cp == U'p' || *cp == U'P') + else if (is_match(*cp, U'p', U'P')) { - if (seen_exponent) + // 0x10.0pp-0 (multiple 'p') + if (current_fragment == fragments + 2) abort_with_error( "Encountered unexpected character while parsing hexadecimal "sv, - node_type::floating_point, "; exponents may appear only once"sv + node_type::floating_point, "; expected exponent digit or sign, saw '"sv, *cp, '\'' ); - else if (!seen_decimal) + + // 0x.p-0 (mantissa is just '.') + else if (fragments[0].length == 0_sz && fragments[1].length == 0_sz) abort_with_error( "Encountered unexpected character while parsing hexadecimal "sv, - node_type::floating_point, "; exponents may not appear before decimal points"sv + node_type::floating_point, "; expected hexadecimal digit, saw '"sv, *cp, '\'' ); - seen_exponent = true; + else + current_fragment = fragments + 2; } - else if (*cp == U'+' || *cp == U'-') + else if (is_match(*cp, U'+', U'-')) { - if (!seen_exponent || !(*prev == U'p' || *prev == U'P')) + // 0x-10.0p-0 (sign in mantissa) + if (current_fragment != fragments + 2) abort_with_error( "Encountered unexpected character while parsing hexadecimal "sv, - node_type::floating_point, "; exponent signs must immediately follow 'p'"sv + node_type::floating_point, "; expected hexadecimal digit or '.', saw '"sv, *cp, '\'' ); - else if (seen_exponent_sign) + + // 0x10.0p0- (misplaced exponent sign) + else if (!is_match(*prev, U'p', U'P')) abort_with_error( "Encountered unexpected character while parsing hexadecimal "sv, - node_type::floating_point, "; exponents signs may appear only once"sv + node_type::floating_point, "; expected exponent digit, saw '"sv, *cp, '\'' ); - seen_exponent_sign = true; + else + exponent_sign = *cp == U'-' ? -1 : 1; } - else if (!seen_exponent && !is_hexadecimal_digit(*cp)) + else if (current_fragment < fragments + 2 && !is_hexadecimal_digit(*cp)) abort_with_error("Encountered unexpected character while parsing hexadecimal "sv, - node_type::floating_point, "; expected hexadecimal digit, saw '"sv, *cp, '\'' + node_type::floating_point, "; expected hexadecimal digit or '.', saw '"sv, *cp, '\'' ); - else if (seen_exponent && !is_decimal_digit(*cp)) + else if (current_fragment == fragments + 2 && !is_decimal_digit(*cp)) abort_with_error("Encountered unexpected character while parsing hexadecimal "sv, - node_type::floating_point, " exponent; expected decimal digit, saw '"sv, *cp, '\'' + node_type::floating_point, "; expected exponent digit or sign, saw '"sv, *cp, '\'' ); - else if (length == sizeof(chars)) + else if (current_fragment->length == sizeof(fragment::chars)) abort_with_error( "Error parsing hexadecimal "sv, node_type::floating_point, - "; exceeds maximum length of "sv, sizeof(chars), " characters"sv - ); + "; fragment exceeeds maximum length of "sv, sizeof(fragment::chars), " characters"sv + ); + else + current_fragment->chars[current_fragment->length++] = static_cast(cp->bytes[0]); TOML_ERROR_CHECK({}); - chars[length++] = static_cast(cp->bytes[0]); prev = cp; advance(); TOML_ERROR_CHECK({}); } TOML_ERROR_CHECK({}); + // sanity-check ending state + if (current_fragment != fragments + 2 || current_fragment->length == 0_sz) + { + eof_check(); + abort_with_error( + "Error parsing hexadecimal "sv, node_type::floating_point, + "; missing exponent"sv + ); + } if (prev && *prev == U'_') { eof_check(); - if (seen_exponent) - abort_with_error( - "Encountered unexpected character while parsing hexadecimal "sv, - node_type::floating_point, " exponent; expected decimal digit, saw '"sv, *cp, '\'' - ); - else - abort_with_error( - "Encountered unexpected character while parsing hexadecimal "sv, - node_type::floating_point, "; expected hexadecimal digit, saw '"sv, *cp, '\'' - ); - TOML_ERROR_CHECK({}); - TOML_UNREACHABLE; + abort_with_error( + "Error parsing hexadecimal "sv, node_type::floating_point, + "; trailing underscores are not allowed"sv + ); } + TOML_ERROR_CHECK({}); - - // convert to double - double result; - #if TOML_FLOATING_POINT_CHARCONV + // calculate values for the three fragments + static constexpr auto calc_value = [](fragment& f, auto fragment_idx) noexcept { - auto fc_result = std::from_chars(chars, chars + length, result, std::chars_format::hex); - switch (fc_result.ec) + static constexpr uint32_t base = decltype(fragment_idx)::value == 2 ? 10 : 16; + + // left-trim zeroes + const char* c = f.chars; + size_t sig = {}; + while (f.length && *c == '0') { - case std::errc{}: //ok - return result; - - case std::errc::invalid_argument: - abort_with_error( - "Error parsing hexadecimal "sv, node_type::floating_point, - "; '"sv, std::string_view{ chars, length }, "' could not be interpreted as a value"sv - ); - break; - - case std::errc::result_out_of_range: - abort_with_error( - "Error parsing hexadecimal "sv, node_type::floating_point, - "; '"sv, std::string_view{ chars, length }, "' is not representable in 64 bits"sv - ); - break; - - default: //?? - abort_with_error( - "Error parsing hexadecimal "sv, node_type::floating_point, - "; an unspecified error occurred while trying to interpret '"sv, - std::string_view{ chars, length }, "' as a value"sv - ); + f.length--; + c++; + sig++; } - } - #else - { - std::string str; + if (!f.length) + return; + + // calculate value + auto place = 1u; + for (size_t i = 0; i < f.length - 1_sz; i++) + place *= base; + uint32_t val{}; + while (place) { - std::ostringstream ss; - ss.write("0x", 2_sz); - ss.write(chars, static_cast(length)); - str = std::move(ss).str(); + if constexpr (base == 16) + val += place * hex_to_dec(*c); + else + val += place * static_cast(*c - '0'); + if constexpr (decltype(fragment_idx)::value == 1) + sig++; + c++; + place /= base; } + f.value = static_cast(val); - char* end = {}; - result = std::strtod(str.c_str(), &end); - if (result == 0.0 && end == str.c_str()) - abort_with_error( - "Error parsing hexadecimal "sv, node_type::floating_point, - "; '"sv, std::string_view{ chars, length }, "' could not be interpreted as a value"sv - ); - else - return result; - } - #endif + // shift the fractional part + if constexpr (decltype(fragment_idx)::value == 1) + { + while (sig--) + f.value /= base; + } + }; + calc_value(fragments[0], std::integral_constant{}); + calc_value(fragments[1], std::integral_constant{}); + calc_value(fragments[2], std::integral_constant{}); + + return (fragments[0].value + fragments[1].value) + * pow(2.0, fragments[2].value * exponent_sign) + * sign; + + #else // !TOML_LANG_UNRELEASED + + TOML_ERROR( + "Hexadecimal floating-point values are not supported " + "in TOML 1.0.0 and earlier.", + cp->position, + reader.source_path() + ); TOML_ERROR_CHECK({}); TOML_UNREACHABLE; + + #endif // !TOML_LANG_UNRELEASED } - #endif // TOML_LANG_UNRELEASED - - template + template [[nodiscard]] TOML_NEVER_INLINE int64_t parse_integer() TOML_MAY_THROW { TOML_ERROR_CHECK({}); - TOML_ASSERT(cp); + TOML_NOT_EOF; using traits = parse_integer_traits; const auto eof_check = [this]() TOML_MAY_THROW @@ -1422,8 +1518,8 @@ namespace toml::impl { eof_check(); abort_with_error( - "Encountered unexpected character while parsing "sv, traits::qualifier, - ' ', node_type::integer, "; expected "sv, traits::qualifier, " digit, saw '_'"sv + "Error parsing "sv, traits::qualifier, ' ', node_type::integer, + "; trailing underscores are not allowed"sv ); } @@ -1439,67 +1535,48 @@ namespace toml::impl TOML_ERROR_CHECK({}); - // single digits can be converted directly + // single digits can be converted trivially if (length == 1_sz) { - if constexpr (base > 10) - { - return chars[0] >= 'A' - ? 10LL + static_cast(*cp - (*cp >= 'a' ? 'a' : 'A')) - : static_cast(*cp - '0'); - } - else + if constexpr (base == 16) + return static_cast(hex_to_dec(chars[0])); + else if constexpr (base <= 10) return static_cast(chars[0] - '0'); } - // otherwise invoke charconv - uint64_t result; - auto fc_result = std::from_chars(chars, chars + length, result, base); - if constexpr (traits::is_signed) + // otherwise do the thing + uint64_t result = {}; { - if (fc_result.ec == std::errc{} && ( - (sign < 0 && result > static_cast(std::numeric_limits::max()) + 1ull) - || (sign > 0 && result > static_cast(std::numeric_limits::max())) - )) - fc_result.ec = std::errc::result_out_of_range; + const char* msd = chars; + const char* end = msd + length; + while (msd < end && *msd == '0') + msd++; + if (msd == end) + return 0ll; + uint64_t power = 1; + while (--end >= msd) + { + if constexpr (base == 16) + result += power * hex_to_dec(*end); + else + result += power * static_cast(*end - '0'); + power *= base; + } } + + // range check + if (result > static_cast(std::numeric_limits::max()) + (sign < 0 ? 1ull : 0ull)) + abort_with_error( + "Error parsing "sv, traits::qualifier, ' ', node_type::integer, + "; '"sv, std::string_view{ chars, length }, "' is not representable in 64 bits"sv + ); else { - if (fc_result.ec == std::errc{} && - result > static_cast(std::numeric_limits::max()) - ) - fc_result.ec = std::errc::result_out_of_range; + if constexpr (traits::is_signed) + return static_cast(result) * sign; + else + return static_cast(result); } - switch (fc_result.ec) - { - case std::errc{}: //ok - if constexpr (traits::is_signed) - return static_cast(result) * sign; - else - return static_cast(result); - - case std::errc::invalid_argument: - abort_with_error( - "Error parsing "sv, traits::qualifier, ' ', node_type::integer, - "; '"sv, std::string_view{ chars, length }, "' could not be interpreted as a value"sv - ); - break; - - case std::errc::result_out_of_range: - abort_with_error( - "Error parsing "sv, traits::qualifier, ' ', node_type::integer, - "; '"sv, std::string_view{ chars, length }, "' is not representable in 64 bits"sv - ); - break; - - default: //?? - abort_with_error( - "Error parsing "sv, traits::qualifier, ' ', node_type::integer, - "; an unspecified error occurred while trying to interpret '"sv, - std::string_view{ chars, length }, "' as a value"sv - ); - } - TOML_ERROR_CHECK({}); TOML_UNREACHABLE; } @@ -1509,7 +1586,8 @@ namespace toml::impl date parse_date(bool part_of_datetime = false) TOML_MAY_THROW { TOML_ERROR_CHECK({}); - TOML_ASSERT(cp && is_decimal_digit(*cp)); + TOML_NOT_EOF; + TOML_ASSERT(is_decimal_digit(*cp)); const auto type = part_of_datetime ? node_type::date_time : node_type::date; const auto eof_check = [this, type]() TOML_MAY_THROW @@ -1525,11 +1603,14 @@ namespace toml::impl if (!consume_digit_sequence(year_digits)) { eof_check(); - abort_with_error( - "Encountered unexpected character while parsing "sv, type, - "; expected 4-digit year, saw '"sv, *cp, '\'' - ); + if (cp) + abort_with_error( + "Encountered unexpected character while parsing "sv, type, + "; expected 4-digit year, saw '"sv, *cp, '\'' + ); } + TOML_ERROR_CHECK({}); + TOML_NOT_EOF; const auto year = year_digits[3] + year_digits[2] * 10u + year_digits[1] * 100u @@ -1537,24 +1618,26 @@ namespace toml::impl const auto is_leap_year = (year % 4u == 0u) && ((year % 100u != 0u) || (year % 400u == 0u)); // '-' - eof_check(); if (*cp != U'-') abort_with_error( "Encountered unexpected character while parsing "sv, type, "; expected '-', saw '"sv, *cp, '\'' ); advance(); + eof_check(); TOML_ERROR_CHECK({}); + TOML_NOT_EOF; // "MM" uint32_t month_digits[2]; if (!consume_digit_sequence(month_digits)) { eof_check(); - abort_with_error( - "Encountered unexpected character while parsing "sv, type, - "; expected 2-digit month, saw '"sv, *cp, '\'' - ); + if (cp) + abort_with_error( + "Encountered unexpected character while parsing "sv, type, + "; expected 2-digit month, saw '"sv, *cp, '\'' + ); } const auto month = month_digits[1] + month_digits[0] * 10u; if (month == 0u || month > 12u) @@ -1567,26 +1650,31 @@ namespace toml::impl ? (is_leap_year ? 29u : 28u) : (month == 4u || month == 6u || month == 9u || month == 11u ? 30u : 31u) ; + eof_check(); + TOML_ERROR_CHECK({}); + TOML_NOT_EOF; // '-' - eof_check(); if (*cp != U'-') abort_with_error( "Encountered unexpected character while parsing "sv, type, "; expected '-', saw '"sv, *cp, '\'' ); advance(); + eof_check(); TOML_ERROR_CHECK({}); + TOML_NOT_EOF; // "DD" uint32_t day_digits[2]; if (!consume_digit_sequence(day_digits)) { eof_check(); - abort_with_error( - "Encountered unexpected character while parsing "sv, type, - "; expected 2-digit day, saw '"sv, *cp, '\'' - ); + if (cp) + abort_with_error( + "Encountered unexpected character while parsing "sv, type, + "; expected 2-digit day, saw '"sv, *cp, '\'' + ); } const auto day = day_digits[1] + day_digits[0] * 10u; if (day == 0u || day > max_days_in_month) @@ -1618,7 +1706,8 @@ namespace toml::impl time parse_time(bool part_of_datetime = false) TOML_MAY_THROW { TOML_ERROR_CHECK({}); - TOML_ASSERT(cp && is_decimal_digit(*cp)); + TOML_NOT_EOF; + TOML_ASSERT(is_decimal_digit(*cp)); const auto type = part_of_datetime ? node_type::date_time : node_type::date; const auto eof_check = [this, type]() TOML_MAY_THROW @@ -1633,10 +1722,11 @@ namespace toml::impl if (!consume_digit_sequence(digits)) { eof_check(); - abort_with_error( - "Encountered unexpected character while parsing "sv, type, - "; expected 2-digit hour, saw '"sv, *cp, '\'' - ); + if (cp) + abort_with_error( + "Encountered unexpected character while parsing "sv, type, + "; expected 2-digit hour, saw '"sv, *cp, '\'' + ); } const auto hour = digits[1] + digits[0] * 10u; if (hour > 23u) @@ -1644,26 +1734,31 @@ namespace toml::impl "Error parsing "sv, type, "; expected hour between 0 to 59 (inclusive), saw "sv, hour ); + eof_check(); + TOML_ERROR_CHECK({}); + TOML_NOT_EOF; // ':' - eof_check(); if (*cp != U':') abort_with_error( "Encountered unexpected character while parsing "sv, type, "; expected ':', saw '"sv, *cp, '\'' ); advance(); + eof_check(); TOML_ERROR_CHECK({}); + TOML_NOT_EOF; // "MM" if (!consume_digit_sequence(digits)) { eof_check(); - abort_with_error( - "Encountered unexpected character while parsing "sv, type, - "; expected 2-digit minute, saw '"sv, - *cp, '\'' - ); + if (cp) + abort_with_error( + "Encountered unexpected character while parsing "sv, type, + "; expected 2-digit minute, saw '"sv, + *cp, '\'' + ); } const auto minute = digits[1] + digits[0] * 10u; if (minute > 59u) @@ -1671,49 +1766,52 @@ namespace toml::impl "Error parsing "sv, type, "; expected minute between 0 and 59 (inclusive), saw "sv, minute ); - auto time = ::toml::time{ static_cast(hour), static_cast(minute), }; + TOML_ERROR_CHECK({}); // ':' - if constexpr (!TOML_LANG_UNRELEASED) // toml/issues/671 (allow omission of seconds) - { - eof_check(); - if (*cp != U':') - abort_with_error( - "Encountered unexpected character while parsing "sv, type, - "; expected ':', saw '"sv, *cp, '\'' - ); - } - else + if constexpr (TOML_LANG_UNRELEASED) // toml/issues/671 (allow omission of seconds) { if (cp && !is_value_terminator(*cp) && *cp != U':' - && (!part_of_datetime || (*cp != U'+' && *cp != U'-' && *cp != U'Z' && *cp != U'z'))) + && (!part_of_datetime || !is_match(*cp, U'+', U'-', U'Z'))) abort_with_error( "Encountered unexpected character while parsing "sv, type, "; expected ':'"sv, (part_of_datetime ? ", offset"sv : ""sv), " or value-terminator, saw '"sv, *cp, '\'' ); - TOML_ERROR_CHECK({}); - if (!cp || *cp != U':') + else if (!cp || *cp != U':') return time; } + else + { + eof_check(); + + if (cp && *cp != U':') + abort_with_error( + "Encountered unexpected character while parsing "sv, type, + "; expected ':', saw '"sv, *cp, '\'' + ); + } advance(); + eof_check(); TOML_ERROR_CHECK({}); + TOML_NOT_EOF; // "SS" if (!consume_digit_sequence(digits)) { eof_check(); - abort_with_error( - "Encountered unexpected character while parsing "sv, type, - "; expected 2-digit second, saw '"sv, - *cp, '\'' - ); + if (cp) + abort_with_error( + "Encountered unexpected character while parsing "sv, type, + "; expected 2-digit second, saw '"sv, + *cp, '\'' + ); } const auto second = digits[1] + digits[0] * 10u; if (second > 59u) @@ -1722,26 +1820,28 @@ namespace toml::impl "; expected second between 0 and 59 (inclusive), saw "sv, second ); time.second = static_cast(second); + TOML_ERROR_CHECK({}); //early exit here if the fractional is omitted if (cp && !is_value_terminator(*cp) && *cp != U'.' - && (!part_of_datetime || (*cp != U'+' && *cp != U'-' && *cp != U'Z' && *cp != U'z'))) + && (!part_of_datetime || !is_match(*cp, U'+', U'-', U'Z'))) abort_with_error( "Encountered unexpected character while parsing "sv, type, "; expected fractional"sv, (part_of_datetime ? ", offset"sv : ""sv), " or value-terminator, saw '"sv, *cp, '\'' ); - TOML_ERROR_CHECK({}); - if (!cp || *cp != U'.') + else if (!cp || *cp != U'.') return time; + TOML_ERROR_CHECK({}); // '.' advance(); eof_check(); TOML_ERROR_CHECK({}); + TOML_NOT_EOF; // ".FFFFFFFFF" static constexpr auto max_fractional_digits = 9_sz; @@ -1779,7 +1879,8 @@ namespace toml::impl date_time parse_date_time() TOML_MAY_THROW { TOML_ERROR_CHECK({}); - TOML_ASSERT(cp && is_decimal_digit(*cp)); + TOML_NOT_EOF; + TOML_ASSERT(is_decimal_digit(*cp)); const auto eof_check = [this]() TOML_MAY_THROW { @@ -1790,16 +1891,23 @@ namespace toml::impl // "YYYY-MM-DD" auto date = parse_date(true); + eof_check(); TOML_ERROR_CHECK({}); + TOML_NOT_EOF; // ' ' or 'T' - eof_check(); - if (*cp != U' ' && *cp != U'T' && *cp != U't') + if (!is_match(*cp, U' ', U'T')) abort_with_error( "Encountered unexpected character while parsing "sv, node_type::date_time, "; expected space or 'T', saw '"sv, *cp, '\'' ); - advance(); + else + { + advance(); + eof_check(); + } + TOML_ERROR_CHECK({}); + TOML_NOT_EOF; // "HH:MM:SS.fractional" @@ -1812,19 +1920,21 @@ namespace toml::impl if (cp) { // zero offset ("Z") - if (*cp == U'Z' || *cp == U'z') + if (*cp == U'Z') { has_offset = true; advance(); } // explicit offset ("+/-HH:MM") - else if (*cp == U'+' || *cp == U'-') + else if (is_match(*cp, U'+', U'-')) { // sign int sign = *cp == U'-' ? -1 : 1; advance(); eof_check(); + TOML_ERROR_CHECK({}); + TOML_NOT_EOF; // "HH" int digits[2]; @@ -1842,16 +1952,24 @@ namespace toml::impl "Error parsing "sv, node_type::date_time, " offset; expected hour between 0 and 23 (inclusive), saw "sv, hour ); - + else + eof_check(); + TOML_ERROR_CHECK({}); + TOML_NOT_EOF; // ':' - eof_check(); if (*cp != U':') abort_with_error( "Encountered unexpected character while parsing "sv, node_type::date_time, "offset; expected ':', saw '"sv, *cp, '\'' ); - advance(); + else + { + advance(); + eof_check(); + } + TOML_ERROR_CHECK({}); + TOML_NOT_EOF; // "MM" if (!consume_digit_sequence(digits)) @@ -1873,8 +1991,8 @@ namespace toml::impl offset.minutes = static_cast((hour * 60 + minute) * sign); } } + TOML_ERROR_CHECK({}); - if (cp && !is_value_terminator(*cp)) abort_with_error( "Encountered unexpected character while parsing "sv, node_type::date_time, @@ -1903,7 +2021,8 @@ namespace toml::impl std::unique_ptr parse_value() TOML_MAY_THROW { TOML_ERROR_CHECK({}); - TOML_ASSERT(cp && !is_value_terminator(*cp)); + TOML_NOT_EOF; + TOML_ASSERT(!is_value_terminator(*cp)); const auto begin_pos = cp->position; std::unique_ptr val; @@ -1919,7 +2038,7 @@ namespace toml::impl val = std::make_unique>(parse_string()); // bools - else if (*cp == U't' || *cp == U'f') + else if (is_match(*cp, U't', U'f', U'T', U'F')) val = std::make_unique>(parse_bool()); // arrays @@ -1942,7 +2061,7 @@ namespace toml::impl val = parse_inline_table(); // inf or nan - else if (*cp == U'i' || *cp == U'n') + else if (is_match(*cp, U'i', U'n', U'I', U'N')) val = std::make_unique>(parse_inf_or_nan()); // underscores at the beginning @@ -1953,23 +2072,37 @@ namespace toml::impl if (val) break; - // value types from here down require more than one character - // to unambiguously identify, so scan ahead a bit. - enum value_string_traits : int + // value types from here down require more than one character to unambiguously identify + // so scan ahead and collect a set of value 'traits'. + enum value_traits : int { - has_nothing = 0, - has_digits = 1, - has_e = 2, - has_t = 4, - has_z = 8, - has_colon = 16, - has_plus = 32, - has_minus = 64, - has_sign = has_plus | has_minus, - has_dot = 128, - has_space = 256 + has_nothing = 0, + has_digits = 1, + has_b = 1 << 1, // as second char only (0b) + has_e = 1 << 2, // only float exponents + has_o = 1 << 3, // as second char only (0o) + has_p = 1 << 4, // only hexfloat exponents + has_t = 1 << 5, + has_x = 1 << 6, // as second or third char only (0x, -0x, +0x) + has_z = 1 << 7, + has_colon = 1 << 8, + has_plus = 1 << 9, + has_minus = 1 << 10, + has_dot = 1 << 11, + begins_sign = 1 << 12, + begins_digit = 1 << 13, + begins_zero = 1 << 14 + + // Q: "why not make these real values in the enum??" + // A: because the visual studio debugger stops treating them as a set of flags if you add + // non-pow2 values, making them much harder to debug. + #define toml_signs_msk (has_plus | has_minus) + #define toml_bzero_msk (begins_zero | has_digits) + #define toml_bdigit_msk (begins_digit | has_digits) }; - int traits = has_nothing; + value_traits traits = has_nothing; + const auto has_trait = [&](auto t) noexcept { return (traits & t) != has_nothing; }; + const auto add_trait = [&](auto t) noexcept { traits = static_cast(traits | t); }; char32_t chars[utf8_buffered_reader::max_history_length]; size_t char_count = {}, advance_count = {}; bool eof_while_scanning = false; @@ -1988,15 +2121,47 @@ namespace toml::impl chars[char_count++] = *cp; switch (*cp) { + case U'B': [[fallthrough]]; + case U'b': + if (chars[0] == U'0' && char_count == 2_sz) + add_trait(has_b); + break; + case U'E': [[fallthrough]]; - case U'e': traits |= has_e; break; - case U'T': traits |= has_t; break; - case U'Z': traits |= has_z; break; - case U'+': traits |= has_plus; break; - case U'-': traits |= has_minus; break; - case U'.': traits |= has_dot; break; - case U':': traits |= has_colon; break; - default: if (is_decimal_digit(*cp)) traits |= has_digits; + case U'e': + if (char_count > 1_sz && !has_trait(has_b | has_o | has_p | has_x)) + add_trait(has_e); + break; + + case U'O': [[fallthrough]]; + case U'o': + if (chars[0] == U'0' && char_count == 2_sz) + add_trait(has_o); + break; + + case U'P': [[fallthrough]]; + case U'p': + if (has_trait(has_x)) + add_trait(has_p); + break; + + case U'X': [[fallthrough]]; + case U'x': + if ((chars[0] == U'0' && char_count == 2_sz) + || (is_match(chars[0], U'-', U'+') && chars[1] == U'0' && char_count == 3_sz)) + add_trait(has_x); + break; + + case U'T': add_trait(has_t); break; + case U'Z': add_trait(has_z); break; + case U'+': add_trait(has_plus); break; + case U'-': add_trait(has_minus); break; + case U'.': add_trait(has_dot); break; + case U':': add_trait(has_colon); break; + + default: + if (is_decimal_digit(*cp)) + add_trait(has_digits); } } advance(); @@ -2007,7 +2172,7 @@ namespace toml::impl scan(); TOML_ERROR_CHECK({}); - //force further scanning if this could have been a date-time with a space instead of a T + // force further scanning if this could have been a date-time with a space instead of a T if (char_count == 10_sz && traits == (has_digits | has_minus) && chars[4] == U'-' @@ -2018,7 +2183,7 @@ namespace toml::impl const auto pre_advance_count = advance_count; const auto pre_scan_traits = traits; chars[char_count++] = *cp; - traits |= has_space; + add_trait(has_t); const auto backpedal = [&]() noexcept { @@ -2050,7 +2215,7 @@ namespace toml::impl } } - //set the reader back to where we started + // set the reader back to where we started go_back(advance_count); if (char_count < utf8_buffered_reader::max_history_length - 1_sz) chars[char_count] = U'\0'; @@ -2059,7 +2224,7 @@ namespace toml::impl // the only valid value type is an integer. if (char_count == 1_sz) { - if ((traits & has_digits)) + if (traits == has_digits) { val = std::make_unique>(static_cast(chars[0] - U'0')); advance(); //skip the digit @@ -2078,12 +2243,48 @@ namespace toml::impl TOML_ERROR_CHECK({}); TOML_ASSERT(char_count >= 2_sz); - // numbers that begin with a sign - const auto begins_with_sign = chars[0] == U'+' || chars[0] == U'-'; - const auto begins_with_digit = is_decimal_digit(chars[0]); - if (begins_with_sign) + // slightly deeper trait invariant resolution. + // some 'fuzzy matching' is done here where there's no ambiguity, since that allows the specific + // typed parse functions to take over and show better diagnostics if there's an issue + // (as opposed to the fallback "could not determine type" message) + if (is_decimal_digit(chars[0])) { - if (char_count == 2_sz && is_decimal_digit(chars[1])) + if (chars[0] == U'0') + { + // hex integers and floats + if (has_trait(has_x)) + { + if (has_trait(has_p)) + val = std::make_unique>(parse_hex_float()); + else + val = std::make_unique>(parse_integer<16>()); + } + + // octal integers + else if (has_trait(has_o)) + val = std::make_unique>(parse_integer<8>()); + + // binary integers + else if (has_trait(has_b)) + val = std::make_unique>(parse_integer<2>()); + + else + add_trait(begins_zero); + } + else + add_trait(begins_digit); + + if (!val) + { + // simple floats (e.g. 1.0, 1e1) + if (is_match(chars[1], U'.', U'e', U'E')) + val = std::make_unique>(parse_float()); + } + } + else if (is_match(chars[0], U'+', U'-')) + { + // single-digit signed integers + if (char_count == 2_sz && has_trait(has_digits)) { val = std::make_unique>( static_cast(chars[1] - U'0') @@ -2091,170 +2292,217 @@ namespace toml::impl ); advance(); //skip the sign advance(); //skip the digit + break; } - else if (chars[1] == U'i' || chars[1] == U'n') - val = std::make_unique>(parse_inf_or_nan()); - else if (is_decimal_digit(chars[1]) && (chars[2] == U'.' || chars[2] == U'e' || chars[2] == U'E')) + // simple signed floats (e.g. +1.0, -1e1) + else if (is_decimal_digit(chars[1]) && is_match(chars[2], U'.', U'e', U'E')) val = std::make_unique>(parse_float()); - } - // numbers that begin with 0 - else if (chars[0] == U'0') - { - switch (chars[1]) + // signed hexfloats (e.g. +1.0, -1e1) + // (hex integers cannot be signed in TOML) + else if (has_trait(has_x)) + val = std::make_unique>(parse_hex_float()); + + // signed infinity or nan + else if (is_match(chars[1], U'i', U'n', U'I', U'N')) { - case U'E': [[fallthrough]]; - case U'e': [[fallthrough]]; - case U'.': val = std::make_unique>(parse_float()); break; - case U'b': val = std::make_unique>(parse_integer<2>()); break; - case U'o': val = std::make_unique>(parse_integer<8>()); break; - case U'X': [[fallthrough]]; - case U'x': - { - for (size_t i = char_count; i-- > 2_sz;) - { - if (chars[i] == U'p' || chars[i] == U'P') - { - #if TOML_LANG_UNRELEASED // toml/issues/562 - hexfloats - val = std::make_unique>(parse_hex_float()); - break; - #else - TOML_ERROR( - "Hexadecimal floating-point values are not supported " - "in TOML 1.0.0 and earlier.", - begin_pos, - reader.source_path() - ); - #endif - } - } - TOML_ERROR_CHECK({}); - if (val) - break; - - val = std::make_unique>(parse_integer<16>()); - break; - } + val = std::make_unique>(parse_inf_or_nan()); + break; } + + else + add_trait(begins_sign); } TOML_ERROR_CHECK({}); if (val) break; - // from here down it can only be date-times, floats and integers. - if (begins_with_digit) + // match trait masks against what they can match exclusively. + // all correct value parses will come out of this list, so doing this as a switch is likely to + // be a better friend to the optimizer on the success path (failure path can be slow but that + // doesn't matter much). + switch (unbox_enum(traits)) { - switch (traits) - { - // 100 - case has_digits: - val = std::make_unique>(parse_integer<10>()); - break; + //=================== binary integers + // 0b10 + case toml_bzero_msk | has_b: + val = std::make_unique>(parse_integer<2>()); + break; - // 1e1 - // 1e-1 - // 1e+1 - // 1.0 - // 1.0e1 - // 1.0e-1 - // 1.0e+1 - case has_digits | has_e: [[fallthrough]]; - case has_digits | has_e | has_minus: [[fallthrough]]; - case has_digits | has_e | has_plus: [[fallthrough]]; - case has_digits | has_dot: [[fallthrough]]; - case has_digits | has_dot | has_e: [[fallthrough]]; - case has_digits | has_dot | has_e | has_minus: [[fallthrough]]; - case has_digits | has_dot | has_e | has_plus: - val = std::make_unique>(parse_float()); - break; + //=================== octal integers + // 0o10 + case toml_bzero_msk | has_o: + val = std::make_unique>(parse_integer<8>()); + break; - // HH:MM - // HH:MM:SS - // HH:MM:SS.FFFFFF - case has_digits | has_colon: [[fallthrough]]; - case has_digits | has_colon | has_dot: - val = std::make_unique>(parse_time()); - break; + //=================== decimal integers + // 00 + // 10 + // +10 + // -10 + case toml_bzero_msk: [[fallthrough]]; + case toml_bdigit_msk: [[fallthrough]]; + case begins_sign | has_digits | has_minus: [[fallthrough]]; + case begins_sign | has_digits | has_plus: + val = std::make_unique>(parse_integer<10>()); + break; - // YYYY-MM-DD - case has_digits | has_minus: - val = std::make_unique>(parse_date()); - break; + //=================== hexadecimal integers + // 0x10 + case toml_bzero_msk | has_x: + val = std::make_unique>(parse_integer<16>()); + break; - // YYYY-MM-DDTHH:MM - // YYYY-MM-DDTHH:MM-HH:MM - // YYYY-MM-DDTHH:MM+HH:MM - // YYYY-MM-DD HH:MM - // YYYY-MM-DD HH:MM-HH:MM - // YYYY-MM-DD HH:MM+HH:MM - // YYYY-MM-DDTHH:MM:SS - // YYYY-MM-DDTHH:MM:SS-HH:MM - // YYYY-MM-DDTHH:MM:SS+HH:MM - // YYYY-MM-DD HH:MM:SS - // YYYY-MM-DD HH:MM:SS-HH:MM - // YYYY-MM-DD HH:MM:SS+HH:MM - case has_digits | has_minus | has_colon | has_t: [[fallthrough]]; - case has_digits | has_sign | has_colon | has_t: [[fallthrough]]; - case has_digits | has_minus | has_colon | has_space: [[fallthrough]]; - case has_digits | has_sign | has_colon | has_space: [[fallthrough]]; - // YYYY-MM-DDTHH:MM:SS.FFFFFF - // YYYY-MM-DDTHH:MM:SS.FFFFFF-HH:MM - // YYYY-MM-DDTHH:MM:SS.FFFFFF+HH:MM - // YYYY-MM-DD HH:MM:SS.FFFFFF - // YYYY-MM-DD HH:MM:SS.FFFFFF-HH:MM - // YYYY-MM-DD HH:MM:SS.FFFFFF+HH:MM - case has_digits | has_minus | has_colon | has_dot | has_t: [[fallthrough]]; - case has_digits | has_sign | has_colon | has_dot | has_t: [[fallthrough]]; - case has_digits | has_minus | has_colon | has_dot | has_space: [[fallthrough]]; - case has_digits | has_sign | has_colon | has_dot | has_space: [[fallthrough]]; - // YYYY-MM-DDTHH:MMZ - // YYYY-MM-DD HH:MMZ - // YYYY-MM-DDTHH:MM:SSZ - // YYYY-MM-DD HH:MM:SSZ - // YYYY-MM-DDTHH:MM:SS.FFFFFFZ - // YYYY-MM-DD HH:MM:SS.FFFFFFZ - case has_digits | has_minus | has_colon | has_z | has_t: [[fallthrough]]; - case has_digits | has_minus | has_colon | has_z | has_space: [[fallthrough]]; - case has_digits | has_minus | has_colon | has_dot | has_z | has_t: [[fallthrough]]; - case has_digits | has_minus | has_colon | has_dot | has_z | has_space: - val = std::make_unique>(parse_date_time()); - break; - } + //=================== decimal floats + // 0e1 + // 0e-1 + // 0e+1 + // 0.0 + // 0.0e1 + // 0.0e-1 + // 0.0e+1 + case toml_bzero_msk | has_e: [[fallthrough]]; + case toml_bzero_msk | has_e | has_minus: [[fallthrough]]; + case toml_bzero_msk | has_e | has_plus: [[fallthrough]]; + case toml_bzero_msk | has_dot: [[fallthrough]]; + case toml_bzero_msk | has_dot | has_e: [[fallthrough]]; + case toml_bzero_msk | has_dot | has_e | has_minus: [[fallthrough]]; + case toml_bzero_msk | has_dot | has_e | has_plus: [[fallthrough]]; + // 1e1 + // 1e-1 + // 1e+1 + // 1.0 + // 1.0e1 + // 1.0e-1 + // 1.0e+1 + case toml_bdigit_msk | has_e: [[fallthrough]]; + case toml_bdigit_msk | has_e | has_minus: [[fallthrough]]; + case toml_bdigit_msk | has_e | has_plus: [[fallthrough]]; + case toml_bdigit_msk | has_dot: [[fallthrough]]; + case toml_bdigit_msk | has_dot | has_e: [[fallthrough]]; + case toml_bdigit_msk | has_dot | has_e | has_minus: [[fallthrough]]; + case toml_bdigit_msk | has_dot | has_e | has_plus: [[fallthrough]]; + // +1e1 + // +1.0 + // +1.0e1 + // +1.0e+1 + // +1.0e-1 + // -1.0e+1 + case begins_sign | has_digits | has_e | has_plus: [[fallthrough]]; + case begins_sign | has_digits | has_dot | has_plus: [[fallthrough]]; + case begins_sign | has_digits | has_dot | has_e | has_plus: [[fallthrough]]; + case begins_sign | has_digits | has_dot | has_e | toml_signs_msk: [[fallthrough]]; + // -1e1 + // -1e+1 + // +1e-1 + // -1.0 + // -1.0e1 + // -1.0e-1 + case begins_sign | has_digits | has_e | has_minus: [[fallthrough]]; + case begins_sign | has_digits | has_e | toml_signs_msk: [[fallthrough]]; + case begins_sign | has_digits | has_dot | has_minus: [[fallthrough]]; + case begins_sign | has_digits | has_dot | has_e | has_minus: + val = std::make_unique>(parse_float()); + break; + + //=================== hexadecimal floats + // 0x10p0 + // 0x10p-0 + // 0x10p+0 + case toml_bzero_msk | has_x | has_p: [[fallthrough]]; + case toml_bzero_msk | has_x | has_p | has_minus: [[fallthrough]]; + case toml_bzero_msk | has_x | has_p | has_plus: [[fallthrough]]; + // -0x10p0 + // -0x10p-0 + // +0x10p0 + // +0x10p+0 + // -0x10p+0 + // +0x10p-0 + case begins_sign | has_digits | has_x | has_p | has_minus: [[fallthrough]]; + case begins_sign | has_digits | has_x | has_p | has_plus: [[fallthrough]]; + case begins_sign | has_digits | has_x | has_p | toml_signs_msk: [[fallthrough]]; + // 0x10.1p0 + // 0x10.1p-0 + // 0x10.1p+0 + case toml_bzero_msk | has_x | has_dot | has_p: [[fallthrough]]; + case toml_bzero_msk | has_x | has_dot | has_p | has_minus: [[fallthrough]]; + case toml_bzero_msk | has_x | has_dot | has_p | has_plus: [[fallthrough]]; + // -0x10.1p0 + // -0x10.1p-0 + // +0x10.1p0 + // +0x10.1p+0 + // -0x10.1p+0 + // +0x10.1p-0 + case begins_sign | has_digits | has_x | has_dot | has_p | has_minus: [[fallthrough]]; + case begins_sign | has_digits | has_x | has_dot | has_p | has_plus: [[fallthrough]]; + case begins_sign | has_digits | has_x | has_dot | has_p | toml_signs_msk: + val = std::make_unique>(parse_hex_float()); + break; + + //=================== times + // HH:MM + // HH:MM:SS + // HH:MM:SS.FFFFFF + case toml_bzero_msk | has_colon: [[fallthrough]]; + case toml_bzero_msk | has_colon | has_dot: [[fallthrough]]; + case toml_bdigit_msk | has_colon: [[fallthrough]]; + case toml_bdigit_msk | has_colon | has_dot: + val = std::make_unique>(parse_time()); + break; + + //=================== local dates + // YYYY-MM-DD + case toml_bzero_msk | has_minus: [[fallthrough]]; + case toml_bdigit_msk | has_minus: + val = std::make_unique>(parse_date()); + break; + + //=================== date-times + // YYYY-MM-DDTHH:MM + // YYYY-MM-DDTHH:MM-HH:MM + // YYYY-MM-DDTHH:MM+HH:MM + // YYYY-MM-DD HH:MM + // YYYY-MM-DD HH:MM-HH:MM + // YYYY-MM-DD HH:MM+HH:MM + // YYYY-MM-DDTHH:MM:SS + // YYYY-MM-DDTHH:MM:SS-HH:MM + // YYYY-MM-DDTHH:MM:SS+HH:MM + // YYYY-MM-DD HH:MM:SS + // YYYY-MM-DD HH:MM:SS-HH:MM + // YYYY-MM-DD HH:MM:SS+HH:MM + case toml_bzero_msk | has_minus | has_colon | has_t: [[fallthrough]]; + case toml_bzero_msk | toml_signs_msk | has_colon | has_t: [[fallthrough]]; + case toml_bdigit_msk | has_minus | has_colon | has_t: [[fallthrough]]; + case toml_bdigit_msk | toml_signs_msk | has_colon | has_t: [[fallthrough]]; + // YYYY-MM-DDTHH:MM:SS.FFFFFF + // YYYY-MM-DDTHH:MM:SS.FFFFFF-HH:MM + // YYYY-MM-DDTHH:MM:SS.FFFFFF+HH:MM + // YYYY-MM-DD HH:MM:SS.FFFFFF + // YYYY-MM-DD HH:MM:SS.FFFFFF-HH:MM + // YYYY-MM-DD HH:MM:SS.FFFFFF+HH:MM + case toml_bzero_msk | has_minus | has_colon | has_dot | has_t: [[fallthrough]]; + case toml_bzero_msk | toml_signs_msk | has_colon | has_dot | has_t: [[fallthrough]]; + case toml_bdigit_msk | has_minus | has_colon | has_dot | has_t: [[fallthrough]]; + case toml_bdigit_msk | toml_signs_msk | has_colon | has_dot | has_t: [[fallthrough]]; + // YYYY-MM-DDTHH:MMZ + // YYYY-MM-DD HH:MMZ + // YYYY-MM-DDTHH:MM:SSZ + // YYYY-MM-DD HH:MM:SSZ + // YYYY-MM-DDTHH:MM:SS.FFFFFFZ + // YYYY-MM-DD HH:MM:SS.FFFFFFZ + case toml_bzero_msk | has_minus | has_colon | has_z | has_t: [[fallthrough]]; + case toml_bzero_msk | has_minus | has_colon | has_dot | has_z | has_t: [[fallthrough]]; + case toml_bdigit_msk | has_minus | has_colon | has_z | has_t: [[fallthrough]]; + case toml_bdigit_msk | has_minus | has_colon | has_dot | has_z | has_t: + val = std::make_unique>(parse_date_time()); + break; } - else if (begins_with_sign) - { - switch (traits) - { - // +100 - // -100 - case has_digits | has_minus: [[fallthrough]]; - case has_digits | has_plus: - val = std::make_unique>(parse_integer<10>()); - break; - // +1e1 - // +1.0 - // +1.0e1 - // +1.0e+1 - // +1.0e-1 - // -1.0e+1 - case has_digits | has_e | has_plus: [[fallthrough]]; - case has_digits | has_dot | has_plus: [[fallthrough]]; - case has_digits | has_dot | has_e | has_plus: [[fallthrough]]; - case has_digits | has_dot | has_e | has_sign: [[fallthrough]]; - // -1e1 - // -1.0 - // -1.0e1 - // -1.0e-1 - case has_digits | has_e | has_minus: [[fallthrough]]; - case has_digits | has_dot | has_minus: [[fallthrough]]; - case has_digits | has_dot | has_e | has_minus: - val = std::make_unique>(parse_float()); - break; - } - } + #undef toml_signs_msk + #undef toml_bzero_msk + #undef toml_bdigit_msk } while (false); @@ -2279,7 +2527,8 @@ namespace toml::impl key parse_key() TOML_MAY_THROW { TOML_ERROR_CHECK({}); - TOML_ASSERT(cp && (is_bare_key_start_character(*cp) || is_string_delimiter(*cp))); + TOML_NOT_EOF; + TOML_ASSERT(is_bare_key_start_character(*cp) || is_string_delimiter(*cp)); key key; @@ -2353,13 +2602,16 @@ namespace toml::impl }; // get the key - TOML_ASSERT(cp && (is_string_delimiter(cp->value) || is_bare_key_start_character(cp->value))); + TOML_NOT_EOF; + TOML_ASSERT(is_string_delimiter(cp->value) || is_bare_key_start_character(cp->value)); start_recording(); auto key = parse_key(); //parse_key() calls stop_recording() // skip past any whitespace that followed the key consume_leading_whitespace(); eof_check(); + TOML_ERROR_CHECK({}); + TOML_NOT_EOF; // consume the '=' if (*cp != U'=') @@ -2373,6 +2625,8 @@ namespace toml::impl // skip past any whitespace that followed the '=' consume_leading_whitespace(); eof_check(); + TOML_ERROR_CHECK({}); + TOML_NOT_EOF; // get the value if (is_value_terminator(*cp)) @@ -2389,7 +2643,8 @@ namespace toml::impl table* parse_table_header() TOML_MAY_THROW { TOML_ERROR_CHECK({}); - TOML_ASSERT(cp && *cp == U'['); + TOML_NOT_EOF; + TOML_ASSERT(*cp == U'['); const auto header_begin_pos = cp->position; source_position header_end_pos; @@ -2408,6 +2663,8 @@ namespace toml::impl // skip first '[' advance(); eof_check(); + TOML_ERROR_CHECK({}); + TOML_NOT_EOF; // skip second '[' (if present) if (*cp == U'[') @@ -2447,6 +2704,8 @@ namespace toml::impl // skip past any whitespace that followed the key consume_leading_whitespace(); eof_check(); + TOML_ERROR_CHECK({}); + TOML_NOT_EOF; // consume the first closing ']' if (*cp != U']') @@ -2460,6 +2719,8 @@ namespace toml::impl if (is_arr) { eof_check(); + TOML_ERROR_CHECK({}); + TOML_NOT_EOF; if (*cp != U']') abort_with_error( @@ -2654,7 +2915,7 @@ namespace toml::impl TOML_NEVER_INLINE void parse_document() TOML_MAY_THROW { - TOML_ASSERT(cp); + TOML_NOT_EOF; table* current_table = &root; @@ -2734,6 +2995,7 @@ namespace toml::impl auto end = nde.source_.end; for (auto& [k, v] : tbl.values) { + (void)k; update_region_ends(*v); if (end < v->source_.end) end = v->source_.end; @@ -2804,7 +3066,8 @@ namespace toml::impl std::unique_ptr parser::parse_array() TOML_MAY_THROW { TOML_ERROR_CHECK({}); - TOML_ASSERT(cp && *cp == U'['); + TOML_NOT_EOF; + TOML_ASSERT(*cp == U'['); const auto eof_check = [this]() TOML_MAY_THROW { @@ -2886,7 +3149,8 @@ namespace toml::impl std::unique_ptr parser::parse_inline_table() TOML_MAY_THROW { TOML_ERROR_CHECK({}); - TOML_ASSERT(cp && *cp == U'{'); + TOML_NOT_EOF; + TOML_ASSERT(*cp == U'{'); const auto eof_check = [this]() TOML_MAY_THROW { @@ -3003,6 +3267,7 @@ namespace toml::impl #undef TOML_ERROR_CHECK #undef TOML_ERROR #undef TOML_NORETURN + #undef TOML_NOT_EOF TOML_API TOML_FUNC_EXTERNAL_LINKAGE diff --git a/include/toml++/toml_print_to_stream.h b/include/toml++/toml_print_to_stream.h index b9313b3..e4d50b6 100644 --- a/include/toml++/toml_print_to_stream.h +++ b/include/toml++/toml_print_to_stream.h @@ -5,6 +5,19 @@ #pragma once #include "toml_date_time.h" +TOML_PUSH_WARNINGS +TOML_DISABLE_ALL_WARNINGS +#if TOML_INTEGER_CHARCONV || TOML_FLOATING_POINT_CHARCONV + #include +#endif +#if !TOML_INTEGER_CHARCONV || !TOML_FLOATING_POINT_CHARCONV + #include +#endif +#if !TOML_INTEGER_CHARCONV + #include +#endif +TOML_POP_WARNINGS + namespace toml::impl { // Q: "why does print_to_stream() exist? why not just use ostream::write(), ostream::put() etc?" @@ -93,15 +106,29 @@ namespace toml::impl "The stream's underlying character type must be 1 byte in size." ); - char buf[charconv_buffer_length]; - const auto res = std::to_chars(buf, buf + sizeof(buf), val); - print_to_stream(buf, static_cast(res.ptr - buf), stream); + #if TOML_INTEGER_CHARCONV + + char buf[charconv_buffer_length]; + const auto res = std::to_chars(buf, buf + sizeof(buf), val); + const auto len = static_cast(res.ptr - buf); + print_to_stream(buf, len, stream); + + #else + + std::ostringstream ss; + ss.imbue(std::locale::classic()); + using cast_type = std::conditional_t, int64_t, uint64_t>; + ss << static_cast(val); + const auto str = std::move(ss).str(); + print_to_stream(str, stream); + + #endif } - #define TOML_P2S_OVERLOAD(type) \ + #define TOML_P2S_OVERLOAD(Type) \ template \ TOML_ALWAYS_INLINE \ - void print_to_stream(type val, std::basic_ostream& stream) \ + void print_to_stream(Type val, std::basic_ostream& stream) \ { \ static_assert(sizeof(Char) == 1); \ print_integer_to_stream(val, stream); \ @@ -148,20 +175,13 @@ namespace toml::impl } #else { - char buf[charconv_buffer_length + 1_sz]; - int len = -1; + std::ostringstream ss; + ss.imbue(std::locale::classic()); + ss.precision(std::numeric_limits::digits10 + 1); if (hexfloat) - len = snprintf(buf, charconv_buffer_length + 1_sz, "%a", static_cast(val)); - else - len = snprintf( - buf, charconv_buffer_length + 1_sz, "%.*g", - std::numeric_limits::digits10 + 1, static_cast(val) - ); - TOML_ASSERT(len > 0); - len = static_cast(charconv_buffer_length) < len - ? static_cast(charconv_buffer_length) - : len; - const auto str = std::string_view{ buf, static_cast(len) }; + ss << std::hexfloat; + ss << val; + const auto str = std::move(ss).str(); print_to_stream(str, stream); if (!hexfloat && needs_decimal_point(str)) print_to_stream(".0"sv, stream); @@ -174,10 +194,10 @@ namespace toml::impl extern template TOML_API void print_floating_point_to_stream(double, std::ostream&, bool); #endif - #define TOML_P2S_OVERLOAD(type) \ + #define TOML_P2S_OVERLOAD(Type) \ template \ TOML_ALWAYS_INLINE \ - void print_to_stream(type val, std::basic_ostream& stream) \ + void print_to_stream(Type val, std::basic_ostream& stream) \ { \ static_assert(sizeof(Char) == 1); \ print_floating_point_to_stream(val, stream); \ @@ -200,12 +220,25 @@ namespace toml::impl inline void print_to_stream(T val, std::basic_ostream& stream, size_t zero_pad_to_digits) { static_assert(sizeof(Char) == 1); - char buf[charconv_buffer_length]; - const auto res = std::to_chars(buf, buf + sizeof(buf), val); - const auto len = static_cast(res.ptr - buf); - for (size_t i = len; i < zero_pad_to_digits; i++) - print_to_stream('0', stream); - print_to_stream(buf, static_cast(res.ptr - buf), stream); + #if TOML_INTEGER_CHARCONV + + char buf[charconv_buffer_length]; + const auto res = std::to_chars(buf, buf + sizeof(buf), val); + const auto len = static_cast(res.ptr - buf); + for (size_t i = len; i < zero_pad_to_digits; i++) + print_to_stream('0', stream); + print_to_stream(buf, static_cast(res.ptr - buf), stream); + + #else + + std::ostringstream ss; + ss.imbue(std::locale::classic()); + using cast_type = std::conditional_t, int64_t, uint64_t>; + ss << std::setfill('0') << std::setw(zero_pad_to_digits) << static_cast(val); + const auto str = std::move(ss).str(); + print_to_stream(str, stream); + + #endif } template diff --git a/include/toml++/toml_utf8.h b/include/toml++/toml_utf8.h index 6d76519..081617f 100644 --- a/include/toml++/toml_utf8.h +++ b/include/toml++/toml_utf8.h @@ -9,6 +9,14 @@ namespace toml::impl { + template + [[nodiscard]] TOML_ALWAYS_INLINE + constexpr bool is_match(char32_t codepoint, T... vals) noexcept + { + static_assert((std::is_same_v && ...)); + return ((codepoint == vals) || ...); + } + [[nodiscard]] TOML_ALWAYS_INLINE constexpr bool is_ascii_whitespace(char32_t codepoint) noexcept { @@ -103,6 +111,24 @@ namespace toml::impl ; } + [[nodiscard]] TOML_ALWAYS_INLINE + constexpr uint32_t hex_to_dec(char codepoint) noexcept + { + return codepoint >= 'A' + ? 10u + static_cast(codepoint - (codepoint >= 'a' ? 'a' : 'A')) + : static_cast(codepoint - '0') + ; + } + + [[nodiscard]] TOML_ALWAYS_INLINE + constexpr uint32_t hex_to_dec(char32_t codepoint) noexcept + { + return codepoint >= U'A' + ? 10u + static_cast(codepoint - (codepoint >= U'a' ? U'a' : U'A')) + : static_cast(codepoint - U'0') + ; + } + [[nodiscard]] constexpr bool is_bare_key_start_character(char32_t codepoint) noexcept { @@ -632,7 +658,7 @@ namespace toml::impl #undef TOML_ERROR_CHECK #undef TOML_ERROR #if TOML_ABI_NAMESPACES - } //end abi namespace for TOML_EXCEPTIONS + } //end abi namespace for TOML_EXCEPTIONS / !TOML_EXCEPTIONS #endif } diff --git a/include/toml++/toml_value.h b/include/toml++/toml_value.h index 381ec6d..4af22f6 100644 --- a/include/toml++/toml_value.h +++ b/include/toml++/toml_value.h @@ -371,6 +371,9 @@ namespace toml extern template TOML_API std::ostream& operator << (std::ostream&, const value&); #endif + TOML_PUSH_WARNINGS + TOML_DISABLE_INIT_WARNINGS + template inline optional node::value() const noexcept { @@ -452,6 +455,8 @@ namespace toml TOML_UNREACHABLE; } + TOML_POP_WARNINGS + template inline auto node::value_or(T&& default_value) const noexcept { diff --git a/include/toml++/toml_version.h b/include/toml++/toml_version.h index 2b8cb82..e81387c 100644 --- a/include/toml++/toml_version.h +++ b/include/toml++/toml_version.h @@ -5,7 +5,7 @@ #pragma once #define TOML_LIB_MAJOR 1 -#define TOML_LIB_MINOR 1 +#define TOML_LIB_MINOR 2 #define TOML_LIB_PATCH 0 #define TOML_LANG_MAJOR 1 diff --git a/meson.build b/meson.build index f1ff3b9..f8d05d6 100644 --- a/meson.build +++ b/meson.build @@ -1,13 +1,14 @@ project( 'tomlplusplus', 'cpp', - version : '1.1.0', + version : '1.2.0', license : 'MIT', default_options : [ 'cpp_std=c++17', 'warning_level=3', 'werror=true', - 'cpp_eh=default' + 'cpp_eh=default', + 'b_ndebug=if-release' ] ) @@ -38,7 +39,6 @@ if build_tests or build_examples if compiler.get_id() == 'gcc' add_project_arguments([ - '-g0', '-fmax-errors=5', '-march=native', '-Wno-init-list-lifetime' @@ -49,7 +49,6 @@ if build_tests or build_examples if compiler.get_id() == 'clang' add_project_arguments([ - '-g0', '-ferror-limit=5', '-march=native', '-fchar8_t', @@ -108,6 +107,5 @@ pkgc = import('pkgconfig') pkgc.generate ( name: 'toml++', version: meson.project_version(), - description: 'Header-only TOML config file parser and serializer for modern C++', - subdirs: 'toml++' + description: 'Header-only TOML config file parser and serializer for modern C++' ) diff --git a/tests/catch2.h b/tests/catch2.h index d2cd153..f701ac8 100644 --- a/tests/catch2.h +++ b/tests/catch2.h @@ -6,6 +6,7 @@ #define CATCH_CONFIG_CPP17_STRING_VIEW #define CATCH_CONFIG_FAST_COMPILE #define CATCH_CONFIG_CONSOLE_WIDTH 120 +#define CATCH_CONFIG_CPP11_TO_STRING //windows.h config #ifdef _WIN32 diff --git a/tests/impl_catch2.cpp b/tests/impl_catch2.cpp index 8d8901c..5308311 100644 --- a/tests/impl_catch2.cpp +++ b/tests/impl_catch2.cpp @@ -1,11 +1,13 @@ #define CATCH_CONFIG_RUNNER #include "catch2.h" +#include int main(int argc, char* argv[]) { #ifdef _WIN32 - SetConsoleOutputCP(65001); + SetConsoleOutputCP(65001); #endif - + std::setlocale(LC_ALL, ""); + std::locale::global(std::locale("")); return Catch::Session().run(argc, argv); } diff --git a/tests/manipulating_arrays.cpp b/tests/manipulating_arrays.cpp index 2c40b30..7586aea 100644 --- a/tests/manipulating_arrays.cpp +++ b/tests/manipulating_arrays.cpp @@ -5,6 +5,7 @@ TEST_CASE("arrays - moving") static constexpr auto filename = "foo.toml"sv; parsing_should_succeed( + FILE_LINE_ARGS, S(R"(test = [ "foo" ])"sv), [&](table&& tbl) noexcept { diff --git a/tests/manipulating_tables.cpp b/tests/manipulating_tables.cpp index acc6e19..4cbdb9c 100644 --- a/tests/manipulating_tables.cpp +++ b/tests/manipulating_tables.cpp @@ -5,6 +5,7 @@ TEST_CASE("tables - moving") static constexpr auto filename = "foo.toml"sv; parsing_should_succeed( + FILE_LINE_ARGS, S(R"(test = { val1 = "foo" })"sv), [&](table&& tbl) noexcept { diff --git a/tests/manipulating_values.cpp b/tests/manipulating_values.cpp new file mode 100644 index 0000000..d20645a --- /dev/null +++ b/tests/manipulating_values.cpp @@ -0,0 +1,42 @@ +#include "tests.h" + +TEST_CASE("values - printing") +{ + static constexpr auto print_value = [](auto&& raw) noexcept + { + auto val = toml::value{ std::forward(raw) }; + std::stringstream ss; + ss.imbue(std::locale::classic()); + ss << val; + return ss.str(); + }; + + CHECK(print_value(1) == "1"); + CHECK(print_value(1.0f) == "1.0"); + CHECK(print_value(1.0) == "1.0"); + + CHECK(print_value(1.5f) == "1.5"); + CHECK(print_value(1.5) == "1.5"); + + CHECK(print_value(10) == "10"); + CHECK(print_value(10.0f) == "10.0"); + CHECK(print_value(10.0) == "10.0"); + + CHECK(print_value(100) == "100"); + CHECK(print_value(100.0f) == "100.0"); + CHECK(print_value(100.0) == "100.0"); + + CHECK(print_value(1000) == "1000"); + CHECK(print_value(1000.0f) == "1000.0"); + CHECK(print_value(1000.0) == "1000.0"); + + CHECK(print_value(10000) == "10000"); + CHECK(print_value(10000.0f) == "10000.0"); + CHECK(print_value(10000.0) == "10000.0"); + + // only integers for large values; + // large floats might get output as scientific notation and that's fine + CHECK(print_value(10000000000) == "10000000000"); + CHECK(print_value(100000000000000) == "100000000000000"); +} + diff --git a/tests/meson.build b/tests/meson.build index ed31bec..573f845 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -1,11 +1,12 @@ test_sources = [ 'impl_toml.cpp', 'impl_catch2.cpp', + 'tests.cpp', + 'parsing_floats.cpp', 'parsing_arrays.cpp', 'parsing_booleans.cpp', 'parsing_comments.cpp', 'parsing_dates_and_times.cpp', - 'parsing_floats.cpp', 'parsing_integers.cpp', 'parsing_key_value_pairs.cpp', 'parsing_spec_example.cpp', @@ -13,15 +14,9 @@ test_sources = [ 'parsing_tables.cpp', 'manipulating_arrays.cpp', 'manipulating_tables.cpp', - 'tests.cpp' + 'manipulating_values.cpp', ] -disable_exceptions = 'cpp_eh=none' -no_unreleased_features = '-DTOML_UNRELEASED_FEATURES=0' -toml_char8_strings = '-DTOML_CHAR_8_STRINGS=1' -manually_set_cpp_std = 'cpp_std=none' -cpp20 = '-std=c++2a' -use_tloptional = '-DTARTANLLAMA_OPTIONAL' compiler_supports_char8_strings = compiler.compiles(''' #include #include @@ -35,137 +30,78 @@ compiler_supports_char8_strings = compiler.compiles(''' args : [ '-std=c++2a' ] ) -############################################################################ -### char -############################################################################ +character_types = ['char', 'char8'] +exception_modes = [ true, false ] +unreleased_feature_modes = [ true, false ] +tloptional_modes = [ true, false ] +executables = [] +foreach character_type : character_types + if character_type == 'char8' and not compiler_supports_char8_strings + continue + endif + foreach exceptions : exception_modes + foreach unreleased_features : unreleased_feature_modes + foreach tloptional : tloptional_modes + if tloptional and not (character_type =='char' and unreleased_features and exceptions) + continue + endif -char = executable( - 'char', - test_sources, - include_directories : inc -) -test('char', char) + name = character_type + overrides = [] + args = [] + if character_type =='char8' + overrides += 'cpp_std=none' + args += '-DTOML_CHAR_8_STRINGS=1' + args += '-std=c++2a' + endif -char_noexcept = executable( - 'char_noexcept', - test_sources, - include_directories : inc, - override_options : [ disable_exceptions ] -) -test('char_noexcept', char_noexcept) + if not unreleased_features + name = name + '_strict' + args += '-DTOML_UNRELEASED_FEATURES=0' + endif + if not exceptions + name = name + '_noexcept' + overrides += 'cpp_eh=none' + endif -char_strict = executable( - 'char_strict', - test_sources, - include_directories : inc, - cpp_args : [ no_unreleased_features ] -) -test('char_strict', char_strict) + if tloptional + name = name + '_tlopt' + args += '-DTARTANLLAMA_OPTIONAL' + endif + executables += [[ + name, + executable( + name, + test_sources, + include_directories : inc, + cpp_args : args, + override_options : overrides + ) + ]] -char_strict_noexcept = executable( - 'char_strict_noexcept', - test_sources, - include_directories : inc, - override_options : [ disable_exceptions ], - cpp_args : [ no_unreleased_features ] -) -test('char_strict_noexcept', char_strict_noexcept) + endforeach # tloptional_modes + endforeach # unreleased_feature_modes + endforeach # exception_modes +endforeach # character_type +locales = [ + 'C', + 'en_US.utf8', + 'ja_JP.utf8', + 'it_IT.utf8', + 'tr_TR.utf8', + 'fi_FI.utf8', + 'fr_FR.utf8', + 'zh_CN.utf8', + 'de_DE.utf8' +] -############################################################################ -### char w/ tl::optional -############################################################################ - - -char_tlopt = executable( - 'char_tlopt', - test_sources, - include_directories : inc, - cpp_args : [ use_tloptional ] -) -test('char_tlopt', char_tlopt) - - -char_tlopt_strict = executable( - 'char_tlopt_strict', - test_sources, - include_directories : inc, - cpp_args : [ no_unreleased_features, use_tloptional ] -) -test('char_tlopt_strict', char_tlopt_strict) - - -if compiler_supports_char8_strings - - - ############################################################################ - ### char8 - ############################################################################ - - - char8 = executable( - 'char8', - test_sources, - include_directories : inc, - override_options : [ manually_set_cpp_std ], - cpp_args : [ cpp20, toml_char8_strings ] - ) - test('char8', char8) - - char8_noexcept = executable( - 'char8_noexcept', - test_sources, - include_directories : inc, - override_options : [ manually_set_cpp_std, disable_exceptions ], - cpp_args : [ cpp20, toml_char8_strings ] - ) - test('char8_noexcept', char8_noexcept) - - char8_strict = executable( - 'char8_strict', - test_sources, - include_directories : inc, - override_options : [ manually_set_cpp_std ], - cpp_args : [ cpp20, toml_char8_strings, no_unreleased_features ] - ) - test('char8_strict', char8_strict) - - char8_strict_noexcept = executable( - 'char8_strict_noexcept', - test_sources, - include_directories : inc, - override_options : [ manually_set_cpp_std, disable_exceptions ], - cpp_args : [ cpp20, toml_char8_strings, no_unreleased_features ] - ) - test('char8_strict_noexcept', char8_strict_noexcept) - - - ############################################################################ - ### char8 w/ tl::optional - ############################################################################ - - - char8_tlopt = executable( - 'char8_tlopt', - test_sources, - include_directories : inc, - override_options : [ manually_set_cpp_std ], - cpp_args : [ cpp20, toml_char8_strings, use_tloptional ] - ) - test('char8_tlopt', char8_tlopt) - - char8_tlopt_strict = executable( - 'char8_tlopt_strict', - test_sources, - include_directories : inc, - override_options : [ manually_set_cpp_std ], - cpp_args : [ cpp20, toml_char8_strings, no_unreleased_features, use_tloptional ] - ) - test('char8_tlopt_strict', char8_tlopt_strict) - - -endif +foreach locale : locales + foreach executable : executables + test(locale + '_' + executable[0], executable[1], env : ['LC_ALL=' + locale]) + endforeach +endforeach diff --git a/tests/parsing_arrays.cpp b/tests/parsing_arrays.cpp index a5ae9cf..2100e36 100644 --- a/tests/parsing_arrays.cpp +++ b/tests/parsing_arrays.cpp @@ -2,7 +2,9 @@ TEST_CASE("parsing - arrays") { - parsing_should_succeed(S(R"( + parsing_should_succeed( + FILE_LINE_ARGS, + S(R"( integers = [ 1, 2, 3 ] integers2 = [ 1, 2, 3 @@ -94,7 +96,9 @@ string_array = [ "all", 'strings', """are the same""", '''type''' ] // toml/issues/665 (heterogeneous arrays) #if TOML_LANG_AT_LEAST(1, 0, 0) - parsing_should_succeed(S(R"( + parsing_should_succeed( + FILE_LINE_ARGS, + S(R"( # Mixed-type arrays are allowed numbers = [ 0.1, 0.2, 0.5, 1, 2, 5 ] contributors = [ @@ -134,7 +138,7 @@ contributors = [ #else - parsing_should_fail(S("numbers = [ 0.1, 0.2, 0.5, 1, 2, 5 ]"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("numbers = [ 0.1, 0.2, 0.5, 1, 2, 5 ]"sv)); #endif } diff --git a/tests/parsing_booleans.cpp b/tests/parsing_booleans.cpp index b86aed9..8f1a9b8 100644 --- a/tests/parsing_booleans.cpp +++ b/tests/parsing_booleans.cpp @@ -2,7 +2,9 @@ TEST_CASE("parsing - booleans") { - parsing_should_succeed(S(R"( + parsing_should_succeed( + FILE_LINE_ARGS, + S(R"( bool1 = true bool2 = false )"sv), @@ -14,14 +16,14 @@ bool2 = false ); // "Always lowercase." - parsing_should_fail(S("bool = True"sv)); - parsing_should_fail(S("bool = TRUE"sv)); - parsing_should_fail(S("bool = tRUE"sv)); - parsing_should_fail(S("bool = False"sv)); - parsing_should_fail(S("bool = FALSE"sv)); - parsing_should_fail(S("bool = fALSE"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("bool = True"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("bool = TRUE"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("bool = tRUE"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("bool = False"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("bool = FALSE"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("bool = fALSE"sv)); // value tests - parse_expected_value(" true", true); - parse_expected_value("false", false); + parse_expected_value(FILE_LINE_ARGS, " true", true); + parse_expected_value(FILE_LINE_ARGS, "false", false); } diff --git a/tests/parsing_comments.cpp b/tests/parsing_comments.cpp index dc13b26..a2dd93e 100644 --- a/tests/parsing_comments.cpp +++ b/tests/parsing_comments.cpp @@ -2,7 +2,9 @@ TEST_CASE("parsing - comments") { - parsing_should_succeed(S(R"( + parsing_should_succeed( + FILE_LINE_ARGS, + S(R"( # This is a full-line comment key = "value" # This is a comment at the end of a line another = "# This is not a comment" @@ -15,7 +17,9 @@ another = "# This is not a comment" } ); - parsing_should_succeed(S(R"(# this = "looks like a KVP but is commented out)"sv), + parsing_should_succeed( + FILE_LINE_ARGS, + S(R"(# this = "looks like a KVP but is commented out)"sv), [](table&& tbl) noexcept { CHECK(tbl.size() == 0); @@ -26,15 +30,15 @@ another = "# This is not a comment" { // toml/issues/567 (disallow non-TAB control characters in comments) // 00 - 08 - parsing_should_fail(S("# \u0000"sv)); - parsing_should_fail(S("# \u0001"sv)); - parsing_should_fail(S("# \u0002"sv)); - parsing_should_fail(S("# \u0003"sv)); - parsing_should_fail(S("# \u0004"sv)); - parsing_should_fail(S("# \u0005"sv)); - parsing_should_fail(S("# \u0006"sv)); - parsing_should_fail(S("# \u0007"sv)); - parsing_should_fail(S("# \u0008"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("# \u0000"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("# \u0001"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("# \u0002"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("# \u0003"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("# \u0004"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("# \u0005"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("# \u0006"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("# \u0007"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("# \u0008"sv)); // skip tab and line breaks (real and otherwise) // \u0009 is \t @@ -44,30 +48,30 @@ another = "# This is not a comment" // \u000D is \r // 0E - 1F - parsing_should_fail(S("# \u000E"sv)); - parsing_should_fail(S("# \u000F"sv)); - parsing_should_fail(S("# \u0010"sv)); - parsing_should_fail(S("# \u0011"sv)); - parsing_should_fail(S("# \u0012"sv)); - parsing_should_fail(S("# \u0013"sv)); - parsing_should_fail(S("# \u0014"sv)); - parsing_should_fail(S("# \u0015"sv)); - parsing_should_fail(S("# \u0016"sv)); - parsing_should_fail(S("# \u0017"sv)); - parsing_should_fail(S("# \u0018"sv)); - parsing_should_fail(S("# \u0019"sv)); - parsing_should_fail(S("# \u001A"sv)); - parsing_should_fail(S("# \u001B"sv)); - parsing_should_fail(S("# \u001C"sv)); - parsing_should_fail(S("# \u001D"sv)); - parsing_should_fail(S("# \u001E"sv)); - parsing_should_fail(S("# \u001F"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("# \u000E"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("# \u000F"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("# \u0010"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("# \u0011"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("# \u0012"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("# \u0013"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("# \u0014"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("# \u0015"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("# \u0016"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("# \u0017"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("# \u0018"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("# \u0019"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("# \u001A"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("# \u001B"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("# \u001C"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("# \u001D"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("# \u001E"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("# \u001F"sv)); // 7F - parsing_should_fail(S("# \u007F"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("# \u007F"sv)); } else { - parsing_should_succeed(S( + parsing_should_succeed(FILE_LINE_ARGS, S( "## 00 - 08" "# \u0000 " "# \u0001 " diff --git a/tests/parsing_dates_and_times.cpp b/tests/parsing_dates_and_times.cpp index 40a6c4a..e43b5bc 100644 --- a/tests/parsing_dates_and_times.cpp +++ b/tests/parsing_dates_and_times.cpp @@ -5,7 +5,9 @@ TOML_DISABLE_INIT_WARNINGS TEST_CASE("parsing - dates and times") { - parsing_should_succeed(S(R"( + parsing_should_succeed( + FILE_LINE_ARGS, + S(R"( odt1 = 1979-05-27T07:32:00Z odt2 = 1979-05-27T00:32:00-07:00 odt3 = 1979-05-27T00:32:00.999999-07:00 @@ -40,89 +42,120 @@ lt2 = 00:32:00.999999 ); //value tests - parse_expected_value( "1987-03-16"sv, date{ 1987, 3, 16 } ); - parse_expected_value( "10:20:30"sv, toml::time{ 10, 20, 30 } ); - parse_expected_value( "10:20:30.04"sv, toml::time{ 10, 20, 30, 40000000 } ); + parse_expected_value(FILE_LINE_ARGS, "1987-03-16"sv, date{ 1987, 3, 16 } ); + parse_expected_value(FILE_LINE_ARGS, "10:20:30"sv, toml::time{ 10, 20, 30 } ); + parse_expected_value(FILE_LINE_ARGS, "10:20:30.04"sv, toml::time{ 10, 20, 30, 40000000 } ); { const auto val = date_time{ { 1987, 3, 16 }, { 10, 20, 30 } }; - parse_expected_value("1987-03-16T10:20:30"sv, val); - parse_expected_value("1987-03-16 10:20:30"sv, val); + parse_expected_value(FILE_LINE_ARGS, "1987-03-16T10:20:30"sv, val); + parse_expected_value(FILE_LINE_ARGS, "1987-03-16 10:20:30"sv, val); } { const auto val = date_time{ { 1987, 3, 16 }, { 10, 20, 30 }, { -9, -30 } }; - parse_expected_value("1987-03-16T10:20:30-09:30"sv, val); - parse_expected_value("1987-03-16 10:20:30-09:30"sv, val); + parse_expected_value(FILE_LINE_ARGS, "1987-03-16T10:20:30-09:30"sv, val); + parse_expected_value(FILE_LINE_ARGS, "1987-03-16 10:20:30-09:30"sv, val); } { const auto val = date_time{ { 1987, 3, 16 }, { 10, 20, 30 }, { 9, 30 } }; - parse_expected_value("1987-03-16T10:20:30+09:30"sv, val); - parse_expected_value("1987-03-16 10:20:30+09:30"sv, val); + parse_expected_value(FILE_LINE_ARGS, "1987-03-16T10:20:30+09:30"sv, val); + parse_expected_value(FILE_LINE_ARGS, "1987-03-16 10:20:30+09:30"sv, val); } { const auto val = date_time{ { 1987, 3, 16 }, { 10, 20, 30, 40000000 } }; - parse_expected_value("1987-03-16T10:20:30.04"sv, val); - parse_expected_value("1987-03-16 10:20:30.04"sv, val); + parse_expected_value(FILE_LINE_ARGS, "1987-03-16T10:20:30.04"sv, val); + parse_expected_value(FILE_LINE_ARGS, "1987-03-16 10:20:30.04"sv, val); } { const auto val = date_time{ { 1987, 3, 16 }, { 10, 20, 30, 40000000 }, { -9, -30 } }; - parse_expected_value("1987-03-16T10:20:30.04-09:30"sv, val); - parse_expected_value("1987-03-16 10:20:30.04-09:30"sv, val); + parse_expected_value(FILE_LINE_ARGS, "1987-03-16T10:20:30.04-09:30"sv, val); + parse_expected_value(FILE_LINE_ARGS, "1987-03-16 10:20:30.04-09:30"sv, val); } { const auto val = date_time{ { 1987, 3, 16 }, { 10, 20, 30, 40000000 }, { 9, 30 } }; - parse_expected_value("1987-03-16T10:20:30.04+09:30"sv, val); - parse_expected_value("1987-03-16 10:20:30.04+09:30"sv, val); + parse_expected_value(FILE_LINE_ARGS, "1987-03-16T10:20:30.04+09:30"sv, val); + parse_expected_value(FILE_LINE_ARGS, "1987-03-16 10:20:30.04+09:30"sv, val); } { const auto val = date_time{ { 1987, 3, 16 }, { 10, 20, 30 }, {} }; - parse_expected_value("1987-03-16T10:20:30Z"sv, val); - parse_expected_value("1987-03-16 10:20:30Z"sv, val); + parse_expected_value(FILE_LINE_ARGS, "1987-03-16T10:20:30Z"sv, val); + parse_expected_value(FILE_LINE_ARGS, "1987-03-16 10:20:30Z"sv, val); } { const auto val = date_time{ { 1987, 3, 16 }, { 10, 20, 30, 40000000 }, {} }; - parse_expected_value("1987-03-16T10:20:30.04Z"sv, val); - parse_expected_value("1987-03-16 10:20:30.04Z"sv, val); + parse_expected_value(FILE_LINE_ARGS, "1987-03-16T10:20:30.04Z"sv, val); + parse_expected_value(FILE_LINE_ARGS, "1987-03-16 10:20:30.04Z"sv, val); } // toml/issues/671 (allow omission of seconds) #if TOML_LANG_UNRELEASED - parse_expected_value( "10:20"sv, toml::time{ 10, 20 } ); + parse_expected_value(FILE_LINE_ARGS, "10:20"sv, toml::time{ 10, 20 } ); { const auto val = date_time{ { 1987, 3, 16 }, { 10, 20 } }; - parse_expected_value("1987-03-16T10:20"sv, val ); - parse_expected_value("1987-03-16 10:20"sv, val ); + parse_expected_value(FILE_LINE_ARGS, "1987-03-16T10:20"sv, val ); + parse_expected_value(FILE_LINE_ARGS, "1987-03-16 10:20"sv, val ); } { const auto val = date_time{ { 1987, 3, 16 }, { 10, 20 }, { -9, -30 } }; - parse_expected_value("1987-03-16T10:20-09:30"sv, val); - parse_expected_value("1987-03-16 10:20-09:30"sv, val); + parse_expected_value(FILE_LINE_ARGS, "1987-03-16T10:20-09:30"sv, val); + parse_expected_value(FILE_LINE_ARGS, "1987-03-16 10:20-09:30"sv, val); } { const auto val = date_time{ { 1987, 3, 16 }, { 10, 20 }, { 9, 30 } }; - parse_expected_value("1987-03-16T10:20+09:30"sv, val); - parse_expected_value("1987-03-16 10:20+09:30"sv, val); + parse_expected_value(FILE_LINE_ARGS, "1987-03-16T10:20+09:30"sv, val); + parse_expected_value(FILE_LINE_ARGS, "1987-03-16 10:20+09:30"sv, val); } { const auto val = date_time{ { 1987, 3, 16 }, { 10, 20 }, {} }; - parse_expected_value("1987-03-16T10:20Z"sv, val); - parse_expected_value("1987-03-16 10:20Z"sv, val); + parse_expected_value(FILE_LINE_ARGS, "1987-03-16T10:20Z"sv, val); + parse_expected_value(FILE_LINE_ARGS, "1987-03-16 10:20Z"sv, val); } #else - parsing_should_fail("10:20"sv); - parsing_should_fail("1987-03-16T10:20"sv); - parsing_should_fail("1987-03-16 10:20"sv); - parsing_should_fail("1987-03-16T10:20-09:30"sv); - parsing_should_fail("1987-03-16 10:20-09:30"sv); - parsing_should_fail("1987-03-16T10:20+09:30"sv); - parsing_should_fail("1987-03-16 10:20+09:30"sv); - parsing_should_fail("1987-03-16T10:20Z"sv); - parsing_should_fail("1987-03-16 10:20Z"sv); + parsing_should_fail(FILE_LINE_ARGS, "10:20"sv); + parsing_should_fail(FILE_LINE_ARGS, "1987-03-16T10:20"sv); + parsing_should_fail(FILE_LINE_ARGS, "1987-03-16 10:20"sv); + parsing_should_fail(FILE_LINE_ARGS, "1987-03-16T10:20-09:30"sv); + parsing_should_fail(FILE_LINE_ARGS, "1987-03-16 10:20-09:30"sv); + parsing_should_fail(FILE_LINE_ARGS, "1987-03-16T10:20+09:30"sv); + parsing_should_fail(FILE_LINE_ARGS, "1987-03-16 10:20+09:30"sv); + parsing_should_fail(FILE_LINE_ARGS, "1987-03-16T10:20Z"sv); + parsing_should_fail(FILE_LINE_ARGS, "1987-03-16 10:20Z"sv); #endif + + // eof tests + parsing_should_fail(FILE_LINE_ARGS, S("val = 1987-03-1"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("val = 1987-03-"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("val = 1987-03"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("val = 1987-0"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("val = 1987-"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("val = 10:20:30."sv)); + parsing_should_fail(FILE_LINE_ARGS, S("val = 10:20:3"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("val = 10:20:"sv)); + #if !TOML_LANG_UNRELEASED // toml/issues/671 (allow omission of seconds) + parsing_should_fail(FILE_LINE_ARGS, S("val = 10:20"sv)); + #endif + parsing_should_fail(FILE_LINE_ARGS, S("val = 10:2"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("val = 10:"sv)); + + parsing_should_fail(FILE_LINE_ARGS, S("val = 1987-03-16 10:20:30.04-09:3"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("val = 1987-03-16 10:20:30.04-09:"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("val = 1987-03-16 10:20:30.04-09"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("val = 1987-03-16 10:20:30.04-0"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("val = 1987-03-16 10:20:30.04-"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("val = 1987-03-16 10:20:30."sv)); + parsing_should_fail(FILE_LINE_ARGS, S("val = 1987-03-16 10:20:3"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("val = 1987-03-16 10:20:"sv)); + #if !TOML_LANG_UNRELEASED // toml/issues/671 (allow omission of seconds) + parsing_should_fail(FILE_LINE_ARGS, S("val = 1987-03-16 10:20"sv)); + #endif + parsing_should_fail(FILE_LINE_ARGS, S("val = 1987-03-16 10:2"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("val = 1987-03-16 10:"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("val = 1987-03-16 10"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("val = 1987-03-16 1"sv)); } TOML_POP_WARNINGS diff --git a/tests/parsing_floats.cpp b/tests/parsing_floats.cpp index 2a11dc1..8dbee99 100644 --- a/tests/parsing_floats.cpp +++ b/tests/parsing_floats.cpp @@ -2,7 +2,9 @@ TEST_CASE("parsing - floats") { - parsing_should_succeed(S(R"( + parsing_should_succeed( + FILE_LINE_ARGS, + S(R"( # fractional flt1 = +1.0 flt2 = 3.1415 @@ -32,12 +34,13 @@ flt8 = 224_617.445_991_228 ); // "Each underscore must be surrounded by at least one digit on each side." - parsing_should_fail(S("flt8 = 224_617.445_991_228_"sv)); - parsing_should_fail(S("flt8 = _224_617.445_991_228"sv)); - parsing_should_fail(S("flt8 = 224__617.445_991_228"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("flt8 = 224_617.445_991_228_"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("flt8 = _224_617.445_991_228"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("flt8 = 224__617.445_991_228"sv)); // "Float values -0.0 and +0.0 are valid and should map according to IEEE 754." parsing_should_succeed( + FILE_LINE_ARGS, S(R"(zeroes = [-0.0, +0.0])"sv), [](table&& tbl) noexcept { @@ -47,76 +50,109 @@ flt8 = 224_617.445_991_228 ); //value tests - parse_expected_value( "1e1"sv, 1e1 ); - parse_expected_value( "1e-1"sv, 1e-1 ); - parse_expected_value( "1e+1"sv, 1e+1 ); - parse_expected_value( "1.0"sv, 1.0 ); - parse_expected_value( "1.0e1"sv, 1.0e1 ); - parse_expected_value( "1.0e-1"sv, 1.0e-1 ); - parse_expected_value( "1.0e+1"sv, 1.0e+1 ); - parse_expected_value( "+1e1"sv, +1e1 ); - parse_expected_value( "+1.0"sv, +1.0 ); - parse_expected_value( "+1.0e1"sv, +1.0e1 ); - parse_expected_value( "+1.0e+1"sv, +1.0e+1 ); - parse_expected_value( "+1.0e-1"sv, +1.0e-1 ); - parse_expected_value( "-1.0e+1"sv, -1.0e+1 ); - parse_expected_value( "-1e1"sv, -1e1 ); - parse_expected_value( "-1.0"sv, -1.0 ); - parse_expected_value( "-1.0e1"sv, -1.0e1 ); - parse_expected_value( "-1.0e-1"sv, -1.0e-1 ); - parse_expected_value( "1.0"sv, 1.0 ); - parse_expected_value( "0.1"sv, 0.1 ); - parse_expected_value( "0.001"sv, 0.001 ); - parse_expected_value( "0.100"sv, 0.100 ); - parse_expected_value( "+3.14"sv, +3.14 ); - parse_expected_value( "-3.14"sv, -3.14 ); - parse_expected_value( "3.1415_9265_3589"sv, 3.141592653589 ); - parse_expected_value( "+3.1415_9265_3589"sv, +3.141592653589 ); - parse_expected_value( "-3.1415_9265_3589"sv, -3.141592653589 ); - parse_expected_value( "123_456.789"sv, 123456.789 ); - parse_expected_value( "+123_456.789"sv, +123456.789 ); - parse_expected_value( "-123_456.789"sv, -123456.789 ); - parse_expected_value( "+0.0"sv, +0.0 ); - parse_expected_value( "-0.0"sv, -0.0 ); - parse_expected_value( "1e10"sv, 1e10 ); - parse_expected_value( "1e+10"sv, 1e+10 ); - parse_expected_value( "1e-10"sv, 1e-10 ); - parse_expected_value( "+1e10"sv, +1e10 ); - parse_expected_value( "+1e+10"sv, +1e+10 ); - parse_expected_value( "+1e-10"sv, +1e-10 ); - parse_expected_value( "-1e10"sv, -1e10 ); - parse_expected_value( "-1e+10"sv, -1e+10 ); - parse_expected_value( "-1e-10"sv, -1e-10 ); - parse_expected_value( "123e-10"sv, 123e-10 ); - parse_expected_value( "1E10"sv, 1E10 ); - parse_expected_value( "1E+10"sv, 1E+10 ); - parse_expected_value( "1E-10"sv, 1E-10 ); - parse_expected_value( "123E-10"sv, 123E-10 ); - parse_expected_value( "1_2_3E-10"sv, 123E-10 ); - parse_expected_value( "1_2_3E-1_0"sv, 123E-10 ); - parse_expected_value( "+0e0"sv, +0e0 ); - parse_expected_value( "-0e0"sv, -0e0 ); - parse_expected_value( "1_2_3E-01"sv, 123E-01 ); - parse_expected_value( "1_2_3E-0_1"sv, 123E-01 ); - parse_expected_value( "6.02e23"sv, 6.02e23 ); - parse_expected_value( "6.02e+23"sv, 6.02e+23 ); - parse_expected_value( "1.112_650_06e-17"sv, 1.11265006e-17 ); + parse_expected_value( FILE_LINE_ARGS, "1e1"sv, 1e1); + parse_expected_value( FILE_LINE_ARGS, "1e-1"sv, 1e-1); + parse_expected_value( FILE_LINE_ARGS, "1e+1"sv, 1e+1); + parse_expected_value( FILE_LINE_ARGS, "1.0"sv, 1.0); + parse_expected_value( FILE_LINE_ARGS, "1.0e1"sv, 1.0e1); + parse_expected_value( FILE_LINE_ARGS, "1.0e-1"sv, 1.0e-1); + parse_expected_value( FILE_LINE_ARGS, "1.0e+1"sv, 1.0e+1); + parse_expected_value( FILE_LINE_ARGS, "+1e1"sv, +1e1); + parse_expected_value( FILE_LINE_ARGS, "+1.0"sv, +1.0); + parse_expected_value( FILE_LINE_ARGS, "+1.0e1"sv, +1.0e1); + parse_expected_value( FILE_LINE_ARGS, "+1.0e+1"sv, +1.0e+1); + parse_expected_value( FILE_LINE_ARGS, "+1.0e-1"sv, +1.0e-1); + parse_expected_value( FILE_LINE_ARGS, "-1.0e+1"sv, -1.0e+1); + parse_expected_value( FILE_LINE_ARGS, "-1e1"sv, -1e1); + parse_expected_value( FILE_LINE_ARGS, "-1.0"sv, -1.0); + parse_expected_value( FILE_LINE_ARGS, "-1.0e1"sv, -1.0e1); + parse_expected_value( FILE_LINE_ARGS, "-1.0e-1"sv, -1.0e-1); + parse_expected_value( FILE_LINE_ARGS, "1.0"sv, 1.0); + parse_expected_value( FILE_LINE_ARGS, "0.1"sv, 0.1); + parse_expected_value( FILE_LINE_ARGS, "0.001"sv, 0.001); + parse_expected_value( FILE_LINE_ARGS, "0.100"sv, 0.100); + parse_expected_value( FILE_LINE_ARGS, "+3.14"sv, +3.14); + parse_expected_value( FILE_LINE_ARGS, "-3.14"sv, -3.14); + parse_expected_value( FILE_LINE_ARGS, "3.1415_9265_3589"sv, 3.141592653589); + parse_expected_value( FILE_LINE_ARGS, "+3.1415_9265_3589"sv, +3.141592653589); + parse_expected_value( FILE_LINE_ARGS, "-3.1415_9265_3589"sv, -3.141592653589); + parse_expected_value( FILE_LINE_ARGS, "123_456.789"sv, 123456.789); + parse_expected_value( FILE_LINE_ARGS, "+123_456.789"sv, +123456.789); + parse_expected_value( FILE_LINE_ARGS, "-123_456.789"sv, -123456.789); + parse_expected_value( FILE_LINE_ARGS, "+0.0"sv, +0.0); + parse_expected_value( FILE_LINE_ARGS, "-0.0"sv, -0.0); + parse_expected_value( FILE_LINE_ARGS, "1e10"sv, 1e10); + parse_expected_value( FILE_LINE_ARGS, "1e+10"sv, 1e+10); + parse_expected_value( FILE_LINE_ARGS, "1e-10"sv, 1e-10); + parse_expected_value( FILE_LINE_ARGS, "+1e10"sv, +1e10); + parse_expected_value( FILE_LINE_ARGS, "+1e+10"sv, +1e+10); + parse_expected_value( FILE_LINE_ARGS, "+1e-10"sv, +1e-10); + parse_expected_value( FILE_LINE_ARGS, "-1e10"sv, -1e10); + parse_expected_value( FILE_LINE_ARGS, "-1e+10"sv, -1e+10); + parse_expected_value( FILE_LINE_ARGS, "-1e-10"sv, -1e-10); + parse_expected_value( FILE_LINE_ARGS, "123e-10"sv, 123e-10); + parse_expected_value( FILE_LINE_ARGS, "1E10"sv, 1E10); + parse_expected_value( FILE_LINE_ARGS, "1E+10"sv, 1E+10); + parse_expected_value( FILE_LINE_ARGS, "1E-10"sv, 1E-10); + parse_expected_value( FILE_LINE_ARGS, "123E-10"sv, 123E-10); + parse_expected_value( FILE_LINE_ARGS, "1_2_3E-10"sv, 123E-10); + parse_expected_value( FILE_LINE_ARGS, "1_2_3E-1_0"sv, 123E-10); + parse_expected_value( FILE_LINE_ARGS, "+0e0"sv, +0e0); + parse_expected_value( FILE_LINE_ARGS, "-0e0"sv, -0e0); + parse_expected_value( FILE_LINE_ARGS, "1_2_3E-01"sv, 123E-01); + parse_expected_value( FILE_LINE_ARGS, "1_2_3E-0_1"sv, 123E-01); + parse_expected_value( FILE_LINE_ARGS, "6.02e23"sv, 6.02e23); + parse_expected_value( FILE_LINE_ARGS, "6.02e+23"sv, 6.02e+23); + parse_expected_value( FILE_LINE_ARGS, "1.112_650_06e-17"sv, 1.11265006e-17); //toml/issues/562 (hexfloats) #if TOML_LANG_UNRELEASED - parse_expected_value(" 0x10.1p0"sv, 0x10.1p0 ); - parse_expected_value(" 0x0.3p10"sv, 0x0.3p10 ); - parse_expected_value(" 0x12.2P2"sv, 0x12.2P2 ); + parse_expected_value(FILE_LINE_ARGS, " 0x1.2p3"sv, 0x1.2p3); + parse_expected_value(FILE_LINE_ARGS, " 0x10p1"sv, 0x10p1); + parse_expected_value(FILE_LINE_ARGS, " 0x10p-1"sv, 0x10p-1); + parse_expected_value(FILE_LINE_ARGS, " 0x10p+1"sv, 0x10p+1); + parse_expected_value(FILE_LINE_ARGS, " -0x10p1"sv, -0x10p1); + parse_expected_value(FILE_LINE_ARGS, " -0x10p-1"sv, -0x10p-1); + parse_expected_value(FILE_LINE_ARGS, " +0x10p1"sv, +0x10p1); + parse_expected_value(FILE_LINE_ARGS, " +0x10p+1"sv, +0x10p+1); + parse_expected_value(FILE_LINE_ARGS, " -0x10p+1"sv, -0x10p+1); + parse_expected_value(FILE_LINE_ARGS, " +0x10p-1"sv, +0x10p-1); + parse_expected_value(FILE_LINE_ARGS, " 0x10.1p1"sv, 0x10.1p1); + parse_expected_value(FILE_LINE_ARGS, " 0x10.1p-1"sv, 0x10.1p-1); + parse_expected_value(FILE_LINE_ARGS, " 0x10.1p+1"sv, 0x10.1p+1); + parse_expected_value(FILE_LINE_ARGS, " -0x10.1p1"sv, -0x10.1p1); + parse_expected_value(FILE_LINE_ARGS, " -0x10.1p-1"sv, -0x10.1p-1); + parse_expected_value(FILE_LINE_ARGS, " +0x10.1p1"sv, +0x10.1p1); + parse_expected_value(FILE_LINE_ARGS, " +0x10.1p+1"sv, +0x10.1p+1); + parse_expected_value(FILE_LINE_ARGS, " -0x10.1p+1"sv, -0x10.1p+1); + parse_expected_value(FILE_LINE_ARGS, " +0x10.1p-1"sv, +0x10.1p-1); #else - parsing_should_fail(S("val = 0x10.1p0"sv)); - parsing_should_fail(S("val = 0x0.3p10"sv)); - parsing_should_fail(S("val = 0x12.2P2"sv)); + parsing_should_fail(FILE_LINE_ARGS, S(" val = 0x10p1"sv)); + parsing_should_fail(FILE_LINE_ARGS, S(" val = 0x10p-1"sv)); + parsing_should_fail(FILE_LINE_ARGS, S(" val = 0x10p+1"sv)); + parsing_should_fail(FILE_LINE_ARGS, S(" val = -0x10p1"sv)); + parsing_should_fail(FILE_LINE_ARGS, S(" val = -0x10p-1"sv)); + parsing_should_fail(FILE_LINE_ARGS, S(" val = +0x10p1"sv)); + parsing_should_fail(FILE_LINE_ARGS, S(" val = +0x10p+1"sv)); + parsing_should_fail(FILE_LINE_ARGS, S(" val = -0x10p+1"sv)); + parsing_should_fail(FILE_LINE_ARGS, S(" val = +0x10p-1"sv)); + parsing_should_fail(FILE_LINE_ARGS, S(" val = 0x10.1p1"sv)); + parsing_should_fail(FILE_LINE_ARGS, S(" val = 0x10.1p-1"sv)); + parsing_should_fail(FILE_LINE_ARGS, S(" val = 0x10.1p+1"sv)); + parsing_should_fail(FILE_LINE_ARGS, S(" val = -0x10.1p1"sv)); + parsing_should_fail(FILE_LINE_ARGS, S(" val = -0x10.1p-1"sv)); + parsing_should_fail(FILE_LINE_ARGS, S(" val = +0x10.1p1"sv)); + parsing_should_fail(FILE_LINE_ARGS, S(" val = +0x10.1p+1"sv)); + parsing_should_fail(FILE_LINE_ARGS, S(" val = -0x10.1p+1"sv)); + parsing_should_fail(FILE_LINE_ARGS, S(" val = +0x10.1p-1"sv)); #endif } TEST_CASE("parsing - inf and nan") { - parsing_should_succeed(S(R"( + parsing_should_succeed( + FILE_LINE_ARGS, + S(R"( # infinity sf1 = inf # positive infinity sf2 = +inf # positive infinity diff --git a/tests/parsing_integers.cpp b/tests/parsing_integers.cpp index 3eec12c..c9d61cd 100644 --- a/tests/parsing_integers.cpp +++ b/tests/parsing_integers.cpp @@ -2,7 +2,9 @@ TEST_CASE("parsing - integers (decimal)") { - parsing_should_succeed(S(R"( + parsing_should_succeed( + FILE_LINE_ARGS, + S(R"( int1 = +99 int2 = 42 int3 = 0 @@ -24,21 +26,22 @@ int7 = 1_2_3_4_5 # VALID but discouraged ); // "Each underscore must be surrounded by at least one digit on each side." - parsing_should_fail(S("int5 = 1__000"sv)); - parsing_should_fail(S("int5 = _1_000"sv)); - parsing_should_fail(S("int5 = 1_000_"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("int5 = 1__000"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("int5 = _1_000"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("int5 = 1_000_"sv)); // "Leading zeroes are not allowed." - parsing_should_fail(S("int1 = +099"sv)); - parsing_should_fail(S("int2 = 042"sv)); - parsing_should_fail(S("int3 = 00"sv)); - parsing_should_fail(S("int4 = -017"sv)); - parsing_should_fail(S("int5 = 01_000"sv)); - parsing_should_fail(S("int6 = 05_349_221"sv)); - parsing_should_fail(S("int7 = 01_2_3_4_5"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("int1 = +099"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("int2 = 042"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("int3 = 00"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("int4 = -017"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("int5 = 01_000"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("int6 = 05_349_221"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("int7 = 01_2_3_4_5"sv)); // "Integer values -0 and +0 are valid and identical to an unprefixed zero." parsing_should_succeed( + FILE_LINE_ARGS, S("zeroes = [-0, +0]"sv), [](table&& tbl) noexcept { @@ -47,29 +50,31 @@ int7 = 1_2_3_4_5 # VALID but discouraged } ); - parsing_should_fail(S("val = +-1"sv)); - parsing_should_fail(S("val = -+1"sv)); - parsing_should_fail(S("val = ++1"sv)); - parsing_should_fail(S("val = --1"sv)); - parsing_should_fail(S("val = 1-"sv)); - parsing_should_fail(S("val = 1+"sv)); - parsing_should_fail(S("val = -1+"sv)); - parsing_should_fail(S("val = +1-"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("val = +-1"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("val = -+1"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("val = ++1"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("val = --1"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("val = 1-"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("val = 1+"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("val = -1+"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("val = +1-"sv)); // value tests - parse_expected_value( "1234"sv, 1234 ); - parse_expected_value( "+1234"sv, 1234 ); - parse_expected_value( "-1234"sv, -1234 ); - parse_expected_value( "0"sv, 0 ); - parse_expected_value( "1_2_3_4"sv, 1234 ); - parse_expected_value( "+1_2_3_4"sv, 1234 ); - parse_expected_value( "-1_2_3_4"sv, -1234 ); - parse_expected_value( "123_456_789"sv, 123456789 ); + parse_expected_value(FILE_LINE_ARGS, "1234"sv, 1234); + parse_expected_value(FILE_LINE_ARGS, "+1234"sv, 1234); + parse_expected_value(FILE_LINE_ARGS, "-1234"sv, -1234); + parse_expected_value(FILE_LINE_ARGS, "0"sv, 0); + parse_expected_value(FILE_LINE_ARGS, "1_2_3_4"sv, 1234); + parse_expected_value(FILE_LINE_ARGS, "+1_2_3_4"sv, 1234); + parse_expected_value(FILE_LINE_ARGS, "-1_2_3_4"sv, -1234); + parse_expected_value(FILE_LINE_ARGS, "123_456_789"sv, 123456789); } TEST_CASE("parsing - integers (hex, bin, oct)") { - parsing_should_succeed(S(R"( + parsing_should_succeed( + FILE_LINE_ARGS, + S(R"( # hexadecimal with prefix `0x` hex1 = 0xDEADBEEF hex2 = 0xdeadbeef @@ -94,16 +99,18 @@ bin1 = 0b11010110 ); // "leading + is not allowed" - parsing_should_fail(S("hex1 = +0xDEADBEEF"sv)); - parsing_should_fail(S("hex2 = +0xdeadbeef"sv)); - parsing_should_fail(S("hex3 = +0xdead_beef"sv)); - parsing_should_fail(S("oct1 = +0o01234567"sv)); - parsing_should_fail(S("oct2 = +0o7550"sv)); - parsing_should_fail(S("int6 = +05_349_221"sv)); - parsing_should_fail(S("bin1 = +0b11010110"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("hex1 = +0xDEADBEEF"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("hex2 = +0xdeadbeef"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("hex3 = +0xdead_beef"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("oct1 = +0o01234567"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("oct2 = +0o7550"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("int6 = +05_349_221"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("bin1 = +0b11010110"sv)); // "leading zeros are allowed (after the prefix)" - parsing_should_succeed(S(R"( + parsing_should_succeed( + FILE_LINE_ARGS, + S(R"( hex1 = 0x000DEADBEEF hex2 = 0x00000deadbeef hex3 = 0x0dead_beef @@ -123,29 +130,29 @@ bin1 = 0b0000011010110 ); // "64 bit (signed long) range expected (−9,223,372,036,854,775,808 to 9,223,372,036,854,775,807)." - parsing_should_fail(S("val = −9223372036854775809"sv)); - parsing_should_fail(S("val = 9223372036854775808"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("val = −9223372036854775809"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("val = 9223372036854775808"sv)); // "***Non-negative*** integer values may also be expressed in hexadecimal, octal, or binary" - parsing_should_fail(S("val = -0o1"sv)); - parsing_should_fail(S("val = -0b1"sv)); - parsing_should_fail(S("val = -0x1"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("val = -0o1"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("val = -0b1"sv)); + parsing_should_fail(FILE_LINE_ARGS, S("val = -0x1"sv)); // value tests - parse_expected_value( "0xDEADBEEF"sv, 0xDEADBEEF ); - parse_expected_value( "0xdeadbeef"sv, 0xDEADBEEF ); - parse_expected_value( "0xDEADbeef"sv, 0xDEADBEEF ); - parse_expected_value( "0xDEAD_BEEF"sv, 0xDEADBEEF ); - parse_expected_value( "0xdead_beef"sv, 0xDEADBEEF ); - parse_expected_value( "0xdead_BEEF"sv, 0xDEADBEEF ); - parse_expected_value( "0xFF"sv, 0xFF ); - parse_expected_value( "0x00FF"sv, 0xFF ); - parse_expected_value( "0x0000FF"sv, 0xFF ); - parse_expected_value( "0o777"sv, 0777 ); - parse_expected_value( "0o7_7_7"sv, 0777 ); - parse_expected_value( "0o007"sv, 0007 ); - parse_expected_value( "0b10000"sv, 0b10000 ); - parse_expected_value( "0b010000"sv, 0b10000 ); - parse_expected_value( "0b01_00_00"sv, 0b10000 ); - parse_expected_value( "0b111111"sv, 0b111111 ); + parse_expected_value(FILE_LINE_ARGS, "0xDEADBEEF"sv, 0xDEADBEEF); + parse_expected_value(FILE_LINE_ARGS, "0xdeadbeef"sv, 0xDEADBEEF); + parse_expected_value(FILE_LINE_ARGS, "0xDEADbeef"sv, 0xDEADBEEF); + parse_expected_value(FILE_LINE_ARGS, "0xDEAD_BEEF"sv, 0xDEADBEEF); + parse_expected_value(FILE_LINE_ARGS, "0xdead_beef"sv, 0xDEADBEEF); + parse_expected_value(FILE_LINE_ARGS, "0xdead_BEEF"sv, 0xDEADBEEF); + parse_expected_value(FILE_LINE_ARGS, "0xFF"sv, 0xFF); + parse_expected_value(FILE_LINE_ARGS, "0x00FF"sv, 0xFF); + parse_expected_value(FILE_LINE_ARGS, "0x0000FF"sv, 0xFF); + parse_expected_value(FILE_LINE_ARGS, "0o777"sv, 0777); + parse_expected_value(FILE_LINE_ARGS, "0o7_7_7"sv, 0777); + parse_expected_value(FILE_LINE_ARGS, "0o007"sv, 0007); + parse_expected_value(FILE_LINE_ARGS, "0b10000"sv, 0b10000); + parse_expected_value(FILE_LINE_ARGS, "0b010000"sv, 0b10000); + parse_expected_value(FILE_LINE_ARGS, "0b01_00_00"sv, 0b10000); + parse_expected_value(FILE_LINE_ARGS, "0b111111"sv, 0b111111); } diff --git a/tests/parsing_key_value_pairs.cpp b/tests/parsing_key_value_pairs.cpp index bdf2733..c7a456b 100644 --- a/tests/parsing_key_value_pairs.cpp +++ b/tests/parsing_key_value_pairs.cpp @@ -2,7 +2,9 @@ TEST_CASE("parsing - key-value pairs") { - parsing_should_succeed(S(R"( + parsing_should_succeed( + FILE_LINE_ARGS, + S(R"( key = "value" bare_key = "value" bare-key = "value" @@ -20,9 +22,11 @@ bare-key = "value" } ); - parsing_should_fail(S(R"(key = # INVALID)"sv)); + parsing_should_fail(FILE_LINE_ARGS, S(R"(key = # INVALID)"sv)); - parsing_should_succeed(S(R"( + parsing_should_succeed( + FILE_LINE_ARGS, + S(R"( "127.0.0.1" = "value" "character encoding" = "value" "ʎǝʞ" = "value" @@ -41,20 +45,21 @@ bare-key = "value" } ); - parsing_should_fail(S(R"(= "no key name")"sv)); + parsing_should_fail(FILE_LINE_ARGS, S(R"(= "no key name")"sv)); - parsing_should_fail(S(R"( + parsing_should_fail(FILE_LINE_ARGS, S(R"( # DO NOT DO THIS name = "Tom" name = "Pradyun" -)"sv) - ); +)"sv)); } TEST_CASE("parsing - key-value pairs (dotted)") { - parsing_should_succeed(S(R"( + parsing_should_succeed( + FILE_LINE_ARGS, + S(R"( name = "Orange" physical.color = "orange" physical.shape = "round" @@ -73,7 +78,9 @@ site."google.com" = true ); - parsing_should_succeed(S(R"( + parsing_should_succeed( + FILE_LINE_ARGS, + S(R"( fruit.apple.smooth = true fruit.orange = 2 )"sv), @@ -84,14 +91,15 @@ fruit.orange = 2 } ); - parsing_should_fail(S(R"( + parsing_should_fail(FILE_LINE_ARGS, S(R"( # THIS IS INVALID fruit.apple = 1 fruit.apple.smooth = true -)"sv) - ); +)"sv)); - parsing_should_succeed(S(R"( + parsing_should_succeed( + FILE_LINE_ARGS, + S(R"( # VALID BUT DISCOURAGED apple.type = "fruit" @@ -114,7 +122,9 @@ orange.color = "orange" } ); - parsing_should_succeed(S(R"( + parsing_should_succeed( + FILE_LINE_ARGS, + S(R"( # RECOMMENDED apple.type = "fruit" @@ -138,10 +148,12 @@ orange.color = "orange" // toml/issues/644 ('+' in bare keys) & toml/issues/687 (unicode bare keys) #if TOML_LANG_UNRELEASED - parsing_should_succeed(S(R"( - key+1 = 0 - ʎǝʞ2 = 0 - )"sv), + parsing_should_succeed( + FILE_LINE_ARGS, + S(R"( + key+1 = 0 + ʎǝʞ2 = 0 + )"sv), [](table&& tbl) noexcept { CHECK(tbl.size() == 2); @@ -150,7 +162,7 @@ orange.color = "orange" } ); #else - parsing_should_fail(R"(key+1 = 0)"sv); - parsing_should_fail(R"(ʎǝʞ2 = 0)"sv); + parsing_should_fail(FILE_LINE_ARGS, R"(key+1 = 0)"sv); + parsing_should_fail(FILE_LINE_ARGS, R"(ʎǝʞ2 = 0)"sv); #endif } diff --git a/tests/parsing_spec_example.cpp b/tests/parsing_spec_example.cpp index 158081f..83b1e30 100644 --- a/tests/parsing_spec_example.cpp +++ b/tests/parsing_spec_example.cpp @@ -40,49 +40,53 @@ hosts = [ "omega" ])"sv); - parsing_should_succeed(toml_text, [](table&& tbl) noexcept - { - CHECK(tbl.size() == 5); + parsing_should_succeed( + FILE_LINE_ARGS, + toml_text, + [](table&& tbl) noexcept + { + CHECK(tbl.size() == 5); - CHECK(tbl[S("title")] == S("TOML Example"sv)); + CHECK(tbl[S("title")] == S("TOML Example"sv)); - CHECK(tbl[S("owner")]); - CHECK(tbl[S("owner")].as()); - CHECK(tbl[S("owner")][S("name")] == S("Tom Preston-Werner"sv)); - const auto dob = date_time{ { 1979, 5, 27 }, { 7, 32 }, { -8, 0 } }; - CHECK(tbl[S("owner")][S("dob")] == dob); + CHECK(tbl[S("owner")]); + CHECK(tbl[S("owner")].as
()); + CHECK(tbl[S("owner")][S("name")] == S("Tom Preston-Werner"sv)); + const auto dob = date_time{ { 1979, 5, 27 }, { 7, 32 }, { -8, 0 } }; + CHECK(tbl[S("owner")][S("dob")] == dob); - CHECK(tbl[S("database")].as
()); - CHECK(tbl[S("database")][S("server")] == S("192.168.1.1"sv)); - const auto ports = { 8001, 8001, 8002 }; - CHECK(tbl[S("database")][S("ports")] == ports); - CHECK(tbl[S("database")][S("connection_max")] == 5000); - CHECK(tbl[S("database")][S("enabled")] == true); + CHECK(tbl[S("database")].as
()); + CHECK(tbl[S("database")][S("server")] == S("192.168.1.1"sv)); + const auto ports = { 8001, 8001, 8002 }; + CHECK(tbl[S("database")][S("ports")] == ports); + CHECK(tbl[S("database")][S("connection_max")] == 5000); + CHECK(tbl[S("database")][S("enabled")] == true); - CHECK(tbl[S("servers")].as
()); - CHECK(tbl[S("servers")][S("alpha")].as
()); - CHECK(tbl[S("servers")][S("alpha")][S("ip")] == S("10.0.0.1"sv)); - CHECK(tbl[S("servers")][S("alpha")][S("dc")] == S("eqdc10"sv)); - CHECK(tbl[S("servers")][S("beta")].as
()); - CHECK(tbl[S("servers")][S("beta")][S("ip")] == S("10.0.0.2"sv)); - CHECK(tbl[S("servers")][S("beta")][S("dc")] == S("eqdc10"sv)); + CHECK(tbl[S("servers")].as
()); + CHECK(tbl[S("servers")][S("alpha")].as
()); + CHECK(tbl[S("servers")][S("alpha")][S("ip")] == S("10.0.0.1"sv)); + CHECK(tbl[S("servers")][S("alpha")][S("dc")] == S("eqdc10"sv)); + CHECK(tbl[S("servers")][S("beta")].as
()); + CHECK(tbl[S("servers")][S("beta")][S("ip")] == S("10.0.0.2"sv)); + CHECK(tbl[S("servers")][S("beta")][S("dc")] == S("eqdc10"sv)); - CHECK(tbl[S("clients")].as
()); - REQUIRE(tbl[S("clients")][S("data")].as()); - CHECK(tbl[S("clients")][S("data")].as()->size() == 2); - REQUIRE(tbl[S("clients")][S("data")][0].as()); - CHECK(tbl[S("clients")][S("data")][0].as()->size() == 2); - CHECK(tbl[S("clients")][S("data")][0][0] == S("gamma"sv)); - CHECK(tbl[S("clients")][S("data")][0][1] == S("delta"sv)); - REQUIRE(tbl[S("clients")][S("data")][1].as()); - CHECK(tbl[S("clients")][S("data")][1].as()->size() == 2); - CHECK(tbl[S("clients")][S("data")][1][0] == 1); - CHECK(tbl[S("clients")][S("data")][1][1] == 2); - REQUIRE(tbl[S("clients")][S("hosts")].as()); - CHECK(tbl[S("clients")][S("hosts")].as()->size() == 2); - CHECK(tbl[S("clients")][S("hosts")][0] == S("alpha"sv)); - CHECK(tbl[S("clients")][S("hosts")][1] == S("omega"sv)); - }); + CHECK(tbl[S("clients")].as
()); + REQUIRE(tbl[S("clients")][S("data")].as()); + CHECK(tbl[S("clients")][S("data")].as()->size() == 2); + REQUIRE(tbl[S("clients")][S("data")][0].as()); + CHECK(tbl[S("clients")][S("data")][0].as()->size() == 2); + CHECK(tbl[S("clients")][S("data")][0][0] == S("gamma"sv)); + CHECK(tbl[S("clients")][S("data")][0][1] == S("delta"sv)); + REQUIRE(tbl[S("clients")][S("data")][1].as()); + CHECK(tbl[S("clients")][S("data")][1].as()->size() == 2); + CHECK(tbl[S("clients")][S("data")][1][0] == 1); + CHECK(tbl[S("clients")][S("data")][1][1] == 2); + REQUIRE(tbl[S("clients")][S("hosts")].as()); + CHECK(tbl[S("clients")][S("hosts")].as()->size() == 2); + CHECK(tbl[S("clients")][S("hosts")][0] == S("alpha"sv)); + CHECK(tbl[S("clients")][S("hosts")][1] == S("omega"sv)); + } + ); } TOML_POP_WARNINGS diff --git a/tests/parsing_strings.cpp b/tests/parsing_strings.cpp index 39396b3..4e4eab1 100644 --- a/tests/parsing_strings.cpp +++ b/tests/parsing_strings.cpp @@ -2,7 +2,9 @@ TEST_CASE("parsing - strings") { - parsing_should_succeed(S(R"( + parsing_should_succeed( + FILE_LINE_ARGS, + S(R"( str = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF." str1 = """ @@ -16,7 +18,9 @@ Violets are blue""" } ); - parsing_should_succeed(S(R"( + parsing_should_succeed( + FILE_LINE_ARGS, + S(R"( # The following strings are byte-for-byte equivalent: str1 = "The quick brown fox jumps over the lazy dog." @@ -54,9 +58,11 @@ str7 = """"This," she said, "is just a pointless statement."""" } ); - parsing_should_fail(S(R"(str5 = """Here are three quotation marks: """.""")"sv)); + parsing_should_fail(FILE_LINE_ARGS, S(R"(str5 = """Here are three quotation marks: """.""")"sv)); - parsing_should_succeed(S(R"( + parsing_should_succeed( + FILE_LINE_ARGS, + S(R"( # What you see is what you get. winpath = 'C:\Users\nodejs\templates' winpath2 = '\\ServerX\admin$\system32\' @@ -85,7 +91,9 @@ trimmed in raw strings. } ); - parsing_should_succeed(S(R"( + parsing_should_succeed( + FILE_LINE_ARGS, + S(R"( quot15 = '''Here are fifteen quotation marks: """""""""""""""''' # apos15 = '''Here are fifteen apostrophes: '''''''''''''''''' # INVALID @@ -102,57 +110,66 @@ str = ''''That's still pointless', she said.''' } ); - parsing_should_fail(S(R"(apos15 = '''Here are fifteen apostrophes: '''''''''''''''''' # INVALID)"sv)); + parsing_should_fail(FILE_LINE_ARGS, S(R"(apos15 = '''Here are fifteen apostrophes: '''''''''''''''''' # INVALID)"sv)); //value tests parse_expected_value( + FILE_LINE_ARGS, R"("The quick brown fox jumps over the lazy dog")"sv, S("The quick brown fox jumps over the lazy dog"sv)); parse_expected_value( + FILE_LINE_ARGS, R"('The quick brown fox jumps over the lazy dog')"sv, S("The quick brown fox jumps over the lazy dog"sv)); parse_expected_value( + FILE_LINE_ARGS, R"("""The quick brown fox jumps over the lazy dog""")"sv, S("The quick brown fox jumps over the lazy dog"sv)); parse_expected_value( + FILE_LINE_ARGS, R"('''The quick brown fox jumps over the lazy dog''')"sv, S("The quick brown fox jumps over the lazy dog"sv)); parse_expected_value( + FILE_LINE_ARGS, R"("Ýôú'ℓℓ λáƭè ₥è áƒƭèř ƭλïƨ - #")"sv, S(R"(Ýôú'ℓℓ λáƭè ₥è áƒƭèř ƭλïƨ - #)"sv)); parse_expected_value( + FILE_LINE_ARGS, R"(" Âñδ ωλèñ \"'ƨ ářè ïñ ƭλè ƨƭřïñϱ, áℓôñϱ ωïƭλ # \"")"sv, S(R"( Âñδ ωλèñ "'ƨ ářè ïñ ƭλè ƨƭřïñϱ, áℓôñϱ ωïƭλ # ")"sv)); parse_expected_value( + FILE_LINE_ARGS, R"("Ýôú δôñ'ƭ ƭλïñƙ ƨô₥è úƨèř ωôñ'ƭ δô ƭλáƭ?")"sv, S(R"(Ýôú δôñ'ƭ ƭλïñƙ ƨô₥è úƨèř ωôñ'ƭ δô ƭλáƭ?)"sv)); parse_expected_value( + FILE_LINE_ARGS, R"("\"\u03B1\u03B2\u03B3\"")"sv, S("\"\u03B1\u03B2\u03B3\""sv)); // toml/pull/709 (\xHH unicode scalars) #if TOML_LANG_UNRELEASED parse_expected_value( + FILE_LINE_ARGS, R"("\x00\x10\x20\x30\x40\x50\x60\x70\x80\x90\x11\xFF\xEE")"sv, S("\u0000\u0010\u0020\u0030\u0040\u0050\u0060\u0070\u0080\u0090\u0011\u00FF\u00EE"sv)); #else - parsing_should_fail(R"(str = "\x00\x10\x20\x30\x40\x50\x60\x70\x80\x90\x11\xFF\xEE")"sv); + parsing_should_fail(FILE_LINE_ARGS, R"(str = "\x00\x10\x20\x30\x40\x50\x60\x70\x80\x90\x11\xFF\xEE")"sv); #endif //check 8-digit \U scalars with insufficient digits - parsing_should_fail(R"(str = "\U1234567")"sv); - parsing_should_fail(R"(str = "\U123456")"sv); - parsing_should_fail(R"(str = "\U12345")"sv); - parsing_should_fail(R"(str = "\U1234")"sv); - parsing_should_fail(R"(str = "\U123")"sv); - parsing_should_fail(R"(str = "\U12")"sv); - parsing_should_fail(R"(str = "\U1")"sv); + parsing_should_fail(FILE_LINE_ARGS,R"(str = "\U1234567")"sv); + parsing_should_fail(FILE_LINE_ARGS,R"(str = "\U123456")"sv); + parsing_should_fail(FILE_LINE_ARGS,R"(str = "\U12345")"sv); + parsing_should_fail(FILE_LINE_ARGS,R"(str = "\U1234")"sv); + parsing_should_fail(FILE_LINE_ARGS,R"(str = "\U123")"sv); + parsing_should_fail(FILE_LINE_ARGS,R"(str = "\U12")"sv); + parsing_should_fail(FILE_LINE_ARGS,R"(str = "\U1")"sv); //check 4-digit \u scalars with insufficient digits - parsing_should_fail(R"(str = "\u123")"sv); - parsing_should_fail(R"(str = "\u12")"sv); - parsing_should_fail(R"(str = "\u1")"sv); + parsing_should_fail(FILE_LINE_ARGS,R"(str = "\u123")"sv); + parsing_should_fail(FILE_LINE_ARGS,R"(str = "\u12")"sv); + parsing_should_fail(FILE_LINE_ARGS,R"(str = "\u1")"sv); //check 2-digit \x scalars with insufficient digits - parsing_should_fail(R"(str = "\x1")"sv); + parsing_should_fail(FILE_LINE_ARGS, R"(str = "\x1")"sv); } diff --git a/tests/parsing_tables.cpp b/tests/parsing_tables.cpp index 1f67e3b..c8fc740 100644 --- a/tests/parsing_tables.cpp +++ b/tests/parsing_tables.cpp @@ -2,7 +2,9 @@ TEST_CASE("parsing - tables") { - parsing_should_succeed(S(R"( + parsing_should_succeed( + FILE_LINE_ARGS, + S(R"( [table] [table-1] @@ -82,7 +84,7 @@ smooth = true ); - parsing_should_fail(S(R"( + parsing_should_fail(FILE_LINE_ARGS, S(R"( # DO NOT DO THIS [fruit] @@ -92,7 +94,7 @@ apple = "red" orange = "orange" )"sv)); - parsing_should_fail(S(R"( + parsing_should_fail(FILE_LINE_ARGS, S(R"( # DO NOT DO THIS EITHER [fruit] @@ -102,7 +104,7 @@ apple = "red" texture = "smooth" )"sv)); - parsing_should_fail(S(R"( + parsing_should_fail(FILE_LINE_ARGS, S(R"( [fruit] apple.color = "red" apple.taste.sweet = true @@ -110,7 +112,7 @@ apple.taste.sweet = true [fruit.apple] )"sv)); - parsing_should_fail(S(R"( + parsing_should_fail(FILE_LINE_ARGS, S(R"( [fruit] apple.color = "red" apple.taste.sweet = true @@ -118,7 +120,9 @@ apple.taste.sweet = true [fruit.apple.taste] )"sv)); - parsing_should_succeed(S(R"( + parsing_should_succeed( + FILE_LINE_ARGS, + S(R"( # VALID BUT DISCOURAGED [fruit.apple] [animal] @@ -138,7 +142,9 @@ apple.taste.sweet = true ); - parsing_should_succeed(S(R"( + parsing_should_succeed( + FILE_LINE_ARGS, + S(R"( # RECOMMENDED [fruit.apple] [fruit.orange] @@ -160,7 +166,9 @@ apple.taste.sweet = true TEST_CASE("parsing - inline tables") { - parsing_should_succeed(S(R"( + parsing_should_succeed( + FILE_LINE_ARGS, + S(R"( name = { first = "Tom", last = "Preston-Werner" } point = { x = 1, y = 2 } animal = { type.name = "pug" } @@ -194,20 +202,22 @@ type = { name = "Nail" } } ); - parsing_should_fail(S(R"( + parsing_should_fail(FILE_LINE_ARGS, S(R"( [product] type = { name = "Nail" } type.edible = false # INVALID )"sv)); - parsing_should_fail(S(R"( + parsing_should_fail(FILE_LINE_ARGS, S(R"( [product] type.name = "Nail" type = { edible = false } # INVALID )"sv)); // "newlines are allowed between the curly braces [if] they are valid within a value." - parsing_should_succeed(S(R"( + parsing_should_succeed( + FILE_LINE_ARGS, + S(R"( test = { val1 = "foo", val2 = [ 1, 2, 3 @@ -230,7 +240,9 @@ test = { val1 = "foo", val2 = [ // toml/issues/516 (newlines/trailing commas in inline tables) #if TOML_LANG_UNRELEASED { - parsing_should_succeed(S(R"( + parsing_should_succeed( + FILE_LINE_ARGS, + S(R"( name = { first = "Tom", last = "Preston-Werner", @@ -249,10 +261,10 @@ name = { #else { // "A terminating comma (also called trailing comma) is not permitted after the last key/value pair in an inline table." - parsing_should_fail(S(R"(name = { first = "Tom", last = "Preston-Werner", })"sv)); + parsing_should_fail(FILE_LINE_ARGS, S(R"(name = { first = "Tom", last = "Preston-Werner", })"sv)); // "No newlines are allowed between the curly braces unless they are valid within a value." - parsing_should_fail(S(R"( + parsing_should_fail(FILE_LINE_ARGS, S(R"( name = { first = "Tom", last = "Preston-Werner" @@ -266,7 +278,9 @@ name = { TEST_CASE("parsing - arrays-of-tables") { - parsing_should_succeed(S(R"( + parsing_should_succeed( + FILE_LINE_ARGS, + S(R"( points = [ { x = 1, y = 2, z = 3 }, { x = 7, y = 8, z = 9 }, { x = 2, y = 4, z = 8 } ] @@ -372,7 +386,7 @@ color = "gray" } ); - parsing_should_fail(S(R"( + parsing_should_fail(FILE_LINE_ARGS, S(R"( # INVALID TOML DOC [fruit.physical] # subtable, but to which parent element should it belong? color = "red" @@ -383,14 +397,14 @@ color = "gray" name = "apple" )"sv)); - parsing_should_fail(S(R"( + parsing_should_fail(FILE_LINE_ARGS, S(R"( # INVALID TOML DOC fruit = [] [[fruit]] # Not allowed )"sv)); - parsing_should_fail(S(R"( + parsing_should_fail(FILE_LINE_ARGS, S(R"( # INVALID TOML DOC [[fruit]] name = "apple" @@ -403,7 +417,7 @@ fruit = [] name = "granny smith" )"sv)); - parsing_should_fail(S(R"( + parsing_should_fail(FILE_LINE_ARGS, S(R"( # INVALID TOML DOC [[fruit]] name = "apple" diff --git a/tests/tests.cpp b/tests/tests.cpp index e91ce2f..4671316 100644 --- a/tests/tests.cpp +++ b/tests/tests.cpp @@ -1,11 +1,11 @@ #include "tests.h" -template void parse_expected_value(std::string_view, const int&) noexcept; -template void parse_expected_value(std::string_view, const unsigned int&) noexcept; -template void parse_expected_value(std::string_view, const bool&) noexcept; -template void parse_expected_value(std::string_view, const float&) noexcept; -template void parse_expected_value(std::string_view, const double&) noexcept; -template void parse_expected_value(std::string_view, const toml::string_view&) noexcept; +template void parse_expected_value(std::string_view, uint32_t, std::string_view, const int&) noexcept; +template void parse_expected_value(std::string_view, uint32_t, std::string_view, const unsigned int&) noexcept; +template void parse_expected_value(std::string_view, uint32_t, std::string_view, const bool&) noexcept; +template void parse_expected_value(std::string_view, uint32_t, std::string_view, const float&) noexcept; +template void parse_expected_value(std::string_view, uint32_t, std::string_view, const double&) noexcept; +template void parse_expected_value(std::string_view, uint32_t, std::string_view, const toml::string_view&) noexcept; namespace std { diff --git a/tests/tests.h b/tests/tests.h index 4473e7e..0e94dd0 100644 --- a/tests/tests.h +++ b/tests/tests.h @@ -25,15 +25,25 @@ #endif #endif +#define FILE_LINE_ARGS std::string_view{ __FILE__ }, __LINE__ + using namespace toml; using namespace Catch::literals; #define S(str) TOML_STRING_PREFIX(str) template -inline void parsing_should_succeed(std::basic_string_view toml_str, Func&& func = {}, std::string_view source_path = {}) noexcept +inline void parsing_should_succeed( + std::string_view test_file, + uint32_t test_line, + std::basic_string_view toml_str, + Func&& func = {}, + std::string_view source_path = {}) noexcept { - INFO("String being parsed: '"sv << std::string_view( reinterpret_cast(toml_str.data()), toml_str.length() ) << "'"sv) + INFO( + "["sv << test_file << ", line "sv << test_line << "] "sv + << "parsing_should_succeed('"sv << std::string_view(reinterpret_cast(toml_str.data()), toml_str.length()) << "')"sv + ) constexpr auto validate_table = [](table&& tabl, std::string_view path) noexcept -> table&& { @@ -134,9 +144,15 @@ inline void parsing_should_succeed(std::basic_string_view toml_str, Func&& } template -inline void parsing_should_fail(std::basic_string_view toml_str) noexcept +inline void parsing_should_fail( + std::string_view test_file, + uint32_t test_line, + std::basic_string_view toml_str) noexcept { - INFO("String being parsed: '"sv << std::string_view(reinterpret_cast(toml_str.data()), toml_str.length()) << "'"sv) + INFO( + "["sv << test_file << ", line "sv << test_line << "] "sv + << "parsing_should_fail('"sv << std::string_view(reinterpret_cast(toml_str.data()), toml_str.length()) << "')"sv + ) #if TOML_EXCEPTIONS @@ -200,8 +216,14 @@ inline void parsing_should_fail(std::basic_string_view toml_str) noexcept } template -inline void parse_expected_value(std::string_view value_str, const T& expected) noexcept +inline void parse_expected_value( + std::string_view test_file, + uint32_t test_line, + std::string_view value_str, + const T& expected) noexcept { + INFO("["sv << test_file << ", line "sv << test_line << "] "sv << "parse_expected_value('"sv << value_str << "')"sv) + std::string val; static constexpr auto key = "val = "sv; val.reserve(key.length() + value_str.length()); @@ -250,84 +272,100 @@ inline void parse_expected_value(std::string_view value_str, const T& expected) using value_type = impl::promoted>; value val_parsed; - - parsing_should_succeed(std::string_view{ val }, [&](table&& tbl) noexcept { - CHECK(tbl.size() == 1); - auto nv = tbl[S("val"sv)]; - REQUIRE(nv); - REQUIRE(nv.as()); - REQUIRE(nv.get()->type() == impl::node_type_of); + INFO("["sv << test_file << ", line "sv << test_line << "] "sv << "parse_expected_value: Checking inital parse"sv) - // check the raw value - CHECK(nv.get()->value() == expected); - CHECK(nv.get()->value_or(T{}) == expected); - CHECK(nv.as()->get() == expected); - CHECK(nv.value() == expected); - CHECK(nv.value_or(T{}) == expected); - CHECK(nv.ref() == expected); - CHECK(nv.get()->ref() == expected); + parsing_should_succeed( + test_file, + test_line, + std::string_view{ val }, + [&](table&& tbl) noexcept + { + REQUIRE(tbl.size() == 1); + auto nv = tbl[S("val"sv)]; + REQUIRE(nv); + REQUIRE(nv.as()); + REQUIRE(nv.get()->type() == impl::node_type_of); - // check the table relops - CHECK(tbl == table{ { { S("val"sv), expected } } }); - CHECK(!(tbl != table{ { { S("val"sv), expected } } })); + // check the raw value + REQUIRE(nv.get()->value() == expected); + REQUIRE(nv.get()->value_or(T{}) == expected); + REQUIRE(nv.as()->get() == expected); + REQUIRE(nv.value() == expected); + REQUIRE(nv.value_or(T{}) == expected); + REQUIRE(nv.ref() == expected); + REQUIRE(nv.get()->ref() == expected); - // check the value relops - CHECK(*nv.as() == expected); - CHECK(expected == *nv.as()); - CHECK(!(*nv.as() != expected)); - CHECK(!(expected != *nv.as())); + // check the table relops + REQUIRE(tbl == table{ { { S("val"sv), expected } } }); + REQUIRE(!(tbl != table{ { { S("val"sv), expected } } })); - // check the node_view relops - CHECK(nv == expected); - CHECK(expected == nv); - CHECK(!(nv != expected)); - CHECK(!(expected != nv)); + // check the value relops + REQUIRE(*nv.as() == expected); + REQUIRE(expected == *nv.as()); + REQUIRE(!(*nv.as() != expected)); + REQUIRE(!(expected != *nv.as())); - // make sure source info is correct - CHECK(nv.get()->source().begin == begin); - CHECK(nv.get()->source().end == end); + // check the node_view relops + REQUIRE(nv == expected); + REQUIRE(expected == nv); + REQUIRE(!(nv != expected)); + REQUIRE(!(expected != nv)); - // steal the val for round-trip tests - val_parsed = std::move(*nv.as()); - }); + // make sure source info is correct + REQUIRE(nv.get()->source().begin == begin); + REQUIRE(nv.get()->source().end == end); + + // steal the val for round-trip tests + val_parsed = std::move(*nv.as()); + } + ); + } // check round-tripping - value val_reparsed; { - std::string str; + INFO("["sv << test_file << ", line "sv << test_line << "] "sv << "parse_expected_value: Checking round-trip"sv) + value val_reparsed; { - auto tbl = table{ { { S("val"sv), *val_parsed } } }; - std::ostringstream ss; - ss << tbl; - str = std::move(ss).str(); + std::string str; + { + auto tbl = table{ { { S("val"sv), *val_parsed } } }; + std::ostringstream ss; + ss << tbl; + str = std::move(ss).str(); + } + + parsing_should_succeed( + test_file, + test_line, + std::string_view{ str }, + [&](table&& tbl) noexcept + { + REQUIRE(tbl.size() == 1); + auto nv = tbl[S("val"sv)]; + REQUIRE(nv); + REQUIRE(nv.as()); + REQUIRE(nv.get()->type() == impl::node_type_of); + + CHECK(nv.as()->get() == expected); + CHECK(nv.ref() == expected); + + val_reparsed = std::move(*nv.as()); + } + ); } - - parsing_should_succeed(std::string_view{ str }, [&](table&& tbl) noexcept - { - CHECK(tbl.size() == 1); - auto nv = tbl[S("val"sv)]; - REQUIRE(nv); - REQUIRE(nv.as()); - REQUIRE(nv.get()->type() == impl::node_type_of); - - CHECK(nv.as()->get() == expected); - CHECK(nv.ref() == expected); - - val_reparsed = std::move(*nv.as()); - }); + CHECK(val_reparsed == val_parsed); + CHECK(val_reparsed == expected); } - CHECK(val_reparsed == val_parsed); - CHECK(val_reparsed == expected); } // manually instantiate some templates to reduce test compilation time (chosen using ClangBuildAnalyzer) -extern template void parse_expected_value(std::string_view, const int&) noexcept; -extern template void parse_expected_value(std::string_view, const unsigned int&) noexcept; -extern template void parse_expected_value(std::string_view, const bool&) noexcept; -extern template void parse_expected_value(std::string_view, const float&) noexcept; -extern template void parse_expected_value(std::string_view, const double&) noexcept; -extern template void parse_expected_value(std::string_view, const toml::string_view&) noexcept; +extern template void parse_expected_value(std::string_view, uint32_t, std::string_view, const int&) noexcept; +extern template void parse_expected_value(std::string_view, uint32_t, std::string_view, const unsigned int&) noexcept; +extern template void parse_expected_value(std::string_view, uint32_t, std::string_view, const bool&) noexcept; +extern template void parse_expected_value(std::string_view, uint32_t, std::string_view, const float&) noexcept; +extern template void parse_expected_value(std::string_view, uint32_t, std::string_view, const double&) noexcept; +extern template void parse_expected_value(std::string_view, uint32_t, std::string_view, const toml::string_view&) noexcept; namespace std { extern template class unique_ptr; diff --git a/toml.hpp b/toml.hpp index 5a4e592..c6f2e11 100644 --- a/toml.hpp +++ b/toml.hpp @@ -1,6 +1,6 @@ //---------------------------------------------------------------------------------------------------------------------- // -// toml++ v1.1.0 +// toml++ v1.2.0 // https://github.com/marzer/tomlplusplus // SPDX-License-Identifier: MIT // @@ -234,9 +234,18 @@ #ifndef TOML_DISABLE_INIT_WARNINGS #define TOML_DISABLE_INIT_WARNINGS #endif +#ifndef TOML_INTEGER_CHARCONV + #define TOML_INTEGER_CHARCONV 1 +#endif #ifndef TOML_FLOATING_POINT_CHARCONV #define TOML_FLOATING_POINT_CHARCONV 1 #endif +#if (TOML_INTEGER_CHARCONV || TOML_FLOATING_POINT_CHARCONV) && !__has_include() + #undef TOML_INTEGER_CHARCONV + #undef TOML_FLOATING_POINT_CHARCONV + #define TOML_INTEGER_CHARCONV 0 + #define TOML_FLOATING_POINT_CHARCONV 0 +#endif #ifndef TOML_PUSH_WARNINGS #define TOML_PUSH_WARNINGS #endif @@ -314,7 +323,7 @@ #endif #define TOML_LIB_MAJOR 1 -#define TOML_LIB_MINOR 1 +#define TOML_LIB_MINOR 2 #define TOML_LIB_PATCH 0 #define TOML_LANG_MAJOR 1 @@ -343,7 +352,6 @@ TOML_PUSH_WARNINGS TOML_DISABLE_ALL_WARNINGS - #if __has_include() #include #endif @@ -355,22 +363,20 @@ TOML_DISABLE_ALL_WARNINGS #include #include #include -#include -#ifndef TOML_ASSERT - #if !defined(NDEBUG) || defined(_DEBUG) || defined(DEBUG) - #include - #define TOML_ASSERT(expr) assert(expr) - #else - #define TOML_ASSERT(expr) (void)0 - #endif -#endif #ifndef TOML_OPTIONAL_TYPE #include #endif #if TOML_EXCEPTIONS #include #endif - +#ifndef TOML_ASSERT + #ifdef NDEBUG + #define TOML_ASSERT(expr) (void)0 + #else + #include + #define TOML_ASSERT(expr) assert(expr) + #endif +#endif TOML_POP_WARNINGS #if TOML_CHAR_8_STRINGS @@ -843,9 +849,9 @@ namespace toml::impl "date-time"sv }; - #define TOML_P2S_DECL(linkage, type) \ + #define TOML_P2S_DECL(Linkage, Type) \ template \ - linkage void print_to_stream(type, std::basic_ostream&) + Linkage void print_to_stream(Type, std::basic_ostream&) TOML_P2S_DECL(TOML_ALWAYS_INLINE, int8_t); TOML_P2S_DECL(TOML_ALWAYS_INLINE, int16_t); @@ -1233,6 +1239,19 @@ namespace toml //-------------------------------------------------------------- ↓ toml_print_to_stream.h ---------------------------- #pragma region +TOML_PUSH_WARNINGS +TOML_DISABLE_ALL_WARNINGS +#if TOML_INTEGER_CHARCONV || TOML_FLOATING_POINT_CHARCONV + #include +#endif +#if !TOML_INTEGER_CHARCONV || !TOML_FLOATING_POINT_CHARCONV + #include +#endif +#if !TOML_INTEGER_CHARCONV + #include +#endif +TOML_POP_WARNINGS + namespace toml::impl { // Q: "why does print_to_stream() exist? why not just use ostream::write(), ostream::put() etc?" @@ -1321,15 +1340,29 @@ namespace toml::impl "The stream's underlying character type must be 1 byte in size." ); - char buf[charconv_buffer_length]; - const auto res = std::to_chars(buf, buf + sizeof(buf), val); - print_to_stream(buf, static_cast(res.ptr - buf), stream); + #if TOML_INTEGER_CHARCONV + + char buf[charconv_buffer_length]; + const auto res = std::to_chars(buf, buf + sizeof(buf), val); + const auto len = static_cast(res.ptr - buf); + print_to_stream(buf, len, stream); + + #else + + std::ostringstream ss; + ss.imbue(std::locale::classic()); + using cast_type = std::conditional_t, int64_t, uint64_t>; + ss << static_cast(val); + const auto str = std::move(ss).str(); + print_to_stream(str, stream); + + #endif } - #define TOML_P2S_OVERLOAD(type) \ + #define TOML_P2S_OVERLOAD(Type) \ template \ TOML_ALWAYS_INLINE \ - void print_to_stream(type val, std::basic_ostream& stream) \ + void print_to_stream(Type val, std::basic_ostream& stream) \ { \ static_assert(sizeof(Char) == 1); \ print_integer_to_stream(val, stream); \ @@ -1376,20 +1409,13 @@ namespace toml::impl } #else { - char buf[charconv_buffer_length + 1_sz]; - int len = -1; + std::ostringstream ss; + ss.imbue(std::locale::classic()); + ss.precision(std::numeric_limits::digits10 + 1); if (hexfloat) - len = snprintf(buf, charconv_buffer_length + 1_sz, "%a", static_cast(val)); - else - len = snprintf( - buf, charconv_buffer_length + 1_sz, "%.*g", - std::numeric_limits::digits10 + 1, static_cast(val) - ); - TOML_ASSERT(len > 0); - len = static_cast(charconv_buffer_length) < len - ? static_cast(charconv_buffer_length) - : len; - const auto str = std::string_view{ buf, static_cast(len) }; + ss << std::hexfloat; + ss << val; + const auto str = std::move(ss).str(); print_to_stream(str, stream); if (!hexfloat && needs_decimal_point(str)) print_to_stream(".0"sv, stream); @@ -1402,10 +1428,10 @@ namespace toml::impl extern template TOML_API void print_floating_point_to_stream(double, std::ostream&, bool); #endif - #define TOML_P2S_OVERLOAD(type) \ + #define TOML_P2S_OVERLOAD(Type) \ template \ TOML_ALWAYS_INLINE \ - void print_to_stream(type val, std::basic_ostream& stream) \ + void print_to_stream(Type val, std::basic_ostream& stream) \ { \ static_assert(sizeof(Char) == 1); \ print_floating_point_to_stream(val, stream); \ @@ -1428,12 +1454,25 @@ namespace toml::impl inline void print_to_stream(T val, std::basic_ostream& stream, size_t zero_pad_to_digits) { static_assert(sizeof(Char) == 1); - char buf[charconv_buffer_length]; - const auto res = std::to_chars(buf, buf + sizeof(buf), val); - const auto len = static_cast(res.ptr - buf); - for (size_t i = len; i < zero_pad_to_digits; i++) - print_to_stream('0', stream); - print_to_stream(buf, static_cast(res.ptr - buf), stream); + #if TOML_INTEGER_CHARCONV + + char buf[charconv_buffer_length]; + const auto res = std::to_chars(buf, buf + sizeof(buf), val); + const auto len = static_cast(res.ptr - buf); + for (size_t i = len; i < zero_pad_to_digits; i++) + print_to_stream('0', stream); + print_to_stream(buf, static_cast(res.ptr - buf), stream); + + #else + + std::ostringstream ss; + ss.imbue(std::locale::classic()); + using cast_type = std::conditional_t, int64_t, uint64_t>; + ss << std::setfill('0') << std::setw(zero_pad_to_digits) << static_cast(val); + const auto str = std::move(ss).str(); + print_to_stream(str, stream); + + #endif } template @@ -2199,6 +2238,9 @@ namespace toml extern template TOML_API std::ostream& operator << (std::ostream&, const value&); #endif + TOML_PUSH_WARNINGS + TOML_DISABLE_INIT_WARNINGS + template inline optional node::value() const noexcept { @@ -2280,6 +2322,8 @@ namespace toml TOML_UNREACHABLE; } + TOML_POP_WARNINGS + template inline auto node::value_or(T&& default_value) const noexcept { @@ -3165,6 +3209,9 @@ namespace toml [[nodiscard]] auto as_time() const noexcept { return as