From 1d8ca1f35eb3a9c9142462b28282a848e5d29a91 Mon Sep 17 00:00:00 2001 From: Frank Osterfeld Date: Thu, 11 Apr 2024 23:16:44 +0200 Subject: [PATCH 01/65] Avoid static reference to temporary These caused issues when used in a wasm project. --- src/emitterutils.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/emitterutils.cpp b/src/emitterutils.cpp index 6cdf6de7e..fc41011a5 100644 --- a/src/emitterutils.cpp +++ b/src/emitterutils.cpp @@ -173,11 +173,11 @@ bool IsValidPlainScalar(const std::string& str, FlowType::value flowType, } // then check until something is disallowed - static const RegEx& disallowed_flow = + static const RegEx disallowed_flow = Exp::EndScalarInFlow() | (Exp::BlankOrBreak() + Exp::Comment()) | Exp::NotPrintable() | Exp::Utf8_ByteOrderMark() | Exp::Break() | Exp::Tab() | Exp::Ampersand(); - static const RegEx& disallowed_block = + static const RegEx disallowed_block = Exp::EndScalar() | (Exp::BlankOrBreak() + Exp::Comment()) | Exp::NotPrintable() | Exp::Utf8_ByteOrderMark() | Exp::Break() | Exp::Tab() | Exp::Ampersand(); From b95aa146ec3226f31b7b75bef1b5f750af25fb8a Mon Sep 17 00:00:00 2001 From: Simon Gene Gottlieb Date: Wed, 17 Jul 2024 13:06:39 +0200 Subject: [PATCH 02/65] fix: use C locale by default --- include/yaml-cpp/emitter.h | 2 ++ include/yaml-cpp/node/convert.h | 2 ++ include/yaml-cpp/traits.h | 1 + src/node_data.cpp | 1 + src/parser.cpp | 1 + test/node/node_test.cpp | 12 ++++++++++++ 6 files changed, 19 insertions(+) diff --git a/include/yaml-cpp/emitter.h b/include/yaml-cpp/emitter.h index 210b1ec97..2897fc0a2 100644 --- a/include/yaml-cpp/emitter.h +++ b/include/yaml-cpp/emitter.h @@ -141,6 +141,7 @@ inline Emitter& Emitter::WriteIntegralType(T value) { PrepareNode(EmitterNodeType::Scalar); std::stringstream stream; + stream.imbue(std::locale("C")); PrepareIntegralStream(stream); stream << value; m_stream << stream.str(); @@ -158,6 +159,7 @@ inline Emitter& Emitter::WriteStreamable(T value) { PrepareNode(EmitterNodeType::Scalar); std::stringstream stream; + stream.imbue(std::locale("C")); SetStreamablePrecision(stream); bool special = false; diff --git a/include/yaml-cpp/node/convert.h b/include/yaml-cpp/node/convert.h index d49702f82..c8b3c336f 100644 --- a/include/yaml-cpp/node/convert.h +++ b/include/yaml-cpp/node/convert.h @@ -171,6 +171,7 @@ ConvertStreamTo(std::stringstream& stream, T& rhs) { \ static Node encode(const type& rhs) { \ std::stringstream stream; \ + stream.imbue(std::locale("C")); \ stream.precision(std::numeric_limits::max_digits10); \ conversion::inner_encode(rhs, stream); \ return Node(stream.str()); \ @@ -182,6 +183,7 @@ ConvertStreamTo(std::stringstream& stream, T& rhs) { } \ const std::string& input = node.Scalar(); \ std::stringstream stream(input); \ + stream.imbue(std::locale("C")); \ stream.unsetf(std::ios::dec); \ if ((stream.peek() == '-') && std::is_unsigned::value) { \ return false; \ diff --git a/include/yaml-cpp/traits.h b/include/yaml-cpp/traits.h index ffe9999f1..7c4cdd900 100644 --- a/include/yaml-cpp/traits.h +++ b/include/yaml-cpp/traits.h @@ -121,6 +121,7 @@ template struct streamable_to_string { static std::string impl(const Key& key) { std::stringstream ss; + ss.imbue(std::locale("C")); ss << key; return ss.str(); } diff --git a/src/node_data.cpp b/src/node_data.cpp index 8f5422ae6..3321263f2 100644 --- a/src/node_data.cpp +++ b/src/node_data.cpp @@ -310,6 +310,7 @@ void node_data::convert_sequence_to_map(const shared_memory_holder& pMemory) { reset_map(); for (std::size_t i = 0; i < m_sequence.size(); i++) { std::stringstream stream; + stream.imbue(std::locale("C")); stream << i; node& key = pMemory->create_node(); diff --git a/src/parser.cpp b/src/parser.cpp index b8b78ebab..5feda358b 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -77,6 +77,7 @@ void Parser::HandleYamlDirective(const Token& token) { } std::stringstream str(token.params[0]); + str.imbue(std::locale("C")); str >> m_pDirectives->version.major; str.get(); str >> m_pDirectives->version.minor; diff --git a/test/node/node_test.cpp b/test/node/node_test.cpp index 5f41ef255..b4444554d 100644 --- a/test/node/node_test.cpp +++ b/test/node/node_test.cpp @@ -849,5 +849,17 @@ TEST_F(NodeEmitterTest, NestFlowMapListNode) { ExpectOutput("{position: [1.5, 2.25, 3.125]}", mapNode); } + +TEST_F(NodeEmitterTest, RobustAgainstLocale) { + std::locale::global(std::locale("")); + Node node; + node.push_back(1.5); + node.push_back(2.25); + node.push_back(3.125); + node.push_back(123456789); + + ExpectOutput("- 1.5\n- 2.25\n- 3.125\n- 123456789", node); +} + } } From 9f7babc3ff000d1a8a567479c1b5d309658d8b7f Mon Sep 17 00:00:00 2001 From: Josiah VanderZee Date: Thu, 1 Aug 2024 08:00:57 -0500 Subject: [PATCH 03/65] Use c-strings to constant initialize token array Since `std::string` has to be dynamically constructed and destructed, it could be accessed before initialization or after destruction in a multithreaded context. By using constant c-strings instead, we guarantee that the array will be valid for the whole lifetime of the program. The use of `constexpr` also enforces this requirement. I have run clang-format on the file to format my changes according to CONTRIBUTING.md. --- src/token.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/token.h b/src/token.h index 9c9a5b779..1134af024 100644 --- a/src/token.h +++ b/src/token.h @@ -13,7 +13,7 @@ #include namespace YAML { -const std::string TokenNames[] = { +constexpr const char* TokenNames[] = { "DIRECTIVE", "DOC_START", "DOC_END", "BLOCK_SEQ_START", "BLOCK_MAP_START", "BLOCK_SEQ_END", "BLOCK_MAP_END", "BLOCK_ENTRY", "FLOW_SEQ_START", "FLOW_MAP_START", "FLOW_SEQ_END", "FLOW_MAP_END", From b11eaf16310d3ceab04db3d07dd3f90b05a661a5 Mon Sep 17 00:00:00 2001 From: Josiah VanderZee Date: Thu, 1 Aug 2024 08:15:04 -0500 Subject: [PATCH 04/65] Run format target from project root The CMake format target does not use the correct .clang-format file in out-of-source builds. This instructs CMake to use the project root as the working directory for running the clang-format command so that it finds the .clang-format file. --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 72fa5427b..7e8a528d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -197,6 +197,7 @@ if (YAML_CPP_FORMAT_SOURCE AND YAML_CPP_CLANG_FORMAT_EXE) COMMAND clang-format --style=file -i $ COMMAND_EXPAND_LISTS COMMENT "Running clang-format" + WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" VERBATIM) endif() From 1f5e971f77cdb3a06185b7e134549cc52906da68 Mon Sep 17 00:00:00 2001 From: NameSirius Date: Mon, 5 Aug 2024 18:03:37 +0800 Subject: [PATCH 05/65] Fix compile warning with -Wshadow --- include/yaml-cpp/emitterstyle.h | 4 ++-- include/yaml-cpp/node/type.h | 4 ++-- src/singledocparser.cpp | 5 ++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/include/yaml-cpp/emitterstyle.h b/include/yaml-cpp/emitterstyle.h index 67bb3981b..7497d357e 100644 --- a/include/yaml-cpp/emitterstyle.h +++ b/include/yaml-cpp/emitterstyle.h @@ -8,8 +8,8 @@ #endif namespace YAML { -struct EmitterStyle { - enum value { Default, Block, Flow }; +namespace EmitterStyle { +enum value { Default, Block, Flow }; }; } diff --git a/include/yaml-cpp/node/type.h b/include/yaml-cpp/node/type.h index 9d55ca966..cc0901c5c 100644 --- a/include/yaml-cpp/node/type.h +++ b/include/yaml-cpp/node/type.h @@ -8,8 +8,8 @@ #endif namespace YAML { -struct NodeType { - enum value { Undefined, Null, Scalar, Sequence, Map }; +namespace NodeType { +enum value { Undefined, Null, Scalar, Sequence, Map }; }; } diff --git a/src/singledocparser.cpp b/src/singledocparser.cpp index 22913d198..a8e949c2e 100644 --- a/src/singledocparser.cpp +++ b/src/singledocparser.cpp @@ -1,4 +1,3 @@ -#include #include #include @@ -93,8 +92,8 @@ void SingleDocParser::HandleNode(EventHandler& eventHandler) { // add non-specific tags if (tag.empty()) tag = (token.type == Token::NON_PLAIN_SCALAR ? "!" : "?"); - - if (token.type == Token::PLAIN_SCALAR + + if (token.type == Token::PLAIN_SCALAR && tag.compare("?") == 0 && IsNullString(token.value)) { eventHandler.OnNull(mark, anchor); m_scanner.pop(); From 04dddd699979a0d55b8abc970f65d93ee175d5cc Mon Sep 17 00:00:00 2001 From: Jesse Beder Date: Mon, 5 Aug 2024 15:50:36 -0500 Subject: [PATCH 06/65] Revert "Fix compile warning with -Wshadow" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 1f5e971f77cdb3a06185b7e134549cc52906da68. See #1306; the previous commit caused an error with -Wpedantic: yaml-cpp/include/yaml-cpp/emitterstyle.h:13:2: error: extra ‘;’ [-Wpedantic] Since the original commit was to resolve warnings, reverting and the OP can produce a new one that fixes this issue. --- include/yaml-cpp/emitterstyle.h | 4 ++-- include/yaml-cpp/node/type.h | 4 ++-- src/singledocparser.cpp | 5 +++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/include/yaml-cpp/emitterstyle.h b/include/yaml-cpp/emitterstyle.h index 7497d357e..67bb3981b 100644 --- a/include/yaml-cpp/emitterstyle.h +++ b/include/yaml-cpp/emitterstyle.h @@ -8,8 +8,8 @@ #endif namespace YAML { -namespace EmitterStyle { -enum value { Default, Block, Flow }; +struct EmitterStyle { + enum value { Default, Block, Flow }; }; } diff --git a/include/yaml-cpp/node/type.h b/include/yaml-cpp/node/type.h index cc0901c5c..9d55ca966 100644 --- a/include/yaml-cpp/node/type.h +++ b/include/yaml-cpp/node/type.h @@ -8,8 +8,8 @@ #endif namespace YAML { -namespace NodeType { -enum value { Undefined, Null, Scalar, Sequence, Map }; +struct NodeType { + enum value { Undefined, Null, Scalar, Sequence, Map }; }; } diff --git a/src/singledocparser.cpp b/src/singledocparser.cpp index a8e949c2e..22913d198 100644 --- a/src/singledocparser.cpp +++ b/src/singledocparser.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -92,8 +93,8 @@ void SingleDocParser::HandleNode(EventHandler& eventHandler) { // add non-specific tags if (tag.empty()) tag = (token.type == Token::NON_PLAIN_SCALAR ? "!" : "?"); - - if (token.type == Token::PLAIN_SCALAR + + if (token.type == Token::PLAIN_SCALAR && tag.compare("?") == 0 && IsNullString(token.value)) { eventHandler.OnNull(mark, anchor); m_scanner.pop(); From 06c3d1db51acac5d641597554a747c349fb93ec5 Mon Sep 17 00:00:00 2001 From: NameSirius Date: Tue, 6 Aug 2024 14:18:15 +0800 Subject: [PATCH 07/65] fix compile warning(Pull requests #1305)(Issues #1306) --- include/yaml-cpp/emitterstyle.h | 7 ++++--- include/yaml-cpp/node/type.h | 7 ++++--- src/singledocparser.cpp | 5 ++--- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/include/yaml-cpp/emitterstyle.h b/include/yaml-cpp/emitterstyle.h index 67bb3981b..5a6355fa2 100644 --- a/include/yaml-cpp/emitterstyle.h +++ b/include/yaml-cpp/emitterstyle.h @@ -8,9 +8,10 @@ #endif namespace YAML { -struct EmitterStyle { - enum value { Default, Block, Flow }; -}; +namespace EmitterStyle { +enum value { Default, Block, Flow }; +} + } #endif // EMITTERSTYLE_H_62B23520_7C8E_11DE_8A39_0800200C9A66 diff --git a/include/yaml-cpp/node/type.h b/include/yaml-cpp/node/type.h index 9d55ca966..b1237670c 100644 --- a/include/yaml-cpp/node/type.h +++ b/include/yaml-cpp/node/type.h @@ -8,9 +8,10 @@ #endif namespace YAML { -struct NodeType { - enum value { Undefined, Null, Scalar, Sequence, Map }; -}; +namespace NodeType { +enum value { Undefined, Null, Scalar, Sequence, Map }; +} + } #endif // VALUE_TYPE_H_62B23520_7C8E_11DE_8A39_0800200C9A66 diff --git a/src/singledocparser.cpp b/src/singledocparser.cpp index 22913d198..a8e949c2e 100644 --- a/src/singledocparser.cpp +++ b/src/singledocparser.cpp @@ -1,4 +1,3 @@ -#include #include #include @@ -93,8 +92,8 @@ void SingleDocParser::HandleNode(EventHandler& eventHandler) { // add non-specific tags if (tag.empty()) tag = (token.type == Token::NON_PLAIN_SCALAR ? "!" : "?"); - - if (token.type == Token::PLAIN_SCALAR + + if (token.type == Token::PLAIN_SCALAR && tag.compare("?") == 0 && IsNullString(token.value)) { eventHandler.OnNull(mark, anchor); m_scanner.pop(); From 8fbf344aeeedbfdf1f235278dee6a52d86942404 Mon Sep 17 00:00:00 2001 From: Simon Gene Gottlieb Date: Thu, 18 Jul 2024 10:29:30 +0200 Subject: [PATCH 08/65] doc, fix: invalid liquid '{{...}}' tags jekyll/liquid got hung up on `{{"Daniel", 26}, {"Jesse", 24}}`. The reason is that `{{...}}` are used as variables that are replaced by there values. In this case we have a YAML object that looks the same. This issue can be fixed by surrounding the block into `{% raw %}...{% endraw %}` tags. --- docs/How-To-Emit-YAML.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/How-To-Emit-YAML.md b/docs/How-To-Emit-YAML.md index f28fcbe0e..6e6f9f58e 100644 --- a/docs/How-To-Emit-YAML.md +++ b/docs/How-To-Emit-YAML.md @@ -154,6 +154,7 @@ produces # STL Containers, and Other Overloads # We overload `operator <<` for `std::vector`, `std::list`, and `std::map`, so you can write stuff like: +{% raw %} ```cpp std::vector squares = {1, 4, 9, 16}; @@ -165,6 +166,7 @@ out << YAML::Flow << squares; out << ages; out << YAML::EndSeq; ``` +{% endraw %} produces From 7b469b4220f96fb3d036cf68cd7bd30bd39e61d2 Mon Sep 17 00:00:00 2001 From: Christopher Fore Date: Wed, 14 Aug 2024 21:02:32 -0400 Subject: [PATCH 09/65] emitterutils: Explicitly include GCC 15 will no longer include it by default, resulting in build failures in projects that do not explicitly include it. Error: src/emitterutils.cpp:221:11: error: 'uint16_t' was not declared in this scope 221 | std::pair EncodeUTF16SurrogatePair(int codePoint) { | ^~~~~~~~ src/emitterutils.cpp:13:1: note: 'uint16_t' is defined in header ''; this is probably fixable by adding '#include ' 12 | #include "yaml-cpp/null.h" +++ |+#include 13 | #include "yaml-cpp/ostream_wrapper.h" Tests pass. Closes: #1307 See-also: https://gcc.gnu.org/pipermail/gcc-cvs/2024-August/407124.html See-also: https://bugs.gentoo.org/937412 Signed-off-by: Christopher Fore --- src/emitterutils.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/emitterutils.cpp b/src/emitterutils.cpp index fc41011a5..f801b1d0c 100644 --- a/src/emitterutils.cpp +++ b/src/emitterutils.cpp @@ -1,4 +1,5 @@ #include +#include #include #include From b38ac5b55f5b64cffce71eac9433e553b3898bd1 Mon Sep 17 00:00:00 2001 From: Cristian Le Date: Wed, 21 Aug 2024 09:46:53 +0200 Subject: [PATCH 10/65] Use FetchContent_MakeAvailable --- README.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/README.md b/README.md index a121b700c..1dc452d57 100644 --- a/README.md +++ b/README.md @@ -61,13 +61,7 @@ FetchContent_Declare( GIT_REPOSITORY https://github.com/jbeder/yaml-cpp.git GIT_TAG # Can be a tag (yaml-cpp-x.x.x), a commit hash, or a branch name (master) ) -FetchContent_GetProperties(yaml-cpp) - -if(NOT yaml-cpp_POPULATED) - message(STATUS "Fetching yaml-cpp...") - FetchContent_Populate(yaml-cpp) - add_subdirectory(${yaml-cpp_SOURCE_DIR} ${yaml-cpp_BINARY_DIR}) -endif() +FetchContent_MakeAvailable(yaml-cpp) target_link_libraries(YOUR_LIBRARY PUBLIC yaml-cpp::yaml-cpp) # The library or executable that require yaml-cpp library ``` From ee9c4d19bedd660734125afed7ef6cf1822ea196 Mon Sep 17 00:00:00 2001 From: Simon Gene Gottlieb Date: Thu, 22 Aug 2024 10:29:29 +0200 Subject: [PATCH 11/65] fix: parse files with '\r' symbols as line ending correctly --- src/stream.cpp | 19 ++++++++++++++++++- src/stream.h | 1 + test/integration/load_node_test.cpp | 16 ++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/stream.cpp b/src/stream.cpp index b1aa092f6..72f0ec0ae 100644 --- a/src/stream.cpp +++ b/src/stream.cpp @@ -262,7 +262,24 @@ char Stream::get() { AdvanceCurrent(); m_mark.column++; - if (ch == '\n') { + // if line ending symbol is unknown, set it to the first + // encountered line ending. + // if line ending '\r' set ending symbol to '\r' + // other wise set it to '\n' + if (!m_lineEndingSymbol) { + if (ch == '\n') { // line ending is '\n' + m_lineEndingSymbol = '\n'; + } else if (ch == '\r') { + auto ch2 = peek(); + if (ch2 == '\n') { // line ending is '\r\n' + m_lineEndingSymbol = '\n'; + } else { // line ending is '\r' + m_lineEndingSymbol = '\r'; + } + } + } + + if (ch == m_lineEndingSymbol) { m_mark.column = 0; m_mark.line++; } diff --git a/src/stream.h b/src/stream.h index 2bc7a1521..214104ade 100644 --- a/src/stream.h +++ b/src/stream.h @@ -53,6 +53,7 @@ class Stream { Mark m_mark; CharacterSet m_charSet; + char m_lineEndingSymbol{}; // 0 means it is not determined yet, must be '\n' or '\r' mutable std::deque m_readahead; unsigned char* const m_pPrefetched; mutable size_t m_nPrefetchedAvailable; diff --git a/test/integration/load_node_test.cpp b/test/integration/load_node_test.cpp index 9d0c790fd..1cc84a45a 100644 --- a/test/integration/load_node_test.cpp +++ b/test/integration/load_node_test.cpp @@ -360,5 +360,21 @@ TEST(LoadNodeTest, BlockCRNLEncoded) { EXPECT_EQ(1, node["followup"].as()); } +TEST(LoadNodeTest, BlockCREncoded) { + Node node = Load( + "blockText: |\r" + " some arbitrary text \r" + " spanning some \r" + " lines, that are split \r" + " by CR and NL\r" + "followup: 1"); + EXPECT_EQ( + "some arbitrary text \nspanning some \nlines, that are split \nby CR and " + "NL\n", + node["blockText"].as()); + EXPECT_EQ(1, node["followup"].as()); +} + + } // namespace } // namespace YAML From 84459a7f982ea4d10e943237b2e9c71afdab6a45 Mon Sep 17 00:00:00 2001 From: Simon Gene Gottlieb Date: Thu, 22 Aug 2024 12:13:35 +0200 Subject: [PATCH 12/65] fix: missing token enum name --- src/token.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/token.h b/src/token.h index 1134af024..ebd8477c6 100644 --- a/src/token.h +++ b/src/token.h @@ -18,7 +18,8 @@ constexpr const char* TokenNames[] = { "BLOCK_MAP_START", "BLOCK_SEQ_END", "BLOCK_MAP_END", "BLOCK_ENTRY", "FLOW_SEQ_START", "FLOW_MAP_START", "FLOW_SEQ_END", "FLOW_MAP_END", "FLOW_MAP_COMPACT", "FLOW_ENTRY", "KEY", "VALUE", - "ANCHOR", "ALIAS", "TAG", "SCALAR"}; + "ANCHOR", "ALIAS", "TAG", "SCALAR", + "NON_PLAIN_SCALAR"}; struct Token { // enums From 850ec4f39e1c4a3a950e01e58329ffeb970769d8 Mon Sep 17 00:00:00 2001 From: Orgad Shaneh Date: Wed, 11 Nov 2020 18:51:46 +0200 Subject: [PATCH 13/65] Fix reference types in iterators Amends 26faac387c237ccac75a56925c6858baf8ccda1b. --- include/yaml-cpp/node/detail/iterator.h | 2 +- include/yaml-cpp/node/detail/node_iterator.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/yaml-cpp/node/detail/iterator.h b/include/yaml-cpp/node/detail/iterator.h index 997c69a14..04d2da4fc 100644 --- a/include/yaml-cpp/node/detail/iterator.h +++ b/include/yaml-cpp/node/detail/iterator.h @@ -41,7 +41,7 @@ class iterator_base { using value_type = V; using difference_type = std::ptrdiff_t; using pointer = V*; - using reference = V; + using reference = V&; public: iterator_base() : m_iterator(), m_pMemory() {} diff --git a/include/yaml-cpp/node/detail/node_iterator.h b/include/yaml-cpp/node/detail/node_iterator.h index 49dcf958d..6eb4ddc13 100644 --- a/include/yaml-cpp/node/detail/node_iterator.h +++ b/include/yaml-cpp/node/detail/node_iterator.h @@ -69,7 +69,7 @@ class node_iterator_base { using value_type = node_iterator_value; using difference_type = std::ptrdiff_t; using pointer = node_iterator_value*; - using reference = node_iterator_value; + using reference = node_iterator_value&; using SeqIter = typename node_iterator_type::seq; using MapIter = typename node_iterator_type::map; From 47cd2725d61e54719933b83ea51c64ad60c24066 Mon Sep 17 00:00:00 2001 From: Orgad Shaneh Date: Tue, 27 Aug 2024 18:42:15 +0300 Subject: [PATCH 14/65] Remove build debug messages They were commented out before 0733aeb45, but when that commit was reverted in 2b65c65e1 they were recovered uncommented. --- include/yaml-cpp/dll.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/yaml-cpp/dll.h b/include/yaml-cpp/dll.h index eabdda1d9..4e55ab8c2 100644 --- a/include/yaml-cpp/dll.h +++ b/include/yaml-cpp/dll.h @@ -15,11 +15,9 @@ # ifndef YAML_CPP_API # ifdef yaml_cpp_EXPORTS /* We are building this library */ -# pragma message( "Defining YAML_CPP_API for DLL export" ) # define YAML_CPP_API __declspec(dllexport) # else /* We are using this library */ -# pragma message( "Defining YAML_CPP_API for DLL import" ) # define YAML_CPP_API __declspec(dllimport) # endif # endif From 1f2b841949de53366508d44e59a6d5bbac562fa3 Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Thu, 12 Sep 2024 14:14:29 +0200 Subject: [PATCH 15/65] fix(src,include,test): fixed multiple cases where a bad yaml was accepted. Signed-off-by: Federico Di Pierro --- include/yaml-cpp/exceptions.h | 2 + src/scanner.cpp | 24 +++++++++++ src/scanner.h | 1 + src/scantoken.cpp | 7 ++++ test/integration/load_node_test.cpp | 63 +++++++++++++++++++++++++++++ 5 files changed, 97 insertions(+) diff --git a/include/yaml-cpp/exceptions.h b/include/yaml-cpp/exceptions.h index f6b2602ae..99e06b24e 100644 --- a/include/yaml-cpp/exceptions.h +++ b/include/yaml-cpp/exceptions.h @@ -48,6 +48,8 @@ const char* const UNKNOWN_TOKEN = "unknown token"; const char* const DOC_IN_SCALAR = "illegal document indicator in scalar"; const char* const EOF_IN_SCALAR = "illegal EOF in scalar"; const char* const CHAR_IN_SCALAR = "illegal character in scalar"; +const char* const UNEXPECTED_SCALAR = "unexpected scalar"; +const char* const UNEXPECTED_FLOW = "plain value cannot start with flow indicator character"; const char* const TAB_IN_INDENTATION = "illegal tab when looking for indentation"; const char* const FLOW_END = "illegal flow end"; diff --git a/src/scanner.cpp b/src/scanner.cpp index ea5511a11..d0eb03cc7 100644 --- a/src/scanner.cpp +++ b/src/scanner.cpp @@ -13,6 +13,7 @@ Scanner::Scanner(std::istream& in) m_startedStream(false), m_endedStream(false), m_simpleKeyAllowed(false), + m_scalarValueAllowed(false), m_canBeJSONFlow(false), m_simpleKeys{}, m_indents{}, @@ -127,6 +128,17 @@ void Scanner::ScanNextToken() { } if (INPUT.peek() == Keys::FlowEntry) { + // values starting with `,` are not allowed. + // eg: reject `,foo` + if (INPUT.column() == 0) { + throw ParserException(INPUT.mark(), ErrorMsg::UNEXPECTED_FLOW); + } + // if we already parsed a quoted scalar value and we are not in a flow, + // then `,` is not a valid character. + // eg: reject `"foo",` + if (!m_scalarValueAllowed) { + throw ParserException(INPUT.mark(), ErrorMsg::UNEXPECTED_SCALAR); + } return ScanFlowEntry(); } @@ -159,6 +171,13 @@ void Scanner::ScanNextToken() { return ScanBlockScalar(); } + // if we already parsed a quoted scalar value in this line, + // another scalar value is an error. + // eg: reject `"foo" "bar"` + if (!m_scalarValueAllowed) { + throw ParserException(INPUT.mark(), ErrorMsg::UNEXPECTED_SCALAR); + } + if (INPUT.peek() == '\'' || INPUT.peek() == '\"') { return ScanQuotedScalar(); } @@ -203,6 +222,9 @@ void Scanner::ScanToNextToken() { // oh yeah, and let's get rid of that simple key InvalidateSimpleKey(); + // new line - we accept a scalar value now + m_scalarValueAllowed = true; + // new line - we may be able to accept a simple key now if (InBlockContext()) { m_simpleKeyAllowed = true; @@ -245,6 +267,7 @@ const RegEx& Scanner::GetValueRegex() const { void Scanner::StartStream() { m_startedStream = true; m_simpleKeyAllowed = true; + m_scalarValueAllowed = true; std::unique_ptr pIndent( new IndentMarker(-1, IndentMarker::NONE)); m_indentRefs.push_back(std::move(pIndent)); @@ -261,6 +284,7 @@ void Scanner::EndStream() { PopAllSimpleKeys(); m_simpleKeyAllowed = false; + m_scalarValueAllowed = false; m_endedStream = true; } diff --git a/src/scanner.h b/src/scanner.h index 4af938e69..a3bfb662d 100644 --- a/src/scanner.h +++ b/src/scanner.h @@ -177,6 +177,7 @@ class Scanner { // state info bool m_startedStream, m_endedStream; bool m_simpleKeyAllowed; + bool m_scalarValueAllowed; bool m_canBeJSONFlow; std::stack m_simpleKeys; std::stack m_indents; diff --git a/src/scantoken.cpp b/src/scantoken.cpp index 1a94ab1d7..b13d21133 100644 --- a/src/scantoken.cpp +++ b/src/scantoken.cpp @@ -211,6 +211,9 @@ void Scanner::ScanValue() { m_simpleKeyAllowed = InBlockContext(); } + // we are parsing a `key: value` pair; scalars are always allowed + m_scalarValueAllowed = true; + // eat Mark mark = INPUT.mark(); INPUT.eat(1); @@ -360,6 +363,10 @@ void Scanner::ScanQuotedScalar() { // and scan scalar = ScanScalar(INPUT, params); m_simpleKeyAllowed = false; + // we just scanned a quoted scalar; + // we can only have another scalar in this line + // if we are in a flow, eg: `[ "foo", "bar" ]` is ok, but `"foo", "bar"` isn't. + m_scalarValueAllowed = InFlowContext(); m_canBeJSONFlow = true; Token token(Token::NON_PLAIN_SCALAR, mark); diff --git a/test/integration/load_node_test.cpp b/test/integration/load_node_test.cpp index 1cc84a45a..3f32d1aa1 100644 --- a/test/integration/load_node_test.cpp +++ b/test/integration/load_node_test.cpp @@ -334,6 +334,69 @@ TEST(NodeTest, LoadQuotedNull) { EXPECT_EQ(node.as(), "null"); } +TEST(NodeTest, LoadNonClosedQuotedString) { + EXPECT_THROW(Load(R"("foo)"), ParserException); +} + +TEST(NodeTest, LoadWrongQuotedString) { + EXPECT_THROW(Load(R"("foo" [)"), ParserException); + EXPECT_THROW(Load(R"("foo", [)"), ParserException); +} + +TEST(NodeTest, LoadUnquotedQuotedStrings) { + Node node = Load(R"(foo,"bar")"); + EXPECT_EQ(node.as(), "foo,\"bar\""); + + node = Load(R"(foo,bar)"); + EXPECT_EQ(node.as(), "foo,bar"); + + node = Load(R"(foo,)"); + EXPECT_EQ(node.as(), "foo,"); + + node = Load(R"(foo "bar")"); + EXPECT_EQ(node.as(), "foo \"bar\""); +} + +TEST(NodeTest, LoadCommaSeparatedStrings) { + EXPECT_THROW(Load(R"("foo","bar")"), ParserException); + EXPECT_THROW(Load(R"("foo",bar)"), ParserException); + EXPECT_THROW(Load(R"(,)"), ParserException); + EXPECT_THROW(Load(R"("foo",)"), ParserException); + EXPECT_THROW(Load(R"("foo","")"), ParserException); + EXPECT_THROW(Load(R"("foo",)"), ParserException); + EXPECT_THROW(Load(R"(,"foo")"), ParserException); + EXPECT_THROW(Load(R"(,foo)"), ParserException); + + const char *doc_ok = R"( +top +, aa +)"; + EXPECT_NO_THROW(Load(doc_ok)); + + const char *doc_ok_2 = R"( +top +, "aa" +)"; + EXPECT_NO_THROW(Load(doc_ok_2)); + + const char *doc_fail = R"( +"top" +, "aa" +)"; + EXPECT_THROW(Load(doc_fail), ParserException); + + const char *doc_fail_2 = R"( +"top" +, aa +)"; + EXPECT_THROW(Load(doc_fail_2), ParserException); +} + +TEST(NodeTest, LoadSameLineStrings) { + EXPECT_THROW(Load(R"("foo" "bar")"), ParserException); + EXPECT_THROW(Load(R"("foo" bar)"), ParserException); +} + TEST(NodeTest, LoadTagWithParenthesis) { Node node = Load("!Complex(Tag) foo"); EXPECT_EQ(node.Tag(), "!Complex(Tag)"); From da82fd982c260e7f335ce5acbceff24b270544d1 Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Fri, 13 Sep 2024 08:50:53 +0200 Subject: [PATCH 16/65] chore(test/integration): refactor some test cases to their own test. Plus, check that the content is what we actually expect. Signed-off-by: Federico Di Pierro --- test/integration/load_node_test.cpp | 47 +++++++++++++++-------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/test/integration/load_node_test.cpp b/test/integration/load_node_test.cpp index 3f32d1aa1..e9a133b74 100644 --- a/test/integration/load_node_test.cpp +++ b/test/integration/load_node_test.cpp @@ -366,30 +366,31 @@ TEST(NodeTest, LoadCommaSeparatedStrings) { EXPECT_THROW(Load(R"("foo",)"), ParserException); EXPECT_THROW(Load(R"(,"foo")"), ParserException); EXPECT_THROW(Load(R"(,foo)"), ParserException); +} - const char *doc_ok = R"( -top -, aa -)"; - EXPECT_NO_THROW(Load(doc_ok)); - - const char *doc_ok_2 = R"( -top -, "aa" -)"; - EXPECT_NO_THROW(Load(doc_ok_2)); - - const char *doc_fail = R"( -"top" -, "aa" -)"; - EXPECT_THROW(Load(doc_fail), ParserException); - - const char *doc_fail_2 = R"( -"top" -, aa -)"; - EXPECT_THROW(Load(doc_fail_2), ParserException); +struct NewLineStringsTestCase { + std::string input; + std::string expected_content; + bool should_throw; +}; +TEST(NodeTest, LoadNewLineStrings) { + std::vector tests = { + {"foo\n, bar", "foo , bar", false}, + {"foo\n, \"bar\"", "foo , \"bar\"", false}, + {"\"foo\"\n, \"bar\"", "", true}, + {"\"foo\"\n, bar", "", true}, + }; + for (const NewLineStringsTestCase& test : tests) { + if (test.should_throw) { + EXPECT_THROW(Load(test.input), ParserException); + } else { + Node node = Load(test.input); + Emitter emitter; + emitter << node; + EXPECT_EQ(NodeType::Scalar, node.Type()); + EXPECT_EQ(test.expected_content, std::string(emitter.c_str())); + } + } } TEST(NodeTest, LoadSameLineStrings) { From bc671571091601f446d1dc07dbba4df6c775e757 Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Thu, 12 Sep 2024 14:16:15 +0200 Subject: [PATCH 17/65] fix(src): avoid possible infinite loop in LoadAll(). Leave at first empty root. Signed-off-by: Federico Di Pierro --- src/parse.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parse.cpp b/src/parse.cpp index 262536b85..4cfab77b2 100644 --- a/src/parse.cpp +++ b/src/parse.cpp @@ -53,7 +53,7 @@ std::vector LoadAll(std::istream& input) { Parser parser(input); while (true) { NodeBuilder builder; - if (!parser.HandleNextDocument(builder)) { + if (!parser.HandleNextDocument(builder) || builder.Root().IsNull()) { break; } docs.push_back(builder.Root()); From 29c59c01d406653358ad3f4afae6caa9b53d01ca Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Fri, 13 Sep 2024 08:58:51 +0200 Subject: [PATCH 18/65] new(test): added a test to avoid future issues with LoadAll(). Signed-off-by: Federico Di Pierro --- test/integration/node_spec_test.cpp | 7 +++++++ test/specexamples.h | 3 +++ 2 files changed, 10 insertions(+) diff --git a/test/integration/node_spec_test.cpp b/test/integration/node_spec_test.cpp index bfc8578a6..1754ee9b7 100644 --- a/test/integration/node_spec_test.cpp +++ b/test/integration/node_spec_test.cpp @@ -941,6 +941,13 @@ TEST(NodeSpecTest, Ex7_24_FlowNodes) { EXPECT_EQ("", doc[4].as()); } +TEST(NodeSpecTest, Ex7_25_InfiniteLoopNodes) { + // Until yaml-cpp <= 0.8.0 this caused an infinite loop; + // After, it triggers an exception (but LoadAll is smart enough to avoid + // the infinite loop in any case). + ASSERT_THROW(LoadAll(ex7_25), ParserException); +} + TEST(NodeSpecTest, Ex8_1_BlockScalarHeader) { Node doc = Load(ex8_1); EXPECT_EQ(4, doc.size()); diff --git a/test/specexamples.h b/test/specexamples.h index 46e2c4c7d..ebe62e5b6 100644 --- a/test/specexamples.h +++ b/test/specexamples.h @@ -687,6 +687,9 @@ const char *ex7_24 = "- *anchor\n" "- !!str"; +const char *ex7_25 = + ","; + const char *ex8_1 = "- | # Empty header\n" " literal\n" From c2bec4c755c67ad86185a2a264996137904fb712 Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Mon, 14 Oct 2024 09:19:43 +0200 Subject: [PATCH 19/65] chore(test): moved infiniteloop test to load_node_test suite. Signed-off-by: Federico Di Pierro --- test/integration/load_node_test.cpp | 7 +++++++ test/integration/node_spec_test.cpp | 7 ------- test/specexamples.h | 3 --- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/test/integration/load_node_test.cpp b/test/integration/load_node_test.cpp index e9a133b74..49f2b61be 100644 --- a/test/integration/load_node_test.cpp +++ b/test/integration/load_node_test.cpp @@ -368,6 +368,13 @@ TEST(NodeTest, LoadCommaSeparatedStrings) { EXPECT_THROW(Load(R"(,foo)"), ParserException); } +TEST(NodeSpecTest, InfiniteLoopNodes) { + // Until yaml-cpp <= 0.8.0 this caused an infinite loop; + // After, it triggers an exception (but LoadAll is smart enough to avoid + // the infinite loop in any case). + EXPECT_THROW(LoadAll(R"(,)"), ParserException); +} + struct NewLineStringsTestCase { std::string input; std::string expected_content; diff --git a/test/integration/node_spec_test.cpp b/test/integration/node_spec_test.cpp index 1754ee9b7..bfc8578a6 100644 --- a/test/integration/node_spec_test.cpp +++ b/test/integration/node_spec_test.cpp @@ -941,13 +941,6 @@ TEST(NodeSpecTest, Ex7_24_FlowNodes) { EXPECT_EQ("", doc[4].as()); } -TEST(NodeSpecTest, Ex7_25_InfiniteLoopNodes) { - // Until yaml-cpp <= 0.8.0 this caused an infinite loop; - // After, it triggers an exception (but LoadAll is smart enough to avoid - // the infinite loop in any case). - ASSERT_THROW(LoadAll(ex7_25), ParserException); -} - TEST(NodeSpecTest, Ex8_1_BlockScalarHeader) { Node doc = Load(ex8_1); EXPECT_EQ(4, doc.size()); diff --git a/test/specexamples.h b/test/specexamples.h index ebe62e5b6..46e2c4c7d 100644 --- a/test/specexamples.h +++ b/test/specexamples.h @@ -687,9 +687,6 @@ const char *ex7_24 = "- *anchor\n" "- !!str"; -const char *ex7_25 = - ","; - const char *ex8_1 = "- | # Empty header\n" " literal\n" From 3d2888cc8a45da2f420454ad728cdfad01a3d54f Mon Sep 17 00:00:00 2001 From: Reini Urban Date: Fri, 1 Nov 2024 13:29:36 +0100 Subject: [PATCH 20/65] missing keys should throw InvalidNode, not BadConversion Fixes GH #1274 --- include/yaml-cpp/node/impl.h | 6 ++++-- test/node/node_test.cpp | 8 ++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/include/yaml-cpp/node/impl.h b/include/yaml-cpp/node/impl.h index 150a6cfc6..f5d622b25 100644 --- a/include/yaml-cpp/node/impl.h +++ b/include/yaml-cpp/node/impl.h @@ -124,8 +124,8 @@ struct as_if { const Node& node; T operator()() const { - if (!node.m_pNode) - throw TypedBadConversion(node.Mark()); + if (!node.m_pNode) // no fallback + throw InvalidNode(node.m_invalidKey); T t; if (convert::decode(node, t)) @@ -140,6 +140,8 @@ struct as_if { const Node& node; std::string operator()() const { + if (node.Type() == NodeType::Undefined) // no fallback + throw InvalidNode(node.m_invalidKey); if (node.Type() == NodeType::Null) return "null"; if (node.Type() != NodeType::Scalar) diff --git a/test/node/node_test.cpp b/test/node/node_test.cpp index b4444554d..a9c47b7a1 100644 --- a/test/node/node_test.cpp +++ b/test/node/node_test.cpp @@ -191,6 +191,14 @@ TEST(NodeTest, MapElementRemoval) { EXPECT_TRUE(!node["foo"]); } +TEST(NodeTest, MissingKey) { + Node node; + node["foo"] = "value"; + EXPECT_TRUE(!node["bar"]); + EXPECT_EQ(NodeType::Undefined, node["bar"].Type()); + EXPECT_THROW(node["bar"].as(), InvalidNode); +} + TEST(NodeTest, MapIntegerElementRemoval) { Node node; node[1] = "hello"; From bd070a7b76b56dcf470512ee53269f3eb10a92bd Mon Sep 17 00:00:00 2001 From: Simon Gene Gottlieb Date: Sat, 6 Jul 2024 08:00:10 +0200 Subject: [PATCH 21/65] fix: prettier floating point numbers Add dragonbox to compute the required precision to print floating point numbers. This avoids uglification of floating point numbers that happen by default via std::stringstream. Numbers like 34.34 used to be converted to '34.340000000000003' as strings. With this version they will be converted to the string '34.34'. This fixes issue https://github.com/jbeder/yaml-cpp/issues/1289 --- include/yaml-cpp/contrib/dragonbox.h | 4192 ++++++++++++++++++++++++++ include/yaml-cpp/emitter.h | 3 +- include/yaml-cpp/fp_to_string.h | 207 ++ include/yaml-cpp/node/convert.h | 3 +- test/fp_to_string_test.cpp | 242 ++ test/integration/emitter_test.cpp | 4 +- test/node/node_test.cpp | 11 +- 7 files changed, 4657 insertions(+), 5 deletions(-) create mode 100644 include/yaml-cpp/contrib/dragonbox.h create mode 100644 include/yaml-cpp/fp_to_string.h create mode 100644 test/fp_to_string_test.cpp diff --git a/include/yaml-cpp/contrib/dragonbox.h b/include/yaml-cpp/contrib/dragonbox.h new file mode 100644 index 000000000..73d8df851 --- /dev/null +++ b/include/yaml-cpp/contrib/dragonbox.h @@ -0,0 +1,4192 @@ +// SPDX-FileCopyrightText: 2020-2024 Junekey Jeon +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-License-Identifier: BSL-1.0 +// SPDX-License-Identifier: MIT + +// This file is an adjusted copy of https://github.com/jk-jeon/dragonbox +// Junekey Jeon agreed to license this file under MIT license explicitly for +// usage in yaml-cpp. + +#ifndef JKJ_HEADER_DRAGONBOX +#define JKJ_HEADER_DRAGONBOX + +// Attribute for storing static data into a dedicated place, e.g. flash memory. Every ODR-used +// static data declaration will be decorated with this macro. The users may define this macro, +// before including the library headers, into whatever they want. +#ifndef JKJ_STATIC_DATA_SECTION + #define JKJ_STATIC_DATA_SECTION +#else + #define JKJ_STATIC_DATA_SECTION_DEFINED 1 +#endif + +// To use the library with toolchains without standard C++ headers, the users may define this macro +// into their custom namespace which contains the defintions of all the standard C++ library +// features used in this header. (The list can be found below.) +#ifndef JKJ_STD_REPLACEMENT_NAMESPACE + #define JKJ_STD_REPLACEMENT_NAMESPACE std + #include + #include + #include + #include + #include + + #ifdef __has_include + #if __has_include() + #include + #endif + #endif +#else + #define JKJ_STD_REPLACEMENT_NAMESPACE_DEFINED 1 +#endif + +//////////////////////////////////////////////////////////////////////////////////////// +// Language feature detections. +//////////////////////////////////////////////////////////////////////////////////////// + +// C++14 constexpr +#if defined(__cpp_constexpr) && __cpp_constexpr >= 201304L + #define JKJ_HAS_CONSTEXPR14 1 +#elif __cplusplus >= 201402L + #define JKJ_HAS_CONSTEXPR14 1 +#elif defined(_MSC_VER) && _MSC_VER >= 1910 && _MSVC_LANG >= 201402L + #define JKJ_HAS_CONSTEXPR14 1 +#else + #define JKJ_HAS_CONSTEXPR14 0 +#endif + +#if JKJ_HAS_CONSTEXPR14 + #define JKJ_CONSTEXPR14 constexpr +#else + #define JKJ_CONSTEXPR14 +#endif + +// C++17 constexpr lambdas +#if defined(__cpp_constexpr) && __cpp_constexpr >= 201603L + #define JKJ_HAS_CONSTEXPR17 1 +#elif __cplusplus >= 201703L + #define JKJ_HAS_CONSTEXPR17 1 +#elif defined(_MSC_VER) && _MSC_VER >= 1911 && _MSVC_LANG >= 201703L + #define JKJ_HAS_CONSTEXPR17 1 +#else + #define JKJ_HAS_CONSTEXPR17 0 +#endif + +// C++17 inline variables +#if defined(__cpp_inline_variables) && __cpp_inline_variables >= 201606L + #define JKJ_HAS_INLINE_VARIABLE 1 +#elif __cplusplus >= 201703L + #define JKJ_HAS_INLINE_VARIABLE 1 +#elif defined(_MSC_VER) && _MSC_VER >= 1912 && _MSVC_LANG >= 201703L + #define JKJ_HAS_INLINE_VARIABLE 1 +#else + #define JKJ_HAS_INLINE_VARIABLE 0 +#endif + +#if JKJ_HAS_INLINE_VARIABLE + #define JKJ_INLINE_VARIABLE inline constexpr +#else + #define JKJ_INLINE_VARIABLE static constexpr +#endif + +// C++17 if constexpr +#if defined(__cpp_if_constexpr) && __cpp_if_constexpr >= 201606L + #define JKJ_HAS_IF_CONSTEXPR 1 +#elif __cplusplus >= 201703L + #define JKJ_HAS_IF_CONSTEXPR 1 +#elif defined(_MSC_VER) && _MSC_VER >= 1911 && _MSVC_LANG >= 201703L + #define JKJ_HAS_IF_CONSTEXPR 1 +#else + #define JKJ_HAS_IF_CONSTEXPR 0 +#endif + +#if JKJ_HAS_IF_CONSTEXPR + #define JKJ_IF_CONSTEXPR if constexpr +#else + #define JKJ_IF_CONSTEXPR if +#endif + +// C++20 std::bit_cast +#if JKJ_STD_REPLACEMENT_NAMESPACE_DEFINED + #if JKJ_STD_REPLACEMENT_HAS_BIT_CAST + #define JKJ_HAS_BIT_CAST 1 + #else + #define JKJ_HAS_BIT_CAST 0 + #endif +#elif defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L + #include + #define JKJ_HAS_BIT_CAST 1 +#else + #define JKJ_HAS_BIT_CAST 0 +#endif + +// C++23 if consteval or C++20 std::is_constant_evaluated +#if defined(__cpp_if_consteval) && __cpp_is_consteval >= 202106L + #define JKJ_IF_CONSTEVAL if consteval + #define JKJ_IF_NOT_CONSTEVAL if !consteval + #define JKJ_CAN_BRANCH_ON_CONSTEVAL 1 + #define JKJ_USE_IS_CONSTANT_EVALUATED 0 +#elif JKJ_STD_REPLACEMENT_NAMESPACE_DEFINED + #if JKJ_STD_REPLACEMENT_HAS_IS_CONSTANT_EVALUATED + #define JKJ_IF_CONSTEVAL if (stdr::is_constant_evaluated()) + #define JKJ_IF_NOT_CONSTEVAL if (!stdr::is_constant_evaluated()) + #define JKJ_CAN_BRANCH_ON_CONSTEVAL 1 + #define JKJ_USE_IS_CONSTANT_EVALUATED 1 + #elif JKJ_HAS_IF_CONSTEXPR + #define JKJ_IF_CONSTEVAL if constexpr (false) + #define JKJ_IF_NOT_CONSTEVAL if constexpr (true) + #define JKJ_CAN_BRANCH_ON_CONSTEVAL 0 + #define JKJ_USE_IS_CONSTANT_EVALUATED 0 + #else + #define JKJ_IF_CONSTEVAL if (false) + #define JKJ_IF_NOT_CONSTEVAL if (true) + #define JKJ_CAN_BRANCH_ON_CONSTEVAL 0 + #define JKJ_USE_IS_CONSTANT_EVALUATED 0 + #endif +#else + #if defined(__cpp_lib_is_constant_evaluated) && __cpp_lib_is_constant_evaluated >= 201811L + #define JKJ_IF_CONSTEVAL if (stdr::is_constant_evaluated()) + #define JKJ_IF_NOT_CONSTEVAL if (!stdr::is_constant_evaluated()) + #define JKJ_CAN_BRANCH_ON_CONSTEVAL 1 + #define JKJ_USE_IS_CONSTANT_EVALUATED 1 + #elif JKJ_HAS_IF_CONSTEXPR + #define JKJ_IF_CONSTEVAL if constexpr (false) + #define JKJ_IF_NOT_CONSTEVAL if constexpr (true) + #define JKJ_CAN_BRANCH_ON_CONSTEVAL 0 + #define JKJ_USE_IS_CONSTANT_EVALUATED 0 + #else + #define JKJ_IF_CONSTEVAL if (false) + #define JKJ_IF_NOT_CONSTEVAL if (true) + #define JKJ_CAN_BRANCH_ON_CONSTEVAL 0 + #define JKJ_USE_IS_CONSTANT_EVALUATED 0 + #endif +#endif + +#if JKJ_CAN_BRANCH_ON_CONSTEVAL && JKJ_HAS_BIT_CAST + #define JKJ_CONSTEXPR20 constexpr +#else + #define JKJ_CONSTEXPR20 +#endif + +// Suppress additional buffer overrun check. +// I have no idea why MSVC thinks some functions here are vulnerable to the buffer overrun +// attacks. No, they aren't. +#if defined(__GNUC__) || defined(__clang__) + #define JKJ_SAFEBUFFERS + #define JKJ_FORCEINLINE inline __attribute__((always_inline)) +#elif defined(_MSC_VER) + #define JKJ_SAFEBUFFERS __declspec(safebuffers) + #define JKJ_FORCEINLINE __forceinline +#else + #define JKJ_SAFEBUFFERS + #define JKJ_FORCEINLINE inline +#endif + +#if defined(__has_builtin) + #define JKJ_HAS_BUILTIN(x) __has_builtin(x) +#else + #define JKJ_HAS_BUILTIN(x) false +#endif + +#if defined(_MSC_VER) + #include +#elif defined(__INTEL_COMPILER) + #include +#endif + +namespace YAML { +namespace jkj { + namespace dragonbox { + //////////////////////////////////////////////////////////////////////////////////////// + // The Compatibility layer for toolchains without standard C++ headers. + //////////////////////////////////////////////////////////////////////////////////////// + namespace detail { + namespace stdr { + // +#if JKJ_HAS_BIT_CAST + using JKJ_STD_REPLACEMENT_NAMESPACE::bit_cast; +#endif + + // + // We need assert() macro, but it is not namespaced anyway, so nothing to do here. + + // + using JKJ_STD_REPLACEMENT_NAMESPACE::int_least8_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::int_least16_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::int_least32_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::int_fast8_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::int_fast16_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::int_fast32_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::uint_least8_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::uint_least16_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::uint_least32_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::uint_least64_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::uint_fast8_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::uint_fast16_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::uint_fast32_t; + // We need INT32_C, UINT32_C and UINT64_C macros too, but again there is nothing to do + // here. + + // + using JKJ_STD_REPLACEMENT_NAMESPACE::size_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::memcpy; + + // + template + using numeric_limits = JKJ_STD_REPLACEMENT_NAMESPACE::numeric_limits; + + // + template + using enable_if = JKJ_STD_REPLACEMENT_NAMESPACE::enable_if; + template + using add_rvalue_reference = JKJ_STD_REPLACEMENT_NAMESPACE::add_rvalue_reference; + template + using conditional = JKJ_STD_REPLACEMENT_NAMESPACE::conditional; +#if JKJ_USE_IS_CONSTANT_EVALUATED + using JKJ_STD_REPLACEMENT_NAMESPACE::is_constant_evaluated; +#endif + template + using is_same = JKJ_STD_REPLACEMENT_NAMESPACE::is_same; +#if !JKJ_HAS_BIT_CAST + template + using is_trivially_copyable = JKJ_STD_REPLACEMENT_NAMESPACE::is_trivially_copyable; +#endif + template + using is_integral = JKJ_STD_REPLACEMENT_NAMESPACE::is_integral; + template + using is_signed = JKJ_STD_REPLACEMENT_NAMESPACE::is_signed; + template + using is_unsigned = JKJ_STD_REPLACEMENT_NAMESPACE::is_unsigned; + } + } + + + //////////////////////////////////////////////////////////////////////////////////////// + // Some general utilities for C++11-compatibility. + //////////////////////////////////////////////////////////////////////////////////////// + namespace detail { +#if !JKJ_HAS_CONSTEXPR17 + template + struct index_sequence {}; + + template + struct make_index_sequence_impl { + using type = typename make_index_sequence_impl::type; + }; + + template + struct make_index_sequence_impl { + using type = index_sequence; + }; + + template + using make_index_sequence = typename make_index_sequence_impl<0, N, void>::type; +#endif + + // Available since C++11, but including just for this is an overkill. + template + typename stdr::add_rvalue_reference::type declval() noexcept; + + // Similarly, including is an overkill. + template + struct array { + T data_[N]; + constexpr T operator[](stdr::size_t idx) const noexcept { return data_[idx]; } + JKJ_CONSTEXPR14 T& operator[](stdr::size_t idx) noexcept { return data_[idx]; } + }; + } + + + //////////////////////////////////////////////////////////////////////////////////////// + // Some basic features for encoding/decoding IEEE-754 formats. + //////////////////////////////////////////////////////////////////////////////////////// + namespace detail { + template + struct physical_bits { + static constexpr stdr::size_t value = + sizeof(T) * stdr::numeric_limits::digits; + }; + template + struct value_bits { + static constexpr stdr::size_t value = stdr::numeric_limits< + typename stdr::enable_if::value, T>::type>::digits; + }; + + template + JKJ_CONSTEXPR20 To bit_cast(const From& from) { +#if JKJ_HAS_BIT_CAST + return stdr::bit_cast(from); +#else + static_assert(sizeof(From) == sizeof(To), ""); + static_assert(stdr::is_trivially_copyable::value, ""); + static_assert(stdr::is_trivially_copyable::value, ""); + To to; + stdr::memcpy(&to, &from, sizeof(To)); + return to; +#endif + } + } + + // These classes expose encoding specs of IEEE-754-like floating-point formats. + // Currently available formats are IEEE-754 binary32 & IEEE-754 binary64. + + struct ieee754_binary32 { + static constexpr int total_bits = 32; + static constexpr int significand_bits = 23; + static constexpr int exponent_bits = 8; + static constexpr int min_exponent = -126; + static constexpr int max_exponent = 127; + static constexpr int exponent_bias = -127; + static constexpr int decimal_significand_digits = 9; + static constexpr int decimal_exponent_digits = 2; + }; + struct ieee754_binary64 { + static constexpr int total_bits = 64; + static constexpr int significand_bits = 52; + static constexpr int exponent_bits = 11; + static constexpr int min_exponent = -1022; + static constexpr int max_exponent = 1023; + static constexpr int exponent_bias = -1023; + static constexpr int decimal_significand_digits = 17; + static constexpr int decimal_exponent_digits = 3; + }; + + // A floating-point format traits class defines ways to interpret a bit pattern of given size as + // an encoding of floating-point number. This is an implementation of such a traits class, + // supporting ways to interpret IEEE-754 binary floating-point numbers. + template + struct ieee754_binary_traits { + // CarrierUInt needs to have enough size to hold the entire contents of floating-point + // numbers. The actual bits are assumed to be aligned to the LSB, and every other bits are + // assumed to be zeroed. + static_assert(detail::value_bits::value >= Format::total_bits, + "jkj::dragonbox: insufficient number of bits"); + static_assert(detail::stdr::is_unsigned::value, ""); + + // ExponentUInt needs to be large enough to hold (unsigned) exponent bits as well as the + // (signed) actual exponent. + // TODO: static overflow guard against intermediate computations. + static_assert(detail::value_bits::value >= Format::exponent_bits + 1, + "jkj::dragonbox: insufficient number of bits"); + static_assert(detail::stdr::is_signed::value, ""); + + using format = Format; + using carrier_uint = CarrierUInt; + static constexpr int carrier_bits = int(detail::value_bits::value); + using exponent_int = ExponentInt; + + // Extract exponent bits from a bit pattern. + // The result must be aligned to the LSB so that there is no additional zero paddings + // on the right. This function does not do bias adjustment. + static constexpr exponent_int extract_exponent_bits(carrier_uint u) noexcept { + return exponent_int((u >> format::significand_bits) & + ((exponent_int(1) << format::exponent_bits) - 1)); + } + + // Extract significand bits from a bit pattern. + // The result must be aligned to the LSB so that there is no additional zero paddings + // on the right. The result does not contain the implicit bit. + static constexpr carrier_uint extract_significand_bits(carrier_uint u) noexcept { + return carrier_uint(u & ((carrier_uint(1) << format::significand_bits) - 1u)); + } + + // Remove the exponent bits and extract significand bits together with the sign bit. + static constexpr carrier_uint remove_exponent_bits(carrier_uint u) noexcept { + return carrier_uint(u & ~(((carrier_uint(1) << format::exponent_bits) - 1u) + << format::significand_bits)); + } + + // Shift the obtained signed significand bits to the left by 1 to remove the sign bit. + static constexpr carrier_uint remove_sign_bit_and_shift(carrier_uint u) noexcept { + return carrier_uint((carrier_uint(u) << 1) & + ((((carrier_uint(1) << (Format::total_bits - 1)) - 1u) << 1) | 1u)); + } + + // Obtain the actual value of the binary exponent from the extracted exponent bits. + static constexpr exponent_int binary_exponent(exponent_int exponent_bits) noexcept { + return exponent_int(exponent_bits == 0 ? format::min_exponent + : exponent_bits + format::exponent_bias); + } + + // Obtain the actual value of the binary significand from the extracted significand bits + // and exponent bits. + static constexpr carrier_uint binary_significand(carrier_uint significand_bits, + exponent_int exponent_bits) noexcept { + return carrier_uint( + exponent_bits == 0 + ? significand_bits + : (significand_bits | (carrier_uint(1) << format::significand_bits))); + } + + /* Various boolean observer functions */ + + static constexpr bool is_nonzero(carrier_uint u) noexcept { + return (u & ((carrier_uint(1) << (format::significand_bits + format::exponent_bits)) - + 1u)) != 0; + } + static constexpr bool is_positive(carrier_uint u) noexcept { + return u < (carrier_uint(1) << (format::significand_bits + format::exponent_bits)); + } + static constexpr bool is_negative(carrier_uint u) noexcept { return !is_positive(u); } + static constexpr bool is_finite(exponent_int exponent_bits) noexcept { + return exponent_bits != ((exponent_int(1) << format::exponent_bits) - 1); + } + static constexpr bool has_all_zero_significand_bits(carrier_uint u) noexcept { + return ((u << 1) & + ((((carrier_uint(1) << (Format::total_bits - 1)) - 1u) << 1) | 1u)) == 0; + } + static constexpr bool has_even_significand_bits(carrier_uint u) noexcept { + return u % 2 == 0; + } + }; + + // Convert between bit patterns stored in carrier_uint and instances of an actual + // floating-point type. Depending on format and carrier_uint, this operation might not + // be possible for some specific bit patterns. However, the contract is that u always + // denotes a valid bit pattern, so the functions here are assumed to be noexcept. + // Users might specialize this class to change the behavior for certain types. + // The default provided by the library is to treat the given floating-point type Float as either + // IEEE-754 binary32 or IEEE-754 binary64, depending on the bitwise size of Float. + template + struct default_float_bit_carrier_conversion_traits { + // Guards against types that have different internal representations than IEEE-754 + // binary32/64. I don't know if there is a truly reliable way of detecting IEEE-754 binary + // formats. I just did my best here. Note that in some cases + // numeric_limits::is_iec559 may report false even if the internal representation is + // IEEE-754 compatible. In such a case, the user can specialize this traits template and + // remove this static sanity check in order to make Dragonbox work for Float. + static_assert(detail::stdr::numeric_limits::is_iec559 && + detail::stdr::numeric_limits::radix == 2 && + (detail::physical_bits::value == 32 || + detail::physical_bits::value == 64), + "jkj::dragonbox: Float may not be of IEEE-754 binary32/binary64"); + + // Specifies the unsigned integer type to hold bitwise value of Float. + using carrier_uint = + typename detail::stdr::conditional::value == 32, + detail::stdr::uint_least32_t, + detail::stdr::uint_least64_t>::type; + + // Specifies the floating-point format. + using format = typename detail::stdr::conditional::value == 32, + ieee754_binary32, ieee754_binary64>::type; + + // Converts the floating-point type into the bit-carrier unsigned integer type. + static JKJ_CONSTEXPR20 carrier_uint float_to_carrier(Float x) noexcept { + return detail::bit_cast(x); + } + + // Converts the bit-carrier unsigned integer type into the floating-point type. + static JKJ_CONSTEXPR20 Float carrier_to_float(carrier_uint x) noexcept { + return detail::bit_cast(x); + } + }; + + // Convenient wrappers for floating-point traits classes. + // In order to reduce the argument passing overhead, these classes should be as simple as + // possible (e.g., no inheritance, no private non-static data member, etc.; this is an + // unfortunate fact about common ABI convention). + + template + struct signed_significand_bits { + using format_traits = FormatTraits; + using carrier_uint = typename format_traits::carrier_uint; + + carrier_uint u; + + signed_significand_bits() = default; + constexpr explicit signed_significand_bits(carrier_uint bit_pattern) noexcept + : u{bit_pattern} {} + + // Shift the obtained signed significand bits to the left by 1 to remove the sign bit. + constexpr carrier_uint remove_sign_bit_and_shift() const noexcept { + return format_traits::remove_sign_bit_and_shift(u); + } + + constexpr bool is_positive() const noexcept { return format_traits::is_positive(u); } + constexpr bool is_negative() const noexcept { return format_traits::is_negative(u); } + constexpr bool has_all_zero_significand_bits() const noexcept { + return format_traits::has_all_zero_significand_bits(u); + } + constexpr bool has_even_significand_bits() const noexcept { + return format_traits::has_even_significand_bits(u); + } + }; + + template + struct float_bits { + using format_traits = FormatTraits; + using carrier_uint = typename format_traits::carrier_uint; + using exponent_int = typename format_traits::exponent_int; + + carrier_uint u; + + float_bits() = default; + constexpr explicit float_bits(carrier_uint bit_pattern) noexcept : u{bit_pattern} {} + + // Extract exponent bits from a bit pattern. + // The result must be aligned to the LSB so that there is no additional zero paddings + // on the right. This function does not do bias adjustment. + constexpr exponent_int extract_exponent_bits() const noexcept { + return format_traits::extract_exponent_bits(u); + } + + // Extract significand bits from a bit pattern. + // The result must be aligned to the LSB so that there is no additional zero paddings + // on the right. The result does not contain the implicit bit. + constexpr carrier_uint extract_significand_bits() const noexcept { + return format_traits::extract_significand_bits(u); + } + + // Remove the exponent bits and extract significand bits together with the sign bit. + constexpr signed_significand_bits remove_exponent_bits() const noexcept { + return signed_significand_bits(format_traits::remove_exponent_bits(u)); + } + + // Obtain the actual value of the binary exponent from the extracted exponent bits. + static constexpr exponent_int binary_exponent(exponent_int exponent_bits) noexcept { + return format_traits::binary_exponent(exponent_bits); + } + constexpr exponent_int binary_exponent() const noexcept { + return binary_exponent(extract_exponent_bits()); + } + + // Obtain the actual value of the binary exponent from the extracted significand bits + // and exponent bits. + static constexpr carrier_uint binary_significand(carrier_uint significand_bits, + exponent_int exponent_bits) noexcept { + return format_traits::binary_significand(significand_bits, exponent_bits); + } + constexpr carrier_uint binary_significand() const noexcept { + return binary_significand(extract_significand_bits(), extract_exponent_bits()); + } + + constexpr bool is_nonzero() const noexcept { return format_traits::is_nonzero(u); } + constexpr bool is_positive() const noexcept { return format_traits::is_positive(u); } + constexpr bool is_negative() const noexcept { return format_traits::is_negative(u); } + constexpr bool is_finite(exponent_int exponent_bits) const noexcept { + return format_traits::is_finite(exponent_bits); + } + constexpr bool is_finite() const noexcept { + return format_traits::is_finite(extract_exponent_bits()); + } + constexpr bool has_even_significand_bits() const noexcept { + return format_traits::has_even_significand_bits(u); + } + }; + + template , + class FormatTraits = ieee754_binary_traits> + JKJ_CONSTEXPR20 float_bits make_float_bits(Float x) noexcept { + return float_bits(ConversionTraits::float_to_carrier(x)); + } + + namespace detail { + //////////////////////////////////////////////////////////////////////////////////////// + // Bit operation intrinsics. + //////////////////////////////////////////////////////////////////////////////////////// + + namespace bits { + // Most compilers should be able to optimize this into the ROR instruction. + // n is assumed to be at most of bit_width bits. + template + JKJ_CONSTEXPR14 UInt rotr(UInt n, unsigned int r) noexcept { + static_assert(bit_width > 0, "jkj::dragonbox: rotation bit-width must be positive"); + static_assert(bit_width <= value_bits::value, + "jkj::dragonbox: rotation bit-width is too large"); + r &= (bit_width - 1); + return (n >> r) | (n << ((bit_width - r) & (bit_width - 1))); + } + } + + //////////////////////////////////////////////////////////////////////////////////////// + // Utilities for wide unsigned integer arithmetic. + //////////////////////////////////////////////////////////////////////////////////////// + + namespace wuint { + // Compilers might support built-in 128-bit integer types. However, it seems that + // emulating them with a pair of 64-bit integers actually produces a better code, + // so we avoid using those built-ins. That said, they are still useful for + // implementing 64-bit x 64-bit -> 128-bit multiplication. + + // clang-format off +#if defined(__SIZEOF_INT128__) + // To silence "error: ISO C++ does not support '__int128' for 'type name' + // [-Wpedantic]" +#if defined(__GNUC__) + __extension__ +#endif + using builtin_uint128_t = unsigned __int128; +#endif + // clang-format on + + struct uint128 { + uint128() = default; + + stdr::uint_least64_t high_; + stdr::uint_least64_t low_; + + constexpr uint128(stdr::uint_least64_t high, stdr::uint_least64_t low) noexcept + : high_{high}, low_{low} {} + + constexpr stdr::uint_least64_t high() const noexcept { return high_; } + constexpr stdr::uint_least64_t low() const noexcept { return low_; } + + JKJ_CONSTEXPR20 uint128& operator+=(stdr::uint_least64_t n) & noexcept { + auto const generic_impl = [&] { + auto const sum = (low_ + n) & UINT64_C(0xffffffffffffffff); + high_ += (sum < low_ ? 1 : 0); + low_ = sum; + }; + // To suppress warning. + static_cast(generic_impl); + + JKJ_IF_CONSTEXPR(value_bits::value > 64) { + generic_impl(); + return *this; + } + + JKJ_IF_CONSTEVAL { + generic_impl(); + return *this; + } + + // See https://github.com/fmtlib/fmt/pull/2985. +#if JKJ_HAS_BUILTIN(__builtin_addcll) && !defined(__ibmxl__) + JKJ_IF_CONSTEXPR( + stdr::is_same::value) { + unsigned long long carry{}; + low_ = stdr::uint_least64_t(__builtin_addcll(low_, n, 0, &carry)); + high_ = stdr::uint_least64_t(__builtin_addcll(high_, 0, carry, &carry)); + return *this; + } +#endif +#if JKJ_HAS_BUILTIN(__builtin_addcl) && !defined(__ibmxl__) + JKJ_IF_CONSTEXPR(stdr::is_same::value) { + unsigned long carry{}; + low_ = stdr::uint_least64_t( + __builtin_addcl(static_cast(low_), + static_cast(n), 0, &carry)); + high_ = stdr::uint_least64_t( + __builtin_addcl(static_cast(high_), 0, carry, &carry)); + return *this; + } +#endif +#if JKJ_HAS_BUILTIN(__builtin_addc) && !defined(__ibmxl__) + JKJ_IF_CONSTEXPR(stdr::is_same::value) { + unsigned int carry{}; + low_ = stdr::uint_least64_t(__builtin_addc(static_cast(low_), + static_cast(n), 0, + &carry)); + high_ = stdr::uint_least64_t( + __builtin_addc(static_cast(high_), 0, carry, &carry)); + return *this; + } +#endif + +#if JKJ_HAS_BUILTIN(__builtin_ia32_addcarry_u64) + // __builtin_ia32_addcarry_u64 is not documented, but it seems it takes unsigned + // long long arguments. + unsigned long long result{}; + auto const carry = __builtin_ia32_addcarry_u64(0, low_, n, &result); + low_ = stdr::uint_least64_t(result); + __builtin_ia32_addcarry_u64(carry, high_, 0, &result); + high_ = stdr::uint_least64_t(result); +#elif defined(_MSC_VER) && defined(_M_X64) + // On MSVC, uint_least64_t and __int64 must be unsigned long long; see + // https://learn.microsoft.com/en-us/cpp/c-runtime-library/standard-types + // and https://learn.microsoft.com/en-us/cpp/cpp/int8-int16-int32-int64. + static_assert(stdr::is_same::value, + ""); + auto const carry = _addcarry_u64(0, low_, n, &low_); + _addcarry_u64(carry, high_, 0, &high_); +#elif defined(__INTEL_COMPILER) && (defined(_M_X64) || defined(__x86_64)) + // Cannot find any documentation on how things are defined, but hopefully this + // is always true... + static_assert(stdr::is_same::value, ""); + auto const carry = _addcarry_u64(0, low_, n, &low_); + _addcarry_u64(carry, high_, 0, &high_); +#else + generic_impl(); +#endif + return *this; + } + }; + + inline JKJ_CONSTEXPR20 stdr::uint_least64_t umul64(stdr::uint_least32_t x, + stdr::uint_least32_t y) noexcept { +#if defined(_MSC_VER) && defined(_M_IX86) + JKJ_IF_NOT_CONSTEVAL { return __emulu(x, y); } +#endif + return x * stdr::uint_least64_t(y); + } + + // Get 128-bit result of multiplication of two 64-bit unsigned integers. + JKJ_SAFEBUFFERS inline JKJ_CONSTEXPR20 uint128 + umul128(stdr::uint_least64_t x, stdr::uint_least64_t y) noexcept { + auto const generic_impl = [=]() -> uint128 { + auto const a = stdr::uint_least32_t(x >> 32); + auto const b = stdr::uint_least32_t(x); + auto const c = stdr::uint_least32_t(y >> 32); + auto const d = stdr::uint_least32_t(y); + + auto const ac = umul64(a, c); + auto const bc = umul64(b, c); + auto const ad = umul64(a, d); + auto const bd = umul64(b, d); + + auto const intermediate = + (bd >> 32) + stdr::uint_least32_t(ad) + stdr::uint_least32_t(bc); + + return {ac + (intermediate >> 32) + (ad >> 32) + (bc >> 32), + (intermediate << 32) + stdr::uint_least32_t(bd)}; + }; + // To silence warning. + static_cast(generic_impl); + +#if defined(__SIZEOF_INT128__) + auto const result = builtin_uint128_t(x) * builtin_uint128_t(y); + return {stdr::uint_least64_t(result >> 64), stdr::uint_least64_t(result)}; +#elif defined(_MSC_VER) && defined(_M_X64) + JKJ_IF_CONSTEVAL { + // This redundant variable is to workaround MSVC's codegen bug caused by the + // interaction of NRVO and intrinsics. + auto const result = generic_impl(); + return result; + } + uint128 result; + #if defined(__AVX2__) + result.low_ = _mulx_u64(x, y, &result.high_); + #else + result.low_ = _umul128(x, y, &result.high_); + #endif + return result; +#else + return generic_impl(); +#endif + } + + // Get high half of the 128-bit result of multiplication of two 64-bit unsigned + // integers. + JKJ_SAFEBUFFERS inline JKJ_CONSTEXPR20 stdr::uint_least64_t + umul128_upper64(stdr::uint_least64_t x, stdr::uint_least64_t y) noexcept { + auto const generic_impl = [=]() -> stdr::uint_least64_t { + auto const a = stdr::uint_least32_t(x >> 32); + auto const b = stdr::uint_least32_t(x); + auto const c = stdr::uint_least32_t(y >> 32); + auto const d = stdr::uint_least32_t(y); + + auto const ac = umul64(a, c); + auto const bc = umul64(b, c); + auto const ad = umul64(a, d); + auto const bd = umul64(b, d); + + auto const intermediate = + (bd >> 32) + stdr::uint_least32_t(ad) + stdr::uint_least32_t(bc); + + return ac + (intermediate >> 32) + (ad >> 32) + (bc >> 32); + }; + // To silence warning. + static_cast(generic_impl); + +#if defined(__SIZEOF_INT128__) + auto const result = builtin_uint128_t(x) * builtin_uint128_t(y); + return stdr::uint_least64_t(result >> 64); +#elif defined(_MSC_VER) && defined(_M_X64) + JKJ_IF_CONSTEVAL { + // This redundant variable is to workaround MSVC's codegen bug caused by the + // interaction of NRVO and intrinsics. + auto const result = generic_impl(); + return result; + } + stdr::uint_least64_t result; + #if defined(__AVX2__) + _mulx_u64(x, y, &result); + #else + result = __umulh(x, y); + #endif + return result; +#else + return generic_impl(); +#endif + } + + // Get upper 128-bits of multiplication of a 64-bit unsigned integer and a 128-bit + // unsigned integer. + JKJ_SAFEBUFFERS inline JKJ_CONSTEXPR20 uint128 umul192_upper128(stdr::uint_least64_t x, + uint128 y) noexcept { + auto r = umul128(x, y.high()); + r += umul128_upper64(x, y.low()); + return r; + } + + // Get upper 64-bits of multiplication of a 32-bit unsigned integer and a 64-bit + // unsigned integer. + inline JKJ_CONSTEXPR20 stdr::uint_least64_t + umul96_upper64(stdr::uint_least32_t x, stdr::uint_least64_t y) noexcept { +#if defined(__SIZEOF_INT128__) || (defined(_MSC_VER) && defined(_M_X64)) + return umul128_upper64(stdr::uint_least64_t(x) << 32, y); +#else + auto const yh = stdr::uint_least32_t(y >> 32); + auto const yl = stdr::uint_least32_t(y); + + auto const xyh = umul64(x, yh); + auto const xyl = umul64(x, yl); + + return xyh + (xyl >> 32); +#endif + } + + // Get lower 128-bits of multiplication of a 64-bit unsigned integer and a 128-bit + // unsigned integer. + JKJ_SAFEBUFFERS inline JKJ_CONSTEXPR20 uint128 umul192_lower128(stdr::uint_least64_t x, + uint128 y) noexcept { + auto const high = x * y.high(); + auto const high_low = umul128(x, y.low()); + return {(high + high_low.high()) & UINT64_C(0xffffffffffffffff), high_low.low()}; + } + + // Get lower 64-bits of multiplication of a 32-bit unsigned integer and a 64-bit + // unsigned integer. + constexpr stdr::uint_least64_t umul96_lower64(stdr::uint_least32_t x, + stdr::uint_least64_t y) noexcept { + return (x * y) & UINT64_C(0xffffffffffffffff); + } + } + + //////////////////////////////////////////////////////////////////////////////////////// + // Some simple utilities for constexpr computation. + //////////////////////////////////////////////////////////////////////////////////////// + + template + constexpr Int compute_power(Int a) noexcept { + static_assert(k >= 0, ""); +#if JKJ_HAS_CONSTEXPR14 + Int p = 1; + for (int i = 0; i < k; ++i) { + p *= a; + } + return p; +#else + return k == 0 ? 1 + : k % 2 == 0 ? compute_power(a * a) + : a * compute_power(a * a); +#endif + } + + template + constexpr int count_factors(UInt n) noexcept { + static_assert(a > 1, ""); +#if JKJ_HAS_CONSTEXPR14 + int c = 0; + while (n % a == 0) { + n /= a; + ++c; + } + return c; +#else + return n % a == 0 ? count_factors(n / a) + 1 : 0; +#endif + } + + //////////////////////////////////////////////////////////////////////////////////////// + // Utilities for fast/constexpr log computation. + //////////////////////////////////////////////////////////////////////////////////////// + + namespace log { + static_assert((stdr::int_fast32_t(-1) >> 1) == stdr::int_fast32_t(-1) && + (stdr::int_fast16_t(-1) >> 1) == stdr::int_fast16_t(-1), + "jkj::dragonbox: right-shift for signed integers must be arithmetic"); + + // For constexpr computation. + // Returns -1 when n = 0. + template + constexpr int floor_log2(UInt n) noexcept { +#if JKJ_HAS_CONSTEXPR14 + int count = -1; + while (n != 0) { + ++count; + n >>= 1; + } + return count; +#else + return n == 0 ? -1 : floor_log2(n / 2) + 1; +#endif + } + + template