From 8308199a0a837bfeaf5e9e35844a3cea03517dce Mon Sep 17 00:00:00 2001 From: sndth <75499293+sndth@users.noreply.github.com> Date: Wed, 4 Mar 2026 23:46:21 +0100 Subject: [PATCH 1/4] Support unsigned 64-bit integers (uint64_t) exceeding INT64_MAX Large positive decimal scalars whose value exceeds INT64_MAX (e.g. xxHash digests, high-half UUID fields) were silently parsed as STRING nodes instead of INTEGER nodes. Calling as_int() on such a node then threw type_error, making the value unreachable without ugly string post-processing. --- .../fkYAML/detail/conversions/from_node.hpp | 5 + include/fkYAML/detail/conversions/to_node.hpp | 14 +- include/fkYAML/detail/input/scalar_parser.hpp | 19 + include/fkYAML/detail/node_attrs.hpp | 5 + include/fkYAML/node.hpp | 50 +- single_include/fkYAML/node.hpp | 5610 +++++++++-------- tests/unit_test/CMakeLists.txt | 3 + tests/unit_test/test_node_attrs_uint.cpp | 61 + tests/unit_test/test_node_class_uint.cpp | 139 + .../test_scalar_parser_class_uint.cpp | 66 + 10 files changed, 3210 insertions(+), 2762 deletions(-) create mode 100644 tests/unit_test/test_node_attrs_uint.cpp create mode 100644 tests/unit_test/test_node_class_uint.cpp create mode 100644 tests/unit_test/test_scalar_parser_class_uint.cpp diff --git a/include/fkYAML/detail/conversions/from_node.hpp b/include/fkYAML/detail/conversions/from_node.hpp index d4555b60..a8378b15 100644 --- a/include/fkYAML/detail/conversions/from_node.hpp +++ b/include/fkYAML/detail/conversions/from_node.hpp @@ -376,6 +376,11 @@ struct from_node_int_helper { // under/overflow check. if (std::is_same::value) { + // Nodes marked with uint_bit store a uint64_t bit pattern in a signed field. + // Recover the original unsigned value without a sign check. + if (n.is_uint()) { + return static_cast(tmp_int); + } if FK_YAML_UNLIKELY (tmp_int < 0) { throw exception("Integer value underflow detected."); } diff --git a/include/fkYAML/detail/conversions/to_node.hpp b/include/fkYAML/detail/conversions/to_node.hpp index bdc06c1b..5af0ef21 100644 --- a/include/fkYAML/detail/conversions/to_node.hpp +++ b/include/fkYAML/detail/conversions/to_node.hpp @@ -65,6 +65,16 @@ struct external_node_constructor { n.m_value.integer = i; } + /// @brief Constructs an INTEGER node from a uint64_t value that exceeds the signed range. + /// The raw bit pattern is stored in the integer field and the uint_bit flag is set so that + /// get_value() / as_uint() can recover the original unsigned value. + static void unsigned_integer_scalar(BasicNodeType& n, const typename BasicNodeType::integer_type i) { + destroy(n); + n.m_attrs |= node_attr_bits::int_bit; + n.m_attrs |= node_attr_bits::uint_bit; + n.m_value.integer = i; + } + static void float_scalar(BasicNodeType& n, const typename BasicNodeType::float_number_type f) { destroy(n); n.m_attrs |= node_attr_bits::float_bit; @@ -81,7 +91,9 @@ struct external_node_constructor { private: static void destroy(BasicNodeType& n) { n.m_value.destroy(n.m_attrs & node_attr_mask::value); - n.m_attrs &= ~node_attr_mask::value; + // Clear both the value-type bits and the uint_bit style flag so that any + // subsequent reassignment starts from a clean state. + n.m_attrs &= ~(node_attr_mask::value | node_attr_bits::uint_bit); } }; diff --git a/include/fkYAML/detail/input/scalar_parser.hpp b/include/fkYAML/detail/input/scalar_parser.hpp index 06e761b4..adac874d 100644 --- a/include/fkYAML/detail/input/scalar_parser.hpp +++ b/include/fkYAML/detail/input/scalar_parser.hpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -534,6 +535,24 @@ class scalar_parser { if FK_YAML_LIKELY (converted) { return basic_node_type(integer); } + + // For untagged plain integer scalars, attempt a uint64_t parse to handle large + // positive values that exceed int64_t max (e.g. xxHash/UUID results like + // 15745692345339290292). This only applies when integer_type is a signed 64-bit + // type; any other width would not be able to represent the value anyway. + if (tag_type != tag_t::INTEGER && std::is_signed::value && + sizeof(integer_type) == sizeof(uint64_t)) { + uint64_t u64 = 0; + if (detail::atoi(token.begin(), token.end(), u64)) { + basic_node_type node; + // Store the bit pattern in the signed field and set uint_bit so that + // as_uint() / get_value() can recover the correct value. + detail::external_node_constructor::unsigned_integer_scalar( + node, static_cast(u64)); + return node; + } + } + if FK_YAML_UNLIKELY (tag_type == tag_t::INTEGER) { throw parse_error("Failed to convert a scalar to an integer.", m_line, m_indent); } diff --git a/include/fkYAML/detail/node_attrs.hpp b/include/fkYAML/detail/node_attrs.hpp index ddf84ed2..ecc70aba 100644 --- a/include/fkYAML/detail/node_attrs.hpp +++ b/include/fkYAML/detail/node_attrs.hpp @@ -59,6 +59,11 @@ constexpr node_attr_t string_bit = 1u << 6; /// A utility bit set to filter scalar node bits. constexpr node_attr_t scalar_bits = null_bit | bool_bit | int_bit | float_bit | string_bit; +/// The unsigned integer flag bit. +/// Set on INTEGER nodes whose stored int64_t value represents a uint64_t that exceeds INT64_MAX. +/// This allows values such as xxHash/UUID results to round-trip correctly through get_value(). +constexpr node_attr_t uint_bit = 1u << 16; // lives in the style bits area (0x00FF0000) + /// The anchor node bit. constexpr node_attr_t anchor_bit = 0x01000000u; /// The alias node bit. diff --git a/include/fkYAML/node.hpp b/include/fkYAML/node.hpp index c5e7a3d5..43d70034 100644 --- a/include/fkYAML/node.hpp +++ b/include/fkYAML/node.hpp @@ -1472,6 +1472,36 @@ class basic_node { throw fkyaml::type_error("The node value is not a boolean.", get_type()); } + /// @brief Checks if the node is an integer that was parsed from a uint64_t value exceeding INT64_MAX. + /// @return true if the node holds an unsigned integer, false otherwise. + bool is_uint() const noexcept { + return resolve_reference().is_uint_impl(); + } + + /// @brief Returns the integer node value as an unsigned 64-bit integer. + /// This is valid both for nodes where integer_type is unsigned and for nodes where a large + /// positive decimal scalar (> INT64_MAX) was stored with the uint_bit flag set. + /// @throw fkyaml::type_error if the node is not a compatible integer. + /// @return The node value as uint64_t. + uint64_t as_uint() const { + const basic_node& act_node = resolve_reference(); + if FK_YAML_LIKELY (act_node.is_integer_impl()) { + // When integer_type is unsigned the stored value IS the uint64_t directly. + if (std::is_unsigned::value) { + return static_cast(act_node.m_value.integer); + } + // When integer_type is signed, only uint_bit-marked nodes carry a uint64_t. + if (act_node.m_attrs & detail::node_attr_bits::uint_bit) { + return static_cast(act_node.m_value.integer); + } + // Signed values in the non-negative range can be returned safely. + if (act_node.m_value.integer >= static_cast(0)) { + return static_cast(act_node.m_value.integer); + } + } + throw fkyaml::type_error("The node value cannot be represented as an unsigned integer.", get_type()); + } + /// @brief Returns reference to the integer node value. /// @throw fkyaml::type_error The node value is not an integer. /// @return Reference to the integer node value. @@ -1479,18 +1509,30 @@ class basic_node { integer_type& as_int() { basic_node& act_node = resolve_reference(); if FK_YAML_LIKELY (act_node.is_integer_impl()) { + if FK_YAML_UNLIKELY (act_node.is_uint_impl()) { + throw fkyaml::type_error( + "The integer value exceeds INT64_MAX and cannot be returned as a signed integer. " + "Use as_uint() instead.", + get_type()); + } return act_node.m_value.integer; } throw fkyaml::type_error("The node value is not an integer.", get_type()); } /// @brief Returns reference to the integer node value. - /// @throw fkyaml::type_error The node value is not an integer. + /// @throw fkyaml::type_error The node value is not an integer, or exceeds INT64_MAX. /// @return Constant reference to the integer node value. /// @sa https://fktn-k.github.io/fkYAML/api/basic_node/as_int/ const integer_type& as_int() const { const basic_node& act_node = resolve_reference(); if FK_YAML_LIKELY (act_node.is_integer_impl()) { + if FK_YAML_UNLIKELY (act_node.is_uint_impl()) { + throw fkyaml::type_error( + "The integer value exceeds INT64_MAX and cannot be returned as a signed integer. " + "Use as_uint() instead.", + get_type()); + } return act_node.m_value.integer; } throw fkyaml::type_error("The node value is not an integer.", get_type()); @@ -1768,6 +1810,12 @@ class basic_node { return m_attrs & detail::node_attr_bits::int_bit; } + bool is_uint_impl() const noexcept { + // Both int_bit and uint_bit must be set: this node stores a uint64_t value + // whose bit pattern was placed into the signed integer_type field. + return (m_attrs & detail::node_attr_bits::int_bit) && (m_attrs & detail::node_attr_bits::uint_bit); + } + bool is_float_number_impl() const noexcept { return m_attrs & detail::node_attr_bits::float_bit; } diff --git a/single_include/fkYAML/node.hpp b/single_include/fkYAML/node.hpp index 05e79572..b931068c 100644 --- a/single_include/fkYAML/node.hpp +++ b/single_include/fkYAML/node.hpp @@ -5356,7 +5356,7 @@ FK_YAML_DETAIL_NAMESPACE_END #endif /* FK_YAML_CONVERSIONS_SCALAR_CONV_HPP */ -// #include +// #include // _______ __ __ __ _____ __ __ __ // | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library // | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 @@ -5365,178 +5365,656 @@ FK_YAML_DETAIL_NAMESPACE_END // SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani // SPDX-License-Identifier: MIT -#ifndef FK_YAML_DETAIL_ENCODINGS_YAML_ESCAPER_HPP -#define FK_YAML_DETAIL_ENCODINGS_YAML_ESCAPER_HPP +#ifndef FK_YAML_DETAIL_CONVERSIONS_TO_NODE_HPP +#define FK_YAML_DETAIL_CONVERSIONS_TO_NODE_HPP -#include +#include // #include -// #include +// #include -// #include +// #include -// #include +// #include + +// #include + +// #include +// _______ __ __ __ _____ __ __ __ +// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library +// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 +// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML +// +// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani +// SPDX-License-Identifier: MIT + +#ifndef FK_YAML_DETAIL_NODE_ATTRS_HPP +#define FK_YAML_DETAIL_NODE_ATTRS_HPP + +#include +#include + +// #include + +// #include FK_YAML_DETAIL_NAMESPACE_BEGIN -class yaml_escaper { - using iterator = ::std::string::const_iterator; +/// @brief The type for node attribute bits. +using node_attr_t = uint32_t; -public: - static bool unescape(const char*& begin, const char* end, std::string& buff) { - FK_YAML_ASSERT(*begin == '\\' && std::distance(begin, end) > 0); - bool ret = true; +/// @brief The namespace to define bit masks for node attribute bits. +namespace node_attr_mask { - switch (*++begin) { - case 'a': - buff.push_back('\a'); - break; - case 'b': - buff.push_back('\b'); - break; - case 't': - case '\t': - buff.push_back('\t'); - break; - case 'n': - buff.push_back('\n'); - break; - case 'v': - buff.push_back('\v'); - break; - case 'f': - buff.push_back('\f'); - break; - case 'r': - buff.push_back('\r'); - break; - case 'e': - buff.push_back(static_cast(0x1B)); - break; - case ' ': - buff.push_back(' '); - break; - case '\"': - buff.push_back('\"'); - break; - case '/': - buff.push_back('/'); - break; - case '\\': - buff.push_back('\\'); - break; - case 'N': // next line - unescape_escaped_unicode(0x85u, buff); - break; - case '_': // non-breaking space - unescape_escaped_unicode(0xA0u, buff); - break; - case 'L': // line separator - unescape_escaped_unicode(0x2028u, buff); - break; - case 'P': // paragraph separator - unescape_escaped_unicode(0x2029u, buff); - break; - case 'x': { - char32_t codepoint {0}; - ret = extract_codepoint(begin, end, 1, codepoint); - if FK_YAML_LIKELY (ret) { - unescape_escaped_unicode(codepoint, buff); - } - break; - } - case 'u': { - char32_t codepoint {0}; - ret = extract_codepoint(begin, end, 2, codepoint); - if FK_YAML_LIKELY (ret) { - unescape_escaped_unicode(codepoint, buff); - } - break; - } - case 'U': { - char32_t codepoint {0}; - ret = extract_codepoint(begin, end, 4, codepoint); - if FK_YAML_LIKELY (ret) { - unescape_escaped_unicode(codepoint, buff); - } - break; - } - default: - // Unsupported escape sequence is found in a string token. - ret = false; - break; - } +/// The bit mask for node value type bits. +constexpr node_attr_t value = 0x0000FFFFu; +/// The bit mask for node style type bits. (bits are not yet defined.) +constexpr node_attr_t style = 0x00FF0000u; +/// The bit mask for node property related bits. +constexpr node_attr_t props = 0xFF000000u; +/// The bit mask for anchor/alias node type bits. +constexpr node_attr_t anchoring = 0x03000000u; +/// The bit mask for anchor offset value bits. +constexpr node_attr_t anchor_offset = 0xFC000000u; +/// The bit mask for all the bits for node attributes. +constexpr node_attr_t all = std::numeric_limits::max(); - return ret; +} // namespace node_attr_mask + +/// @brief The namespace to define bits for node attributes. +namespace node_attr_bits { + +/// The sequence node bit. +constexpr node_attr_t seq_bit = 1u << 0; +/// The mapping node bit. +constexpr node_attr_t map_bit = 1u << 1; +/// The null scalar node bit. +constexpr node_attr_t null_bit = 1u << 2; +/// The boolean scalar node bit. +constexpr node_attr_t bool_bit = 1u << 3; +/// The integer scalar node bit. +constexpr node_attr_t int_bit = 1u << 4; +/// The floating point scalar node bit. +constexpr node_attr_t float_bit = 1u << 5; +/// The string scalar node bit. +constexpr node_attr_t string_bit = 1u << 6; + +/// A utility bit set to filter scalar node bits. +constexpr node_attr_t scalar_bits = null_bit | bool_bit | int_bit | float_bit | string_bit; + +/// The unsigned integer flag bit. +/// Set on INTEGER nodes whose stored int64_t value represents a uint64_t that exceeds INT64_MAX. +/// This allows values such as xxHash/UUID results to round-trip correctly through get_value(). +constexpr node_attr_t uint_bit = 1u << 16; // lives in the style bits area (0x00FF0000) + +/// The anchor node bit. +constexpr node_attr_t anchor_bit = 0x01000000u; +/// The alias node bit. +constexpr node_attr_t alias_bit = 0x02000000u; + +/// A utility bit set for initialization. +constexpr node_attr_t default_bits = null_bit; + +/// @brief Converts a node_type value to a node_attr_t value. +/// @param t A type of node value. +/// @return The associated node value bit. +inline node_attr_t from_node_type(node_type t) noexcept { + switch (t) { + case node_type::SEQUENCE: + return seq_bit; + case node_type::MAPPING: + return map_bit; + case node_type::NULL_OBJECT: + return null_bit; + case node_type::BOOLEAN: + return bool_bit; + case node_type::INTEGER: + return int_bit; + case node_type::FLOAT: + return float_bit; + case node_type::STRING: + return string_bit; + default: // LCOV_EXCL_LINE + return node_attr_mask::all; // LCOV_EXCL_LINE } +} - static ::std::string escape(const char* begin, const char* end, bool& is_escaped) { - ::std::string escaped {}; - escaped.reserve(std::distance(begin, end)); - for (; begin != end; ++begin) { - switch (*begin) { - case 0x01: - escaped += "\\u0001"; - is_escaped = true; - break; - case 0x02: - escaped += "\\u0002"; - is_escaped = true; - break; - case 0x03: - escaped += "\\u0003"; - is_escaped = true; - break; - case 0x04: - escaped += "\\u0004"; - is_escaped = true; - break; - case 0x05: - escaped += "\\u0005"; - is_escaped = true; - break; - case 0x06: - escaped += "\\u0006"; - is_escaped = true; - break; - case '\a': - escaped += "\\a"; - is_escaped = true; - break; - case '\b': - escaped += "\\b"; - is_escaped = true; - break; - case '\t': - escaped += "\\t"; - is_escaped = true; - break; - case '\n': - escaped += "\\n"; - is_escaped = true; - break; - case '\v': - escaped += "\\v"; - is_escaped = true; - break; - case '\f': - escaped += "\\f"; - is_escaped = true; - break; - case '\r': - escaped += "\\r"; - is_escaped = true; - break; - case 0x0E: - escaped += "\\u000E"; - is_escaped = true; - break; - case 0x0F: - escaped += "\\u000F"; - is_escaped = true; - break; +/// @brief Converts a node_attr_t value to a node_type value. +/// @param bits node attribute bits +/// @return An associated node value type with the given node value bit. +inline node_type to_node_type(node_attr_t bits) noexcept { + switch (bits & node_attr_mask::value) { + case seq_bit: + return node_type::SEQUENCE; + case map_bit: + return node_type::MAPPING; + case null_bit: + return node_type::NULL_OBJECT; + case bool_bit: + return node_type::BOOLEAN; + case int_bit: + return node_type::INTEGER; + case float_bit: + return node_type::FLOAT; + case string_bit: + return node_type::STRING; + default: // LCOV_EXCL_LINE + detail::unreachable(); // LCOV_EXCL_LINE + } +} + +/// @brief Get an anchor offset used to reference an anchor node from the given attribute bits. +/// @param attrs node attribute bits +/// @return An anchor offset value. +inline uint32_t get_anchor_offset(node_attr_t attrs) noexcept { + return (attrs & node_attr_mask::anchor_offset) >> 26; +} + +/// @brief Set an anchor offset value to the appropriate bits. +/// @param offset An anchor offset value. +/// @param attrs node attribute bit set into which the offset value is written. +inline void set_anchor_offset(uint32_t offset, node_attr_t& attrs) noexcept { + attrs &= ~node_attr_mask::anchor_offset; + attrs |= (offset & 0x3Fu) << 26; +} + +} // namespace node_attr_bits + +FK_YAML_DETAIL_NAMESPACE_END + +#endif /* FK_YAML_DETAIL_NODE_ATTRS_HPP */ + +// #include + + +FK_YAML_DETAIL_NAMESPACE_BEGIN + +/////////////////////////////////// +// external_node_constructor // +/////////////////////////////////// + +/// @brief The external constructor template for basic_node objects. +/// @note All the non-specialized instantiations results in compilation error since such instantiations are not +/// supported. +/// @warning All the specialization must call n.m_value.destroy() first in the construct function to avoid +/// memory leak. +/// @tparam node_type The resulting YAML node value type. +template +struct external_node_constructor { + template + static void sequence(BasicNodeType& n, Args&&... args) { + destroy(n); + n.m_attrs |= node_attr_bits::seq_bit; + n.m_value.p_seq = create_object(std::forward(args)...); + } + + template + static void mapping(BasicNodeType& n, Args&&... args) { + destroy(n); + n.m_attrs |= node_attr_bits::map_bit; + n.m_value.p_map = create_object(std::forward(args)...); + } + + static void null_scalar(BasicNodeType& n, std::nullptr_t) { + destroy(n); + n.m_attrs |= node_attr_bits::null_bit; + n.m_value.p_map = nullptr; + } + + static void boolean_scalar(BasicNodeType& n, const typename BasicNodeType::boolean_type b) { + destroy(n); + n.m_attrs |= node_attr_bits::bool_bit; + n.m_value.boolean = b; + } + + static void integer_scalar(BasicNodeType& n, const typename BasicNodeType::integer_type i) { + destroy(n); + n.m_attrs |= node_attr_bits::int_bit; + n.m_value.integer = i; + } + + /// @brief Constructs an INTEGER node from a uint64_t value that exceeds the signed range. + /// The raw bit pattern is stored in the integer field and the uint_bit flag is set so that + /// get_value() / as_uint() can recover the original unsigned value. + static void unsigned_integer_scalar(BasicNodeType& n, const typename BasicNodeType::integer_type i) { + destroy(n); + n.m_attrs |= node_attr_bits::int_bit; + n.m_attrs |= node_attr_bits::uint_bit; + n.m_value.integer = i; + } + + static void float_scalar(BasicNodeType& n, const typename BasicNodeType::float_number_type f) { + destroy(n); + n.m_attrs |= node_attr_bits::float_bit; + n.m_value.float_val = f; + } + + template + static void string_scalar(BasicNodeType& n, Args&&... args) { + destroy(n); + n.m_attrs |= node_attr_bits::string_bit; + n.m_value.p_str = create_object(std::forward(args)...); + } + +private: + static void destroy(BasicNodeType& n) { + n.m_value.destroy(n.m_attrs & node_attr_mask::value); + // Clear both the value-type bits and the uint_bit style flag so that any + // subsequent reassignment starts from a clean state. + n.m_attrs &= ~(node_attr_mask::value | node_attr_bits::uint_bit); + } +}; + +///////////////// +// to_node // +///////////////// + +/// @brief to_node function for BasicNodeType::sequence_type objects. +/// @tparam BasicNodeType A basic_node template instance type. +/// @tparam T A sequence node value type. +/// @param n A basic_node object. +/// @param s A sequence node value object. +template < + typename BasicNodeType, typename T, + enable_if_t< + conjunction< + is_basic_node, + std::is_same>>::value, + int> = 0> +inline void to_node(BasicNodeType& n, T&& s) noexcept { + external_node_constructor::sequence(n, std::forward(s)); +} + +/// @brief to_node function for compatible sequence types. +/// @note This overload is enabled when +/// * both begin()/end() functions are callable on a `CompatSeqType` object +/// * CompatSeqType doesn't have `mapped_type` (mapping-like type) +/// * BasicNodeType::string_type cannot be constructed from a CompatSeqType object (string-like type) +/// @tparam BasicNodeType A basic_node template instance type. +/// @tparam CompatSeqType A container type. +/// @param n A basic_node object. +/// @param s A container object. +template < + typename BasicNodeType, typename CompatSeqType, + enable_if_t< + conjunction< + is_basic_node, + negation>>, + negation>, detect::has_begin_end, + negation, detect::has_mapped_type>>, + negation>>::value, + int> = 0> +// NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward) +inline void to_node(BasicNodeType& n, CompatSeqType&& s) { + using std::begin; + using std::end; + external_node_constructor::sequence(n, begin(s), end(s)); +} + +/// @brief to_node function for std::pair objects. +/// @tparam BasicNodeType A basic_node template instance type. +/// @tparam T The first type of std::pair. +/// @tparam U The second type of std::pair. +/// @param n A basic_node object. +/// @param p A std::pair object. +template +inline void to_node(BasicNodeType& n, const std::pair& p) { + n = {p.first, p.second}; +} + +/// @brief concrete implementation of to_node function for std::tuple objects. +/// @tparam BasicNodeType A basic_node template instance type. +/// @tparam ...Types The value types of std::tuple. +/// @tparam ...Idx Index sequence values for std::tuple value types. +/// @param n A basic_node object. +/// @param t A std::tuple object. +/// @param _ An index sequence. (unused) +template +inline void to_node_tuple_impl(BasicNodeType& n, const std::tuple& t, index_sequence /*unused*/) { + n = {std::get(t)...}; +} + +/// @brief to_node function for std::tuple objects with no value types. +/// @note This implementation is needed since calling `to_node_tuple_impl()` with an empty tuple creates a null node. +/// @tparam BasicNodeType A basic_node template instance type. +/// @param n A basic_node object. +/// @param _ A std::tuple object. (unused) +template +inline void to_node(BasicNodeType& n, const std::tuple<>& /*unused*/) { + n = BasicNodeType::sequence(); +} + +/// @brief to_node function for std::tuple objects with at least one value type. +/// @tparam BasicNodeType A basic_node template instance type. +/// @tparam ...FirstType The first value types of std::tuple. +/// @tparam ...RestTypes The rest value types of std::tuple. (maybe empty) +/// @param n A basic_node object. +/// @param t A std::tuple object. +template +inline void to_node(BasicNodeType& n, const std::tuple& t) { + to_node_tuple_impl(n, t, index_sequence_for {}); +} + +/// @brief to_node function for BasicNodeType::mapping_type objects. +/// @tparam BasicNodeType A basic_node template instance type. +/// @tparam T A mapping node value type. +/// @param n A basic_node object. +/// @param m A mapping node value object. +template < + typename BasicNodeType, typename T, + enable_if_t< + conjunction< + is_basic_node, std::is_same>>::value, + int> = 0> +inline void to_node(BasicNodeType& n, T&& m) noexcept { + external_node_constructor::mapping(n, std::forward(m)); +} + +/// @brief to_node function for compatible mapping types. +/// @note This overload is enabled when +/// * both begin()/end() functions are callable on a `CompatMapType` object +/// * CompatMapType has both `key_type` and `mapped_type` +/// @tparam BasicNodeType A basic_node template instance type. +/// @tparam CompatMapType A container type. +/// @param n A basic_node object. +/// @param m A container object. +template < + typename BasicNodeType, typename CompatMapType, + enable_if_t< + conjunction< + is_basic_node, negation>, + negation>>, + detect::has_begin_end, detect::has_key_type, + detect::has_mapped_type>::value, + int> = 0> +inline void to_node(BasicNodeType& n, CompatMapType&& m) { + external_node_constructor::mapping(n); + auto& map = n.as_map(); + for (const auto& pair : std::forward(m)) { + map.emplace(pair.first, pair.second); + } +} + +/// @brief to_node function for null objects. +/// @tparam BasicNodeType A mapping node value type. +/// @tparam NullType This must be std::nullptr_t type +template ::value, int> = 0> +inline void to_node(BasicNodeType& n, std::nullptr_t /*unused*/) { + external_node_constructor::null_scalar(n, nullptr); +} + +/// @brief to_node function for BasicNodeType::boolean_type objects. +/// @tparam BasicNodeType A basic_node template instance type. +/// @tparam T A boolean scalar node value type. +/// @param n A basic_node object. +/// @param b A boolean scalar node value object. +template ::value, int> = 0> +inline void to_node(BasicNodeType& n, typename BasicNodeType::boolean_type b) noexcept { + external_node_constructor::boolean_scalar(n, b); +} + +/// @brief to_node function for integers. +/// @tparam BasicNodeType A basic_node template instance type. +/// @tparam T An integer type. +/// @param n A basic_node object. +/// @param i An integer object. +template < + typename BasicNodeType, typename T, + enable_if_t, is_non_bool_integral>::value, int> = 0> +inline void to_node(BasicNodeType& n, T i) noexcept { + external_node_constructor::integer_scalar(n, i); +} + +/// @brief to_node function for floating point numbers. +/// @tparam BasicNodeType A basic_node template instance type. +/// @tparam T A floating point number type. +/// @param n A basic_node object. +/// @param f A floating point number object. +template < + typename BasicNodeType, typename T, + enable_if_t, std::is_floating_point>::value, int> = 0> +inline void to_node(BasicNodeType& n, T f) noexcept { + external_node_constructor::float_scalar(n, f); +} + +/// @brief to_node function for compatible strings. +/// @tparam BasicNodeType A basic_node template instance type. +/// @tparam T A compatible string type. +/// @param n A basic_node object. +/// @param s A compatible string object. +template < + typename BasicNodeType, typename T, + enable_if_t< + conjunction< + is_basic_node, negation>, + std::is_constructible>::value, + int> = 0> +inline void to_node(BasicNodeType& n, T&& s) { + external_node_constructor::string_scalar(n, std::forward(s)); +} + +/// @brief A function object to call to_node functions. +/// @note User-defined specialization is available by providing implementation **OUTSIDE** fkyaml namespace. +struct to_node_fn { + /// @brief Call to_node function suitable for the given T type. + /// @tparam BasicNodeType A basic_node template instance type. + /// @tparam T A target value type assigned to the basic_node object. + /// @param n A basic_node object. + /// @param val A target object assigned to the basic_node object. + /// @return decltype(to_node(n, std::forward(val))) void by default. User can set it to some other type. + template + auto operator()(BasicNodeType& n, T&& val) const + noexcept(noexcept(to_node(n, std::forward(val)))) -> decltype(to_node(n, std::forward(val))) { + return to_node(n, std::forward(val)); + } +}; + +FK_YAML_DETAIL_NAMESPACE_END + +FK_YAML_NAMESPACE_BEGIN + +#ifndef FK_YAML_HAS_CXX_17 +// anonymous namespace to hold `to_node` functor. +// see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html for why it's needed. +namespace // NOLINT(cert-dcl59-cpp,fuchsia-header-anon-namespaces,google-build-namespaces) +{ +#endif + +/// @brief A global object to represent ADL friendly to_node functor. +// NOLINTNEXTLINE(misc-definitions-in-headers) +FK_YAML_INLINE_VAR constexpr const auto& to_node = detail::static_const::value; + +#ifndef FK_YAML_HAS_CXX_17 +} // namespace +#endif + +FK_YAML_NAMESPACE_END + +#endif /* FK_YAML_DETAIL_CONVERSIONS_TO_NODE_HPP */ + +// #include +// _______ __ __ __ _____ __ __ __ +// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library +// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 +// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML +// +// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani +// SPDX-License-Identifier: MIT + +#ifndef FK_YAML_DETAIL_ENCODINGS_YAML_ESCAPER_HPP +#define FK_YAML_DETAIL_ENCODINGS_YAML_ESCAPER_HPP + +#include + +// #include + +// #include + +// #include + +// #include + + +FK_YAML_DETAIL_NAMESPACE_BEGIN + +class yaml_escaper { + using iterator = ::std::string::const_iterator; + +public: + static bool unescape(const char*& begin, const char* end, std::string& buff) { + FK_YAML_ASSERT(*begin == '\\' && std::distance(begin, end) > 0); + bool ret = true; + + switch (*++begin) { + case 'a': + buff.push_back('\a'); + break; + case 'b': + buff.push_back('\b'); + break; + case 't': + case '\t': + buff.push_back('\t'); + break; + case 'n': + buff.push_back('\n'); + break; + case 'v': + buff.push_back('\v'); + break; + case 'f': + buff.push_back('\f'); + break; + case 'r': + buff.push_back('\r'); + break; + case 'e': + buff.push_back(static_cast(0x1B)); + break; + case ' ': + buff.push_back(' '); + break; + case '\"': + buff.push_back('\"'); + break; + case '/': + buff.push_back('/'); + break; + case '\\': + buff.push_back('\\'); + break; + case 'N': // next line + unescape_escaped_unicode(0x85u, buff); + break; + case '_': // non-breaking space + unescape_escaped_unicode(0xA0u, buff); + break; + case 'L': // line separator + unescape_escaped_unicode(0x2028u, buff); + break; + case 'P': // paragraph separator + unescape_escaped_unicode(0x2029u, buff); + break; + case 'x': { + char32_t codepoint {0}; + ret = extract_codepoint(begin, end, 1, codepoint); + if FK_YAML_LIKELY (ret) { + unescape_escaped_unicode(codepoint, buff); + } + break; + } + case 'u': { + char32_t codepoint {0}; + ret = extract_codepoint(begin, end, 2, codepoint); + if FK_YAML_LIKELY (ret) { + unescape_escaped_unicode(codepoint, buff); + } + break; + } + case 'U': { + char32_t codepoint {0}; + ret = extract_codepoint(begin, end, 4, codepoint); + if FK_YAML_LIKELY (ret) { + unescape_escaped_unicode(codepoint, buff); + } + break; + } + default: + // Unsupported escape sequence is found in a string token. + ret = false; + break; + } + + return ret; + } + + static ::std::string escape(const char* begin, const char* end, bool& is_escaped) { + ::std::string escaped {}; + escaped.reserve(std::distance(begin, end)); + for (; begin != end; ++begin) { + switch (*begin) { + case 0x01: + escaped += "\\u0001"; + is_escaped = true; + break; + case 0x02: + escaped += "\\u0002"; + is_escaped = true; + break; + case 0x03: + escaped += "\\u0003"; + is_escaped = true; + break; + case 0x04: + escaped += "\\u0004"; + is_escaped = true; + break; + case 0x05: + escaped += "\\u0005"; + is_escaped = true; + break; + case 0x06: + escaped += "\\u0006"; + is_escaped = true; + break; + case '\a': + escaped += "\\a"; + is_escaped = true; + break; + case '\b': + escaped += "\\b"; + is_escaped = true; + break; + case '\t': + escaped += "\\t"; + is_escaped = true; + break; + case '\n': + escaped += "\\n"; + is_escaped = true; + break; + case '\v': + escaped += "\\v"; + is_escaped = true; + break; + case '\f': + escaped += "\\f"; + is_escaped = true; + break; + case '\r': + escaped += "\\r"; + is_escaped = true; + break; + case 0x0E: + escaped += "\\u000E"; + is_escaped = true; + break; + case 0x0F: + escaped += "\\u000F"; + is_escaped = true; + break; case 0x10: escaped += "\\u0010"; is_escaped = true; @@ -6606,6 +7084,24 @@ class scalar_parser { if FK_YAML_LIKELY (converted) { return basic_node_type(integer); } + + // For untagged plain integer scalars, attempt a uint64_t parse to handle large + // positive values that exceed int64_t max (e.g. xxHash/UUID results like + // 15745692345339290292). This only applies when integer_type is a signed 64-bit + // type; any other width would not be able to represent the value anyway. + if (tag_type != tag_t::INTEGER && std::is_signed::value && + sizeof(integer_type) == sizeof(uint64_t)) { + uint64_t u64 = 0; + if (detail::atoi(token.begin(), token.end(), u64)) { + basic_node_type node; + // Store the bit pattern in the signed field and set uint_bit so that + // as_uint() / get_value() can recover the correct value. + detail::external_node_constructor::unsigned_integer_scalar( + node, static_cast(u64)); + return node; + } + } + if FK_YAML_UNLIKELY (tag_type == tag_t::INTEGER) { throw parse_error("Failed to convert a scalar to an integer.", m_line, m_indent); } @@ -6877,179 +7373,40 @@ FK_YAML_DETAIL_NAMESPACE_BEGIN /// @brief A type which represents get_buffer_view function. /// @tparam T A target type. -template -using get_buffer_view_fn_t = decltype(std::declval().get_buffer_view()); - -/// @brief Type traits to check if InputAdapterType has get_buffer_view member function. -/// @tparam InputAdapterType An input adapter type to check if it has get_buffer_view function. -/// @tparam typename N/A -template -using has_get_buffer_view = is_detected; - -//////////////////////////////// -// is_input_adapter traits -//////////////////////////////// - -/// @brief Type traits to check if T is an input adapter type. -/// @tparam T A target type. -/// @tparam typename N/A -template -struct is_input_adapter : std::false_type {}; - -/// @brief A partial specialization of is_input_adapter if T is an input adapter type. -/// @tparam InputAdapterType -template -struct is_input_adapter::value>> : std::true_type { -}; - -FK_YAML_DETAIL_NAMESPACE_END - -#endif /* FK_YAML_DETAIL_META_INPUT_ADAPTER_TRAITS_HPP */ - -// #include - -// #include - -// #include -// _______ __ __ __ _____ __ __ __ -// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library -// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 -// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML -// -// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani -// SPDX-License-Identifier: MIT - -#ifndef FK_YAML_DETAIL_NODE_ATTRS_HPP -#define FK_YAML_DETAIL_NODE_ATTRS_HPP - -#include -#include - -// #include - -// #include - - -FK_YAML_DETAIL_NAMESPACE_BEGIN - -/// @brief The type for node attribute bits. -using node_attr_t = uint32_t; - -/// @brief The namespace to define bit masks for node attribute bits. -namespace node_attr_mask { - -/// The bit mask for node value type bits. -constexpr node_attr_t value = 0x0000FFFFu; -/// The bit mask for node style type bits. (bits are not yet defined.) -constexpr node_attr_t style = 0x00FF0000u; -/// The bit mask for node property related bits. -constexpr node_attr_t props = 0xFF000000u; -/// The bit mask for anchor/alias node type bits. -constexpr node_attr_t anchoring = 0x03000000u; -/// The bit mask for anchor offset value bits. -constexpr node_attr_t anchor_offset = 0xFC000000u; -/// The bit mask for all the bits for node attributes. -constexpr node_attr_t all = std::numeric_limits::max(); - -} // namespace node_attr_mask - -/// @brief The namespace to define bits for node attributes. -namespace node_attr_bits { - -/// The sequence node bit. -constexpr node_attr_t seq_bit = 1u << 0; -/// The mapping node bit. -constexpr node_attr_t map_bit = 1u << 1; -/// The null scalar node bit. -constexpr node_attr_t null_bit = 1u << 2; -/// The boolean scalar node bit. -constexpr node_attr_t bool_bit = 1u << 3; -/// The integer scalar node bit. -constexpr node_attr_t int_bit = 1u << 4; -/// The floating point scalar node bit. -constexpr node_attr_t float_bit = 1u << 5; -/// The string scalar node bit. -constexpr node_attr_t string_bit = 1u << 6; - -/// A utility bit set to filter scalar node bits. -constexpr node_attr_t scalar_bits = null_bit | bool_bit | int_bit | float_bit | string_bit; - -/// The anchor node bit. -constexpr node_attr_t anchor_bit = 0x01000000u; -/// The alias node bit. -constexpr node_attr_t alias_bit = 0x02000000u; - -/// A utility bit set for initialization. -constexpr node_attr_t default_bits = null_bit; - -/// @brief Converts a node_type value to a node_attr_t value. -/// @param t A type of node value. -/// @return The associated node value bit. -inline node_attr_t from_node_type(node_type t) noexcept { - switch (t) { - case node_type::SEQUENCE: - return seq_bit; - case node_type::MAPPING: - return map_bit; - case node_type::NULL_OBJECT: - return null_bit; - case node_type::BOOLEAN: - return bool_bit; - case node_type::INTEGER: - return int_bit; - case node_type::FLOAT: - return float_bit; - case node_type::STRING: - return string_bit; - default: // LCOV_EXCL_LINE - return node_attr_mask::all; // LCOV_EXCL_LINE - } -} - -/// @brief Converts a node_attr_t value to a node_type value. -/// @param bits node attribute bits -/// @return An associated node value type with the given node value bit. -inline node_type to_node_type(node_attr_t bits) noexcept { - switch (bits & node_attr_mask::value) { - case seq_bit: - return node_type::SEQUENCE; - case map_bit: - return node_type::MAPPING; - case null_bit: - return node_type::NULL_OBJECT; - case bool_bit: - return node_type::BOOLEAN; - case int_bit: - return node_type::INTEGER; - case float_bit: - return node_type::FLOAT; - case string_bit: - return node_type::STRING; - default: // LCOV_EXCL_LINE - detail::unreachable(); // LCOV_EXCL_LINE - } -} +template +using get_buffer_view_fn_t = decltype(std::declval().get_buffer_view()); -/// @brief Get an anchor offset used to reference an anchor node from the given attribute bits. -/// @param attrs node attribute bits -/// @return An anchor offset value. -inline uint32_t get_anchor_offset(node_attr_t attrs) noexcept { - return (attrs & node_attr_mask::anchor_offset) >> 26; -} +/// @brief Type traits to check if InputAdapterType has get_buffer_view member function. +/// @tparam InputAdapterType An input adapter type to check if it has get_buffer_view function. +/// @tparam typename N/A +template +using has_get_buffer_view = is_detected; -/// @brief Set an anchor offset value to the appropriate bits. -/// @param offset An anchor offset value. -/// @param attrs node attribute bit set into which the offset value is written. -inline void set_anchor_offset(uint32_t offset, node_attr_t& attrs) noexcept { - attrs &= ~node_attr_mask::anchor_offset; - attrs |= (offset & 0x3Fu) << 26; -} +//////////////////////////////// +// is_input_adapter traits +//////////////////////////////// -} // namespace node_attr_bits +/// @brief Type traits to check if T is an input adapter type. +/// @tparam T A target type. +/// @tparam typename N/A +template +struct is_input_adapter : std::false_type {}; + +/// @brief A partial specialization of is_input_adapter if T is an input adapter type. +/// @tparam InputAdapterType +template +struct is_input_adapter::value>> : std::true_type { +}; FK_YAML_DETAIL_NAMESPACE_END -#endif /* FK_YAML_DETAIL_NODE_ATTRS_HPP */ +#endif /* FK_YAML_DETAIL_META_INPUT_ADAPTER_TRAITS_HPP */ + +// #include + +// #include + +// #include // #include // _______ __ __ __ _____ __ __ __ @@ -9410,219 +9767,9 @@ class file_input_adapter { m_buffer.clear(); char tmp_buf[256] {}; - constexpr std::size_t buf_size = sizeof(tmp_buf) / sizeof(tmp_buf[0]); - std::size_t read_size = 0; - while ((read_size = std::fread(&tmp_buf[0], sizeof(char), buf_size, m_file)) > 0) { - char* p_current = &tmp_buf[0]; - char* p_end = p_current + read_size; - - // copy tmp_buf to m_buffer, dropping CRs. - char* p_cr = p_current; - do { - if FK_YAML_UNLIKELY (*p_cr == '\r') { - m_buffer.append(p_current, p_cr); - p_current = p_cr + 1; - } - ++p_cr; - } while (p_cr != p_end); - - m_buffer.append(p_current, p_end); - } - - if FK_YAML_UNLIKELY (m_buffer.empty()) { - return {}; - } - - auto current = m_buffer.begin(); - auto end = m_buffer.end(); - while (current != end) { - const auto first = static_cast(*current++); - const uint32_t num_bytes = utf8::get_num_bytes(first); - - switch (num_bytes) { - case 1: - break; - case 2: { - const auto second = static_cast(*current++); - const bool is_valid = utf8::validate(first, second); - if FK_YAML_UNLIKELY (!is_valid) { - throw fkyaml::invalid_encoding("Invalid UTF-8 encoding.", {first, second}); - } - break; - } - case 3: { - const auto second = static_cast(*current++); - const auto third = static_cast(*current++); - const bool is_valid = utf8::validate(first, second, third); - if FK_YAML_UNLIKELY (!is_valid) { - throw fkyaml::invalid_encoding("Invalid UTF-8 encoding.", {first, second, third}); - } - break; - } - case 4: { - const auto second = static_cast(*current++); - const auto third = static_cast(*current++); - const auto fourth = static_cast(*current++); - const bool is_valid = utf8::validate(first, second, third, fourth); - if FK_YAML_UNLIKELY (!is_valid) { - throw fkyaml::invalid_encoding("Invalid UTF-8 encoding.", {first, second, third, fourth}); - } - break; - } - default: // LCOV_EXCL_LINE - unreachable(); // LCOV_EXCL_LINE - } - } - - return str_view {m_buffer.begin(), m_buffer.end()}; - } - - /// @brief The concrete implementation of get_buffer_view() for UTF-16 encoded inputs. - /// @return View into the UTF-8 encoded input buffer contents. - str_view get_buffer_view_utf16() { - FK_YAML_ASSERT(m_encode_type == utf_encode_t::UTF_16BE || m_encode_type == utf_encode_t::UTF_16LE); - - int shift_bits[2] {0, 0}; - if (m_encode_type == utf_encode_t::UTF_16BE) { - shift_bits[0] = 8; - } - else { // m_encode_type == utf_encode_t::UTF_16LE - shift_bits[1] = 8; - } - - char chars[2] = {0, 0}; - std::array encoded_buffer {{0, 0}}; - uint32_t encoded_buf_size {0}; - std::array utf8_buffer {{0, 0, 0, 0}}; - uint32_t utf8_buf_size {0}; - - while (std::feof(m_file) == 0) { - while (encoded_buf_size < 2 && std::fread(&chars[0], sizeof(char), 2, m_file) == 2) { - const auto utf16 = static_cast( - (static_cast(chars[0]) << shift_bits[0]) | - (static_cast(chars[1]) << shift_bits[1])); - if FK_YAML_LIKELY (utf16 != static_cast(0x000Du)) { - // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) - encoded_buffer[encoded_buf_size++] = utf16; - } - } - - uint32_t consumed_size = 0; - utf8::from_utf16(encoded_buffer, utf8_buffer, consumed_size, utf8_buf_size); - - if FK_YAML_LIKELY (consumed_size == 1) { - encoded_buffer[0] = encoded_buffer[1]; - } - encoded_buf_size -= consumed_size; - - m_buffer.append(reinterpret_cast(utf8_buffer.data()), utf8_buf_size); - } - - return str_view {m_buffer.begin(), m_buffer.end()}; - } - - /// @brief The concrete implementation of get_buffer_view() for UTF-32 encoded inputs. - /// @return View into the UTF-8 encoded input buffer contents. - str_view get_buffer_view_utf32() { - FK_YAML_ASSERT(m_encode_type == utf_encode_t::UTF_32BE || m_encode_type == utf_encode_t::UTF_32LE); - - int shift_bits[4] {0, 0, 0, 0}; - if (m_encode_type == utf_encode_t::UTF_32BE) { - shift_bits[0] = 24; - shift_bits[1] = 16; - shift_bits[2] = 8; - } - else { // m_encode_type == utf_encode_t::UTF_32LE - shift_bits[1] = 8; - shift_bits[2] = 16; - shift_bits[3] = 24; - } - - char chars[4] = {0, 0, 0, 0}; - std::array utf8_buffer {{0, 0, 0, 0}}; - uint32_t utf8_buf_size {0}; - - while (std::feof(m_file) == 0) { - const std::size_t size = std::fread(&chars[0], sizeof(char), 4, m_file); - if (size != 4) { - break; - } - - const auto utf32 = static_cast( - (static_cast(chars[0]) << shift_bits[0]) | (static_cast(chars[1]) << shift_bits[1]) | - (static_cast(chars[2]) << shift_bits[2]) | (static_cast(chars[3]) << shift_bits[3])); - - if FK_YAML_LIKELY (utf32 != char32_t(0x0000000Du)) { - utf8::from_utf32(utf32, utf8_buffer, utf8_buf_size); - m_buffer.append(reinterpret_cast(utf8_buffer.data()), utf8_buf_size); - } - } - - return str_view {m_buffer.begin(), m_buffer.end()}; - } - -private: - /// A pointer to the input file handle. - std::FILE* m_file {nullptr}; - /// The encoding type for this input adapter. - utf_encode_t m_encode_type {utf_encode_t::UTF_8}; - /// The normalized owned buffer. - std::string m_buffer; -}; - -/// @brief An input adapter for streams -class stream_input_adapter { -public: - /// @brief Construct a new stream_input_adapter object. - stream_input_adapter() = default; - - /// @brief Construct a new stream_input_adapter object. - /// @param is A reference to the target input stream. - /// @param encode_type The encoding type for this input adapter. - explicit stream_input_adapter(std::istream& is, utf_encode_t encode_type) noexcept - : m_istream(&is), - m_encode_type(encode_type) { - } - - // allow only move construct/assignment - stream_input_adapter(const stream_input_adapter&) = delete; - stream_input_adapter& operator=(const stream_input_adapter&) = delete; - stream_input_adapter(stream_input_adapter&&) = default; - stream_input_adapter& operator=(stream_input_adapter&&) = default; - ~stream_input_adapter() = default; - - /// @brief Get view into the input buffer contents. - /// @return View into the input buffer contents. - str_view get_buffer_view() { - switch (m_encode_type) { - case utf_encode_t::UTF_8: - return get_buffer_view_utf8(); - case utf_encode_t::UTF_16BE: - case utf_encode_t::UTF_16LE: - return get_buffer_view_utf16(); - case utf_encode_t::UTF_32BE: - case utf_encode_t::UTF_32LE: - return get_buffer_view_utf32(); - default: // LCOV_EXCL_LINE - detail::unreachable(); // LCOV_EXCL_LINE - } - } - -private: - /// @brief The concrete implementation of get_buffer_view() for UTF-8 encoded inputs. - /// @return View into the UTF-8 encoded input buffer contents. - str_view get_buffer_view_utf8() { - FK_YAML_ASSERT(m_encode_type == utf_encode_t::UTF_8); - - m_buffer.clear(); - char tmp_buf[256] {}; - do { - m_istream->read(&tmp_buf[0], 256); - const auto read_size = static_cast(m_istream->gcount()); - if FK_YAML_UNLIKELY (read_size == 0) { - break; - } - + constexpr std::size_t buf_size = sizeof(tmp_buf) / sizeof(tmp_buf[0]); + std::size_t read_size = 0; + while ((read_size = std::fread(&tmp_buf[0], sizeof(char), buf_size, m_file)) > 0) { char* p_current = &tmp_buf[0]; char* p_end = p_current + read_size; @@ -9637,7 +9784,7 @@ class stream_input_adapter { } while (p_cr != p_end); m_buffer.append(p_current, p_end); - } while (!m_istream->eof()); + } if FK_YAML_UNLIKELY (m_buffer.empty()) { return {}; @@ -9706,18 +9853,11 @@ class stream_input_adapter { std::array utf8_buffer {{0, 0, 0, 0}}; uint32_t utf8_buf_size {0}; - do { - while (encoded_buf_size < 2) { - m_istream->read(&chars[0], 2); - const std::streamsize size = m_istream->gcount(); - if FK_YAML_UNLIKELY (size != 2) { - break; - } - + while (std::feof(m_file) == 0) { + while (encoded_buf_size < 2 && std::fread(&chars[0], sizeof(char), 2, m_file) == 2) { const auto utf16 = static_cast( (static_cast(chars[0]) << shift_bits[0]) | (static_cast(chars[1]) << shift_bits[1])); - if FK_YAML_LIKELY (utf16 != static_cast(0x000Du)) { // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) encoded_buffer[encoded_buf_size++] = utf16; @@ -9727,615 +9867,400 @@ class stream_input_adapter { uint32_t consumed_size = 0; utf8::from_utf16(encoded_buffer, utf8_buffer, consumed_size, utf8_buf_size); - if FK_YAML_LIKELY (consumed_size == 1) { - encoded_buffer[0] = encoded_buffer[1]; - } - encoded_buf_size -= consumed_size; - - m_buffer.append(reinterpret_cast(utf8_buffer.data()), utf8_buf_size); - } while (!m_istream->eof()); - - return str_view {m_buffer.begin(), m_buffer.end()}; - } - - /// @brief The concrete implementation of get_buffer_view() for UTF-32 encoded inputs. - /// @return View into the UTF-8 encoded input buffer contents. - str_view get_buffer_view_utf32() { - FK_YAML_ASSERT(m_encode_type == utf_encode_t::UTF_32BE || m_encode_type == utf_encode_t::UTF_32LE); - - int shift_bits[4] {0, 0, 0, 0}; - if (m_encode_type == utf_encode_t::UTF_32BE) { - shift_bits[0] = 24; - shift_bits[1] = 16; - shift_bits[2] = 8; - } - else { // m_encode_type == utf_encode_t::UTF_32LE - shift_bits[1] = 8; - shift_bits[2] = 16; - shift_bits[3] = 24; - } - - char chars[4] = {0, 0, 0, 0}; - std::array utf8_buffer {{0, 0, 0, 0}}; - uint32_t utf8_buf_size {0}; - - do { - m_istream->read(&chars[0], 4); - const std::streamsize size = m_istream->gcount(); - if FK_YAML_UNLIKELY (size != 4) { - break; - } - - const auto utf32 = static_cast( - (static_cast(chars[0]) << shift_bits[0]) | (static_cast(chars[1]) << shift_bits[1]) | - (static_cast(chars[2]) << shift_bits[2]) | (static_cast(chars[3]) << shift_bits[3])); - - if FK_YAML_LIKELY (utf32 != char32_t(0x0000000Du)) { - utf8::from_utf32(utf32, utf8_buffer, utf8_buf_size); - m_buffer.append(reinterpret_cast(utf8_buffer.data()), utf8_buf_size); - } - } while (!m_istream->eof()); - - return str_view {m_buffer.begin(), m_buffer.end()}; - } - -private: - /// A pointer to the input stream object. - std::istream* m_istream {nullptr}; - /// The encoding type for this input adapter. - utf_encode_t m_encode_type {utf_encode_t::UTF_8}; - /// The normalized owned buffer. - std::string m_buffer; -}; - -///////////////////////////////// -// input_adapter providers // -///////////////////////////////// - -/// @brief A concrete factory method for iterator_input_adapter objects with iterators. -/// @tparam ItrType An iterator type. -/// @param begin The beginning of iterators. -/// @param end The end of iterators. -/// @param is_contiguous Whether iterators refer to a contiguous byte array. -/// @return An iterator_input_adapter object for the target iterator type. -template -inline iterator_input_adapter create_iterator_input_adapter(ItrType begin, ItrType end, bool is_contiguous) { - const utf_encode_t encode_type = utf_encode_detector::detect(begin, end); - return iterator_input_adapter(begin, end, encode_type, is_contiguous); -} - -/// @brief A factory method for iterator_input_adapter objects with iterator values. -/// @tparam ItrType An iterator type. -/// @param begin The beginning of iterators. -/// @param end The end of iterators. -/// @return iterator_input_adapter An iterator_input_adapter object for the target iterator type. -template -inline iterator_input_adapter input_adapter(ItrType begin, ItrType end) { - bool is_contiguous = true; - const auto size = std::distance(begin, end); - - // Check if `begin` & `end` are contiguous iterators. - // Getting distance between begin and (end - 1) avoids dereferencing an invalid sentinel. - if FK_YAML_LIKELY (size > 0) { - using char_ptr_t = remove_cvref_t::pointer>; - char_ptr_t p_begin = &*begin; - char_ptr_t p_second_last = &*std::next(begin, size - 1); - is_contiguous = (p_second_last - p_begin == size); - } - return create_iterator_input_adapter(begin, end, is_contiguous); -} - -/// @brief A factory method for iterator_input_adapter objects with C-style arrays. -/// @tparam T A type of arrayed objects. -/// @tparam N A size of an array. -/// @return decltype(input_adapter(array, array + N)) An iterator_input_adapter object for the target array. -template -inline auto input_adapter(T (&array)[N]) -> decltype(create_iterator_input_adapter(array, array + (N - 1), true)) { - return create_iterator_input_adapter(array, array + (N - 1), true); -} - -/// @brief A namespace to implement container_input_adapter_factory for internal use. -namespace input_adapter_factory { - -using std::begin; -using std::end; - -/// @brief A factory of input adapters for containers. -/// @tparam ContainerType A container type. -/// @tparam typename N/A -template -struct container_input_adapter_factory {}; - -/// @brief A partial specialization of container_input_adapter_factory if begin()/end() are available for ContainerType. -/// @tparam ContainerType A container type. -template -struct container_input_adapter_factory< - ContainerType, void_t()), end(std::declval()))>> { - /// A type for resulting input adapter object. - using adapter_type = - decltype(input_adapter(begin(std::declval()), end(std::declval()))); - - /// @brief A factory method of input adapter objects for the target container objects. - /// @param container A container-like input object. - /// @return adapter_type An iterator_input_adapter object. - static adapter_type create(const ContainerType& container) { - return input_adapter(begin(container), end(container)); - } -}; - -} // namespace input_adapter_factory - -/// @brief A factory method for iterator_input_adapter objects with containers. -/// @tparam ContainerType A container type. -/// @param container A container object. -/// @return input_adapter_factory::container_input_adapter_factory::adapter_type -template -inline typename input_adapter_factory::container_input_adapter_factory::adapter_type input_adapter( - const ContainerType& container) { - return input_adapter_factory::container_input_adapter_factory::create(container); -} - -/// @brief A factory method for file_input_adapter objects with C-style file handles. -/// @param file A file handle. -/// @return file_input_adapter A file_input_adapter object. -inline file_input_adapter input_adapter(std::FILE* file) { - if FK_YAML_UNLIKELY (!file) { - throw fkyaml::exception("Invalid FILE object pointer."); - } - - const utf_encode_t encode_type = file_utf_encode_detector::detect(file); - return file_input_adapter(file, encode_type); -} - -/// @brief A factory method for stream_input_adapter objects with std::istream objects. -/// @param stream An input stream. -/// @return stream_input_adapter A stream_input_adapter object. -inline stream_input_adapter input_adapter(std::istream& stream) { - if FK_YAML_UNLIKELY (!stream.good()) { - throw fkyaml::exception("Invalid stream."); - } - - const utf_encode_t encode_type = stream_utf_encode_detector::detect(stream); - return stream_input_adapter(stream, encode_type); -} - -FK_YAML_DETAIL_NAMESPACE_END - -#endif /* FK_YAML_DETAIL_INPUT_INPUT_ADAPTER_HPP */ - -// #include -// _______ __ __ __ _____ __ __ __ -// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library -// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 -// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML -// -// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani -// SPDX-License-Identifier: MIT - -#ifndef FK_YAML_DETAIL_ITERATOR_HPP -#define FK_YAML_DETAIL_ITERATOR_HPP - -#include -#include - -// #include - -// #include - -// #include - - -FK_YAML_DETAIL_NAMESPACE_BEGIN - -/// @brief The template definitions of type information used in @ref Iterator class -/// @tparam ValueType The type of iterated elements. -template -struct iterator_traits { - /// A type of iterated elements. - using value_type = typename ValueType::value_type; - /// A type to represent difference between iterators. - using difference_type = typename ValueType::difference_type; - /// A type of an element pointer. - using pointer = typename ValueType::pointer; - /// A type of reference to an element. - using reference = typename ValueType::reference; -}; - -/// @brief A specialization of @ref iterator_traits for constant value types. -/// @tparam ValueType The type of iterated elements. -template -struct iterator_traits { - /// A type of iterated elements. - using value_type = typename ValueType::value_type; - /// A type to represent difference between iterators. - using difference_type = typename ValueType::difference_type; - /// A type of a constant element pointer. - using pointer = typename ValueType::const_pointer; - /// A type of constant reference to an element. - using reference = typename ValueType::const_reference; -}; - -/// @brief Definitions of iterator types for iterators internally held. -enum class iterator_t : std::uint8_t { - SEQUENCE, //!< sequence iterator type. - MAPPING, //!< mapping iterator type. -}; - -/// @brief The actual storage for iterators internally held in iterator. -template -struct iterator_holder { - static_assert( - is_basic_node::value, - "iterator_holder class only accepts a basic_node as its template parameter."); + if FK_YAML_LIKELY (consumed_size == 1) { + encoded_buffer[0] = encoded_buffer[1]; + } + encoded_buf_size -= consumed_size; - /// A sequence iterator object. - typename BasicNodeType::sequence_type::iterator sequence_iterator {}; - /// A mapping iterator object. - typename BasicNodeType::mapping_type::iterator mapping_iterator {}; -}; + m_buffer.append(reinterpret_cast(utf8_buffer.data()), utf8_buf_size); + } -/// @brief A class which holds iterators either of sequence or mapping type -/// @tparam ValueType The type of iterated elements. -template -class iterator { - /// @brief The iterator type with ValueType of different const-ness. - using other_iterator_type = typename std::conditional< - std::is_const::value, iterator::type>, - iterator>::type; + return str_view {m_buffer.begin(), m_buffer.end()}; + } - friend other_iterator_type; + /// @brief The concrete implementation of get_buffer_view() for UTF-32 encoded inputs. + /// @return View into the UTF-8 encoded input buffer contents. + str_view get_buffer_view_utf32() { + FK_YAML_ASSERT(m_encode_type == utf_encode_t::UTF_32BE || m_encode_type == utf_encode_t::UTF_32LE); -public: - /// A type for iterator traits of instantiated @Iterator template class. - using iterator_traits_type = iterator_traits; + int shift_bits[4] {0, 0, 0, 0}; + if (m_encode_type == utf_encode_t::UTF_32BE) { + shift_bits[0] = 24; + shift_bits[1] = 16; + shift_bits[2] = 8; + } + else { // m_encode_type == utf_encode_t::UTF_32LE + shift_bits[1] = 8; + shift_bits[2] = 16; + shift_bits[3] = 24; + } - /// A type for iterator category tag. - using iterator_category = std::bidirectional_iterator_tag; - /// A type of iterated element. - using value_type = typename iterator_traits_type::value_type; - /// A type to represent differences between iterators. - using difference_type = typename iterator_traits_type::difference_type; - /// A type of an element pointer. - using pointer = typename iterator_traits_type::pointer; - /// A type of reference to an element. - using reference = typename iterator_traits_type::reference; + char chars[4] = {0, 0, 0, 0}; + std::array utf8_buffer {{0, 0, 0, 0}}; + uint32_t utf8_buf_size {0}; - static_assert(is_basic_node::value, "iterator class only accepts a basic_node as its value type."); + while (std::feof(m_file) == 0) { + const std::size_t size = std::fread(&chars[0], sizeof(char), 4, m_file); + if (size != 4) { + break; + } - /// @brief Constructs an iterator object. - iterator() = default; + const auto utf32 = static_cast( + (static_cast(chars[0]) << shift_bits[0]) | (static_cast(chars[1]) << shift_bits[1]) | + (static_cast(chars[2]) << shift_bits[2]) | (static_cast(chars[3]) << shift_bits[3])); - /// @brief Construct a new iterator object with sequence iterator object. - /// @param[in] itr An sequence iterator object. - iterator(const typename value_type::sequence_type::iterator& itr) noexcept { - m_iterator_holder.sequence_iterator = itr; - } + if FK_YAML_LIKELY (utf32 != char32_t(0x0000000Du)) { + utf8::from_utf32(utf32, utf8_buffer, utf8_buf_size); + m_buffer.append(reinterpret_cast(utf8_buffer.data()), utf8_buf_size); + } + } - /// @brief Construct a new iterator object with mapping iterator object. - /// @param[in] itr An mapping iterator object. - iterator(const typename value_type::mapping_type::iterator& itr) noexcept - : m_inner_iterator_type(iterator_t::MAPPING) { - m_iterator_holder.mapping_iterator = itr; + return str_view {m_buffer.begin(), m_buffer.end()}; } - /// @brief Copy constructs an iterator. - iterator(const iterator&) = default; +private: + /// A pointer to the input file handle. + std::FILE* m_file {nullptr}; + /// The encoding type for this input adapter. + utf_encode_t m_encode_type {utf_encode_t::UTF_8}; + /// The normalized owned buffer. + std::string m_buffer; +}; - /// @brief Copy constructs an iterator from another iterator with different const-ness in ValueType. - /// @note This copy constructor is not defined if ValueType is not const to avoid const removal from ValueType. - /// @tparam OtherIterator The iterator type to copy from. - /// @param other An iterator to copy from with different const-ness in ValueType. - template < - typename OtherIterator, - enable_if_t< - conjunction, std::is_const>::value, int> = 0> - iterator(const OtherIterator& other) noexcept - : m_inner_iterator_type(other.m_inner_iterator_type), - m_iterator_holder(other.m_iterator_holder) { +/// @brief An input adapter for streams +class stream_input_adapter { +public: + /// @brief Construct a new stream_input_adapter object. + stream_input_adapter() = default; + + /// @brief Construct a new stream_input_adapter object. + /// @param is A reference to the target input stream. + /// @param encode_type The encoding type for this input adapter. + explicit stream_input_adapter(std::istream& is, utf_encode_t encode_type) noexcept + : m_istream(&is), + m_encode_type(encode_type) { } - /// @brief A copy assignment operator of the iterator class. - iterator& operator=(const iterator&) = default; + // allow only move construct/assignment + stream_input_adapter(const stream_input_adapter&) = delete; + stream_input_adapter& operator=(const stream_input_adapter&) = delete; + stream_input_adapter(stream_input_adapter&&) = default; + stream_input_adapter& operator=(stream_input_adapter&&) = default; + ~stream_input_adapter() = default; - template < - typename OtherIterator, - enable_if_t< - conjunction, std::is_const>::value, int> = 0> - iterator& operator=(const OtherIterator& other) noexcept { - m_inner_iterator_type = other.m_inner_iterator_type; - m_iterator_holder = other.m_iterator_holder; - return *this; + /// @brief Get view into the input buffer contents. + /// @return View into the input buffer contents. + str_view get_buffer_view() { + switch (m_encode_type) { + case utf_encode_t::UTF_8: + return get_buffer_view_utf8(); + case utf_encode_t::UTF_16BE: + case utf_encode_t::UTF_16LE: + return get_buffer_view_utf16(); + case utf_encode_t::UTF_32BE: + case utf_encode_t::UTF_32LE: + return get_buffer_view_utf32(); + default: // LCOV_EXCL_LINE + detail::unreachable(); // LCOV_EXCL_LINE + } } - /// @brief Move constructs an iterator. - iterator(iterator&&) = default; +private: + /// @brief The concrete implementation of get_buffer_view() for UTF-8 encoded inputs. + /// @return View into the UTF-8 encoded input buffer contents. + str_view get_buffer_view_utf8() { + FK_YAML_ASSERT(m_encode_type == utf_encode_t::UTF_8); - /// @brief A move assignment operator of the iterator class. - iterator& operator=(iterator&&) = default; + m_buffer.clear(); + char tmp_buf[256] {}; + do { + m_istream->read(&tmp_buf[0], 256); + const auto read_size = static_cast(m_istream->gcount()); + if FK_YAML_UNLIKELY (read_size == 0) { + break; + } - /// @brief Destroys an iterator. - ~iterator() = default; + char* p_current = &tmp_buf[0]; + char* p_end = p_current + read_size; - /// @brief An arrow operator of the iterator class. - /// @return pointer A pointer to the BasicNodeType object internally referenced by the actual iterator object. - pointer operator->() noexcept { - if (m_inner_iterator_type == iterator_t::SEQUENCE) { - return &(*(m_iterator_holder.sequence_iterator)); + // copy tmp_buf to m_buffer, dropping CRs. + char* p_cr = p_current; + do { + if FK_YAML_UNLIKELY (*p_cr == '\r') { + m_buffer.append(p_current, p_cr); + p_current = p_cr + 1; + } + ++p_cr; + } while (p_cr != p_end); + + m_buffer.append(p_current, p_end); + } while (!m_istream->eof()); + + if FK_YAML_UNLIKELY (m_buffer.empty()) { + return {}; } - // m_inner_iterator_type == iterator_t::MAPPING: - return &(m_iterator_holder.mapping_iterator->second); - } + auto current = m_buffer.begin(); + auto end = m_buffer.end(); + while (current != end) { + const auto first = static_cast(*current++); + const uint32_t num_bytes = utf8::get_num_bytes(first); - /// @brief A dereference operator of the iterator class. - /// @return reference Reference to the Node object internally referenced by the actual iterator object. - reference operator*() const noexcept { - if (m_inner_iterator_type == iterator_t::SEQUENCE) { - return *(m_iterator_holder.sequence_iterator); + switch (num_bytes) { + case 1: + break; + case 2: { + const auto second = static_cast(*current++); + const bool is_valid = utf8::validate(first, second); + if FK_YAML_UNLIKELY (!is_valid) { + throw fkyaml::invalid_encoding("Invalid UTF-8 encoding.", {first, second}); + } + break; + } + case 3: { + const auto second = static_cast(*current++); + const auto third = static_cast(*current++); + const bool is_valid = utf8::validate(first, second, third); + if FK_YAML_UNLIKELY (!is_valid) { + throw fkyaml::invalid_encoding("Invalid UTF-8 encoding.", {first, second, third}); + } + break; + } + case 4: { + const auto second = static_cast(*current++); + const auto third = static_cast(*current++); + const auto fourth = static_cast(*current++); + const bool is_valid = utf8::validate(first, second, third, fourth); + if FK_YAML_UNLIKELY (!is_valid) { + throw fkyaml::invalid_encoding("Invalid UTF-8 encoding.", {first, second, third, fourth}); + } + break; + } + default: // LCOV_EXCL_LINE + unreachable(); // LCOV_EXCL_LINE + } } - // m_inner_iterator_type == iterator_t::MAPPING: - return m_iterator_holder.mapping_iterator->second; + return str_view {m_buffer.begin(), m_buffer.end()}; } - /// @brief A compound assignment operator by sum of the Iterator class. - /// @param i The difference from this Iterator object with which it moves forward. - /// @return Iterator& Reference to this Iterator object. - iterator& operator+=(difference_type i) noexcept { - switch (m_inner_iterator_type) { - case iterator_t::SEQUENCE: - std::advance(m_iterator_holder.sequence_iterator, i); - break; - case iterator_t::MAPPING: - std::advance(m_iterator_holder.mapping_iterator, i); - break; + /// @brief The concrete implementation of get_buffer_view() for UTF-16 encoded inputs. + /// @return View into the UTF-8 encoded input buffer contents. + str_view get_buffer_view_utf16() { + FK_YAML_ASSERT(m_encode_type == utf_encode_t::UTF_16BE || m_encode_type == utf_encode_t::UTF_16LE); + + int shift_bits[2] {0, 0}; + if (m_encode_type == utf_encode_t::UTF_16BE) { + shift_bits[0] = 8; + } + else { // m_encode_type == utf_encode_t::UTF_16LE + shift_bits[1] = 8; } - return *this; - } - /// @brief A plus operator of the iterator class. - /// @param i The difference from this iterator object. - /// @return iterator An iterator object which has been added @a i. - iterator operator+(difference_type i) const noexcept { - auto result = *this; - result += i; - return result; - } + char chars[2] = {0, 0}; + std::array encoded_buffer {{0, 0}}; + uint32_t encoded_buf_size {0}; + std::array utf8_buffer {{0, 0, 0, 0}}; + uint32_t utf8_buf_size {0}; - /// @brief An pre-increment operator of the iterator class. - /// @return iterator& Reference to this iterator object. - iterator& operator++() noexcept { - switch (m_inner_iterator_type) { - case iterator_t::SEQUENCE: - std::advance(m_iterator_holder.sequence_iterator, 1); - break; - case iterator_t::MAPPING: - std::advance(m_iterator_holder.mapping_iterator, 1); - break; - } - return *this; - } + do { + while (encoded_buf_size < 2) { + m_istream->read(&chars[0], 2); + const std::streamsize size = m_istream->gcount(); + if FK_YAML_UNLIKELY (size != 2) { + break; + } - /// @brief A post-increment operator of the iterator class. - /// @return iterator An iterator object which has been incremented. - iterator operator++(int) & noexcept { - auto result = *this; - ++(*this); - return result; - } + const auto utf16 = static_cast( + (static_cast(chars[0]) << shift_bits[0]) | + (static_cast(chars[1]) << shift_bits[1])); - /// @brief A compound assignment operator by difference of the iterator class. - /// @param i The difference from this iterator object with which it moves backward. - /// @return iterator& Reference to this iterator object. - iterator& operator-=(difference_type i) noexcept { - return operator+=(-i); - } + if FK_YAML_LIKELY (utf16 != static_cast(0x000Du)) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) + encoded_buffer[encoded_buf_size++] = utf16; + } + } - /// @brief A minus operator of the iterator class. - /// @param i The difference from this iterator object. - /// @return iterator An iterator object from which has been subtracted @ i. - iterator operator-(difference_type i) const noexcept { - auto result = *this; - result -= i; - return result; - } + uint32_t consumed_size = 0; + utf8::from_utf16(encoded_buffer, utf8_buffer, consumed_size, utf8_buf_size); - /// @brief A pre-decrement operator of the iterator class. - /// @return iterator& Reference to this iterator object. - iterator& operator--() noexcept { - switch (m_inner_iterator_type) { - case iterator_t::SEQUENCE: - std::advance(m_iterator_holder.sequence_iterator, -1); - break; - case iterator_t::MAPPING: - std::advance(m_iterator_holder.mapping_iterator, -1); - break; - } - return *this; - } + if FK_YAML_LIKELY (consumed_size == 1) { + encoded_buffer[0] = encoded_buffer[1]; + } + encoded_buf_size -= consumed_size; - /// @brief A post-decrement operator of the iterator class - /// @return iterator An iterator object which has been decremented. - iterator operator--(int) & noexcept { - auto result = *this; - --(*this); - return result; + m_buffer.append(reinterpret_cast(utf8_buffer.data()), utf8_buf_size); + } while (!m_istream->eof()); + + return str_view {m_buffer.begin(), m_buffer.end()}; } - /// @brief An equal-to operator of the iterator class. - /// @param rhs An iterator object to be compared with this iterator object. - /// @return true This iterator object is equal to the other. - /// @return false This iterator object is not equal to the other. - template < - typename Iterator, - enable_if_t< - disjunction, std::is_same>::value, int> = 0> - bool operator==(const Iterator& rhs) const { - if FK_YAML_UNLIKELY (m_inner_iterator_type != rhs.m_inner_iterator_type) { - throw fkyaml::exception("Cannot compare iterators of different container types."); - } + /// @brief The concrete implementation of get_buffer_view() for UTF-32 encoded inputs. + /// @return View into the UTF-8 encoded input buffer contents. + str_view get_buffer_view_utf32() { + FK_YAML_ASSERT(m_encode_type == utf_encode_t::UTF_32BE || m_encode_type == utf_encode_t::UTF_32LE); - if (m_inner_iterator_type == iterator_t::SEQUENCE) { - return (m_iterator_holder.sequence_iterator == rhs.m_iterator_holder.sequence_iterator); + int shift_bits[4] {0, 0, 0, 0}; + if (m_encode_type == utf_encode_t::UTF_32BE) { + shift_bits[0] = 24; + shift_bits[1] = 16; + shift_bits[2] = 8; + } + else { // m_encode_type == utf_encode_t::UTF_32LE + shift_bits[1] = 8; + shift_bits[2] = 16; + shift_bits[3] = 24; } - // m_inner_iterator_type == iterator_t::MAPPING - return (m_iterator_holder.mapping_iterator == rhs.m_iterator_holder.mapping_iterator); - } + char chars[4] = {0, 0, 0, 0}; + std::array utf8_buffer {{0, 0, 0, 0}}; + uint32_t utf8_buf_size {0}; - /// @brief An not-equal-to operator of the iterator class. - /// @param rhs An iterator object to be compared with this iterator object. - /// @return true This iterator object is not equal to the other. - /// @return false This iterator object is equal to the other. - template < - typename Iterator, - enable_if_t< - disjunction, std::is_same>::value, int> = 0> - bool operator!=(const Iterator& rhs) const { - return !operator==(rhs); - } + do { + m_istream->read(&chars[0], 4); + const std::streamsize size = m_istream->gcount(); + if FK_YAML_UNLIKELY (size != 4) { + break; + } - /// @brief A less-than operator of the iterator class. - /// @param rhs An iterator object to be compared with this iterator object. - /// @return true This iterator object is less than the other. - /// @return false This iterator object is not less than the other. - template < - typename Iterator, - enable_if_t< - disjunction, std::is_same>::value, int> = 0> - bool operator<(const Iterator& rhs) const { - if FK_YAML_UNLIKELY (m_inner_iterator_type != rhs.m_inner_iterator_type) { - throw fkyaml::exception("Cannot compare iterators of different container types."); - } + const auto utf32 = static_cast( + (static_cast(chars[0]) << shift_bits[0]) | (static_cast(chars[1]) << shift_bits[1]) | + (static_cast(chars[2]) << shift_bits[2]) | (static_cast(chars[3]) << shift_bits[3])); - if FK_YAML_UNLIKELY (m_inner_iterator_type == iterator_t::MAPPING) { - throw fkyaml::exception("Cannot compare order of iterators of the mapping container type"); - } + if FK_YAML_LIKELY (utf32 != char32_t(0x0000000Du)) { + utf8::from_utf32(utf32, utf8_buffer, utf8_buf_size); + m_buffer.append(reinterpret_cast(utf8_buffer.data()), utf8_buf_size); + } + } while (!m_istream->eof()); - return (m_iterator_holder.sequence_iterator < rhs.m_iterator_holder.sequence_iterator); + return str_view {m_buffer.begin(), m_buffer.end()}; } - /// @brief A less-than-or-equal-to operator of the iterator class. - /// @param rhs An iterator object to be compared with this iterator object. - /// @return true This iterator object is either less than or equal to the other. - /// @return false This iterator object is neither less than nor equal to the other. - template < - typename Iterator, - enable_if_t< - disjunction, std::is_same>::value, int> = 0> - bool operator<=(const Iterator& rhs) const { - return !rhs.operator<(*this); - } +private: + /// A pointer to the input stream object. + std::istream* m_istream {nullptr}; + /// The encoding type for this input adapter. + utf_encode_t m_encode_type {utf_encode_t::UTF_8}; + /// The normalized owned buffer. + std::string m_buffer; +}; - /// @brief A greater-than operator of the iterator class. - /// @param rhs An iterator object to be compared with this iterator object. - /// @return true This iterator object is greater than the other. - /// @return false This iterator object is not greater than the other. - template < - typename Iterator, - enable_if_t< - disjunction, std::is_same>::value, int> = 0> - bool operator>(const Iterator& rhs) const { - return !operator<=(rhs); - } +///////////////////////////////// +// input_adapter providers // +///////////////////////////////// - /// @brief A greater-than-or-equal-to operator of the iterator class. - /// @param rhs An iterator object to be compared with this iterator object. - /// @return true This iterator object is either greater than or equal to the other. - /// @return false This iterator object is neither greater than nor equal to the other. - template < - typename Iterator, - enable_if_t< - disjunction, std::is_same>::value, int> = 0> - bool operator>=(const Iterator& rhs) const { - return !operator<(rhs); - } +/// @brief A concrete factory method for iterator_input_adapter objects with iterators. +/// @tparam ItrType An iterator type. +/// @param begin The beginning of iterators. +/// @param end The end of iterators. +/// @param is_contiguous Whether iterators refer to a contiguous byte array. +/// @return An iterator_input_adapter object for the target iterator type. +template +inline iterator_input_adapter create_iterator_input_adapter(ItrType begin, ItrType end, bool is_contiguous) { + const utf_encode_t encode_type = utf_encode_detector::detect(begin, end); + return iterator_input_adapter(begin, end, encode_type, is_contiguous); +} -public: - /// @brief Get the type of the internal iterator implementation. - /// @return iterator_t The type of the internal iterator implementation. - iterator_t type() const noexcept { - return m_inner_iterator_type; +/// @brief A factory method for iterator_input_adapter objects with iterator values. +/// @tparam ItrType An iterator type. +/// @param begin The beginning of iterators. +/// @param end The end of iterators. +/// @return iterator_input_adapter An iterator_input_adapter object for the target iterator type. +template +inline iterator_input_adapter input_adapter(ItrType begin, ItrType end) { + bool is_contiguous = true; + const auto size = std::distance(begin, end); + + // Check if `begin` & `end` are contiguous iterators. + // Getting distance between begin and (end - 1) avoids dereferencing an invalid sentinel. + if FK_YAML_LIKELY (size > 0) { + using char_ptr_t = remove_cvref_t::pointer>; + char_ptr_t p_begin = &*begin; + char_ptr_t p_second_last = &*std::next(begin, size - 1); + is_contiguous = (p_second_last - p_begin == size); } + return create_iterator_input_adapter(begin, end, is_contiguous); +} + +/// @brief A factory method for iterator_input_adapter objects with C-style arrays. +/// @tparam T A type of arrayed objects. +/// @tparam N A size of an array. +/// @return decltype(input_adapter(array, array + N)) An iterator_input_adapter object for the target array. +template +inline auto input_adapter(T (&array)[N]) -> decltype(create_iterator_input_adapter(array, array + (N - 1), true)) { + return create_iterator_input_adapter(array, array + (N - 1), true); +} - /// @brief Get the mapping key node of the current iterator. - /// @return The mapping key node of the current iterator. - const typename value_type::mapping_type::key_type& key() const { - if FK_YAML_UNLIKELY (m_inner_iterator_type == iterator_t::SEQUENCE) { - throw fkyaml::exception("Cannot retrieve key from non-mapping iterators."); - } +/// @brief A namespace to implement container_input_adapter_factory for internal use. +namespace input_adapter_factory { - return m_iterator_holder.mapping_iterator->first; - } +using std::begin; +using std::end; - /// @brief Get reference to the YAML node of the current iterator. - /// @return Reference to the YAML node of the current iterator. - reference value() const noexcept { - return operator*(); - } +/// @brief A factory of input adapters for containers. +/// @tparam ContainerType A container type. +/// @tparam typename N/A +template +struct container_input_adapter_factory {}; -private: - /// A type of the internally-held iterator. - iterator_t m_inner_iterator_type {iterator_t::SEQUENCE}; - /// A holder of actual iterators. - iterator_holder m_iterator_holder {}; +/// @brief A partial specialization of container_input_adapter_factory if begin()/end() are available for ContainerType. +/// @tparam ContainerType A container type. +template +struct container_input_adapter_factory< + ContainerType, void_t()), end(std::declval()))>> { + /// A type for resulting input adapter object. + using adapter_type = + decltype(input_adapter(begin(std::declval()), end(std::declval()))); + + /// @brief A factory method of input adapter objects for the target container objects. + /// @param container A container-like input object. + /// @return adapter_type An iterator_input_adapter object. + static adapter_type create(const ContainerType& container) { + return input_adapter(begin(container), end(container)); + } }; -/// @brief Get reference to a mapping key node. -/// @tparam ValueType The iterator value type. -/// @tparam I The element index. -/// @param i An iterator object. -/// @return Reference to a mapping key node. -template = 0> -inline auto get(const iterator& i) -> decltype(i.key()) { - return i.key(); -} +} // namespace input_adapter_factory -/// @brief Get reference to a mapping value node. -/// @tparam ValueType The iterator value type. -/// @tparam I The element index -/// @param i An iterator object. -/// @return Reference to a mapping value node. -template = 0> -inline auto get(const iterator& i) -> decltype(i.value()) { - return i.value(); +/// @brief A factory method for iterator_input_adapter objects with containers. +/// @tparam ContainerType A container type. +/// @param container A container object. +/// @return input_adapter_factory::container_input_adapter_factory::adapter_type +template +inline typename input_adapter_factory::container_input_adapter_factory::adapter_type input_adapter( + const ContainerType& container) { + return input_adapter_factory::container_input_adapter_factory::create(container); } -FK_YAML_DETAIL_NAMESPACE_END - -namespace std { - -#ifdef __clang__ -// clang emits warnings against mixed usage of class/struct for tuple_size/tuple_element. -// see also: https://groups.google.com/a/isocpp.org/g/std-discussion/c/QC-AMb5oO1w -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wmismatched-tags" -#endif +/// @brief A factory method for file_input_adapter objects with C-style file handles. +/// @param file A file handle. +/// @return file_input_adapter A file_input_adapter object. +inline file_input_adapter input_adapter(std::FILE* file) { + if FK_YAML_UNLIKELY (!file) { + throw fkyaml::exception("Invalid FILE object pointer."); + } -/// @brief Partial specialization of std::tuple_size for iterator class. -/// @tparam ValueType The iterator value type. -template -// NOLINTNEXTLINE(cert-dcl58-cpp) -struct tuple_size<::fkyaml::detail::iterator> : integral_constant {}; + const utf_encode_t encode_type = file_utf_encode_detector::detect(file); + return file_input_adapter(file, encode_type); +} -/// @brief Partial specialization of std::tuple_element for iterator class. -/// @tparam ValueType The iterator value type. -/// @tparam I The element index. -template -// NOLINTNEXTLINE(cert-dcl58-cpp) -struct tuple_element> { - using type = decltype(get(std::declval<::fkyaml::detail::iterator>())); -}; +/// @brief A factory method for stream_input_adapter objects with std::istream objects. +/// @param stream An input stream. +/// @return stream_input_adapter A stream_input_adapter object. +inline stream_input_adapter input_adapter(std::istream& stream) { + if FK_YAML_UNLIKELY (!stream.good()) { + throw fkyaml::exception("Invalid stream."); + } -#ifdef __clang__ -#pragma clang diagnostic pop -#endif + const utf_encode_t encode_type = stream_utf_encode_detector::detect(stream); + return stream_input_adapter(stream, encode_type); +} -} // namespace std +FK_YAML_DETAIL_NAMESPACE_END -#endif /* FK_YAML_DETAIL_ITERATOR_HPP */ +#endif /* FK_YAML_DETAIL_INPUT_INPUT_ADAPTER_HPP */ -// #include +// #include // _______ __ __ __ _____ __ __ __ // | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library // | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 @@ -10344,314 +10269,430 @@ struct tuple_element> { // SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani // SPDX-License-Identifier: MIT -#ifndef FK_YAML_DETAIL_MAP_RANGE_PROXY_HPP -#define FK_YAML_DETAIL_MAP_RANGE_PROXY_HPP +#ifndef FK_YAML_DETAIL_ITERATOR_HPP +#define FK_YAML_DETAIL_ITERATOR_HPP + +#include +#include // #include // #include +// #include -FK_YAML_DETAIL_NAMESPACE_BEGIN - -/// @brief A helper iterator class which wraps a mapping iterator object. -/// @tparam Iterator The base iterator type. -template -class map_iterator_proxy { -public: - /// @brief The type of the pointed-to elements by base iterators. - using value_type = Iterator; - - /// @brief The type to represent difference between the pointed-to elements by base iterators. - using difference_type = std::ptrdiff_t; - - /// @brief The type of the pointed-to element references by base iterators. - using reference = value_type&; - - /// @brief The type of the pointed-to element pointers by base iterators. - using pointer = value_type*; - - /// @brief The iterator category. - using iterator_category = std::forward_iterator_tag; - - /// @brief Constructs a map_iterator_proxy object. - map_iterator_proxy() = default; - - /// @brief Constructs a map_iterator_proxy object with an Iterator object. - /// @param i A base iterator object. - map_iterator_proxy(const Iterator& i) noexcept - : m_base_iterator(i) { - } - - /// @brief Copy constructs a map_iterator_proxy object. - map_iterator_proxy(const map_iterator_proxy&) = default; - - /// @brief Copy assigns a map_iterator_proxy object. - map_iterator_proxy& operator=(const map_iterator_proxy&) = default; - - /// @brief Move constructs a map_iterator_proxy object. - map_iterator_proxy(map_iterator_proxy&&) = default; - - /// @brief Move assigns a map_iterator_proxy object. - map_iterator_proxy& operator=(map_iterator_proxy&&) = default; - - /// @brief Destructs a map_iterator_proxy object. - ~map_iterator_proxy() = default; - - /// @brief Get reference to the base iterator object. - /// @return Reference to the base iterator object. - reference operator*() noexcept { - return m_base_iterator; - } - - /// @brief Get pointer to the base iterator object. - /// @return Pointer to the base iterator object. - pointer operator->() noexcept { - return &m_base_iterator; - } - - /// @brief Pre-increments the base iterator object. - /// @return Reference to this map_iterator_proxy object. - map_iterator_proxy& operator++() noexcept { - ++m_base_iterator; - return *this; - } - - /// @brief Post-increments the base iterator object. - /// @return A map_iterator_proxy object with its base iterator incremented. - map_iterator_proxy operator++(int) & noexcept { - auto result = *this; - ++(*this); - return result; - } - - /// @brief Check equality between map_iterator_proxy objects. - /// @param rhs A map_iterator_proxy object to compare with. - /// @return true if this map_iterator_proxy object is equal to `rhs`, false otherwise. - bool operator==(const map_iterator_proxy& rhs) const noexcept { - return m_base_iterator == rhs.m_base_iterator; - } - /// @brief Check inequality between map_iterator_proxy objects. - /// @param rhs A map_iterator_proxy object to compare with. - /// @return true if this map_iterator_proxy object is not equal to `rhs`, false otherwise. - bool operator!=(const map_iterator_proxy& rhs) const noexcept { - return m_base_iterator != rhs.m_base_iterator; - } +FK_YAML_DETAIL_NAMESPACE_BEGIN - /// @brief Get the mapping key node pointed by the base iterator. - /// @return Reference to the mapping key node. - typename Iterator::reference key() const { - return m_base_iterator.key(); - } +/// @brief The template definitions of type information used in @ref Iterator class +/// @tparam ValueType The type of iterated elements. +template +struct iterator_traits { + /// A type of iterated elements. + using value_type = typename ValueType::value_type; + /// A type to represent difference between iterators. + using difference_type = typename ValueType::difference_type; + /// A type of an element pointer. + using pointer = typename ValueType::pointer; + /// A type of reference to an element. + using reference = typename ValueType::reference; +}; - /// @brief Get the mapping value node pointed by the base iterator. - /// @return Reference to the mapping value node. - typename Iterator::reference value() const noexcept { - return m_base_iterator.value(); - } +/// @brief A specialization of @ref iterator_traits for constant value types. +/// @tparam ValueType The type of iterated elements. +template +struct iterator_traits { + /// A type of iterated elements. + using value_type = typename ValueType::value_type; + /// A type to represent difference between iterators. + using difference_type = typename ValueType::difference_type; + /// A type of a constant element pointer. + using pointer = typename ValueType::const_pointer; + /// A type of constant reference to an element. + using reference = typename ValueType::const_reference; +}; -private: - /// The base iterator object. - Iterator m_base_iterator {}; +/// @brief Definitions of iterator types for iterators internally held. +enum class iterator_t : std::uint8_t { + SEQUENCE, //!< sequence iterator type. + MAPPING, //!< mapping iterator type. }; -/// @brief A helper struct which allows accessing node iterator member functions in range-based for loops. -/// @tparam BasicNodeType A basic_node template instance type. +/// @brief The actual storage for iterators internally held in iterator. template -class map_range_proxy { +struct iterator_holder { static_assert( is_basic_node::value, - "map_range_proxy only accepts a basic_node type as its template parameter."); - -public: - /// @brief The type of non-const iterators. - using iterator = map_iterator_proxy::value, typename BasicNodeType::const_iterator, - typename BasicNodeType::iterator>::type>; + "iterator_holder class only accepts a basic_node as its template parameter."); - /// @brief The type of const iterators. - using const_iterator = map_iterator_proxy; + /// A sequence iterator object. + typename BasicNodeType::sequence_type::iterator sequence_iterator {}; + /// A mapping iterator object. + typename BasicNodeType::mapping_type::iterator mapping_iterator {}; +}; - /// @brief Constructs a map_range_proxy object with a BasicNodeType object. - /// @param map A mapping node object. - map_range_proxy(BasicNodeType& map) noexcept - : mp_map(&map) { - } +/// @brief A class which holds iterators either of sequence or mapping type +/// @tparam ValueType The type of iterated elements. +template +class iterator { + /// @brief The iterator type with ValueType of different const-ness. + using other_iterator_type = typename std::conditional< + std::is_const::value, iterator::type>, + iterator>::type; - /// @brief Copy constructs a map_range_proxy object. - map_range_proxy(const map_range_proxy&) = default; + friend other_iterator_type; - /// @brief Copy assigns a map_range_proxy object. - /// @return Reference to this map_range_proxy object. - map_range_proxy& operator=(const map_range_proxy&) = default; +public: + /// A type for iterator traits of instantiated @Iterator template class. + using iterator_traits_type = iterator_traits; - /// @brief Move constructs a map_range_proxy object. - map_range_proxy(map_range_proxy&&) = default; + /// A type for iterator category tag. + using iterator_category = std::bidirectional_iterator_tag; + /// A type of iterated element. + using value_type = typename iterator_traits_type::value_type; + /// A type to represent differences between iterators. + using difference_type = typename iterator_traits_type::difference_type; + /// A type of an element pointer. + using pointer = typename iterator_traits_type::pointer; + /// A type of reference to an element. + using reference = typename iterator_traits_type::reference; - /// @brief Move assigns a map_range_proxy object. - /// @return Reference to this map_range_proxy object. - map_range_proxy& operator=(map_range_proxy&&) = default; + static_assert(is_basic_node::value, "iterator class only accepts a basic_node as its value type."); - /// @brief Destructs a map_range_proxy object. - ~map_range_proxy() = default; + /// @brief Constructs an iterator object. + iterator() = default; - /// @brief Get an iterator to the first element. - /// @return An iterator to the first element. - iterator begin() noexcept { - return {mp_map->begin()}; + /// @brief Construct a new iterator object with sequence iterator object. + /// @param[in] itr An sequence iterator object. + iterator(const typename value_type::sequence_type::iterator& itr) noexcept { + m_iterator_holder.sequence_iterator = itr; } - /// @brief Get a const iterator to the first element. - /// @return A const iterator to the first element. - const_iterator begin() const noexcept { - return {mp_map->cbegin()}; + /// @brief Construct a new iterator object with mapping iterator object. + /// @param[in] itr An mapping iterator object. + iterator(const typename value_type::mapping_type::iterator& itr) noexcept + : m_inner_iterator_type(iterator_t::MAPPING) { + m_iterator_holder.mapping_iterator = itr; } - /// @brief Get an iterator to the past-the-last element. - /// @return An iterator to the past-the-last element. - iterator end() noexcept { - return {mp_map->end()}; + /// @brief Copy constructs an iterator. + iterator(const iterator&) = default; + + /// @brief Copy constructs an iterator from another iterator with different const-ness in ValueType. + /// @note This copy constructor is not defined if ValueType is not const to avoid const removal from ValueType. + /// @tparam OtherIterator The iterator type to copy from. + /// @param other An iterator to copy from with different const-ness in ValueType. + template < + typename OtherIterator, + enable_if_t< + conjunction, std::is_const>::value, int> = 0> + iterator(const OtherIterator& other) noexcept + : m_inner_iterator_type(other.m_inner_iterator_type), + m_iterator_holder(other.m_iterator_holder) { } - /// @brief Get a const iterator to the past-the-last element. - /// @return A const iterator to the past-the-last element. - const_iterator end() const noexcept { - return {mp_map->cend()}; + /// @brief A copy assignment operator of the iterator class. + iterator& operator=(const iterator&) = default; + + template < + typename OtherIterator, + enable_if_t< + conjunction, std::is_const>::value, int> = 0> + iterator& operator=(const OtherIterator& other) noexcept { + m_inner_iterator_type = other.m_inner_iterator_type; + m_iterator_holder = other.m_iterator_holder; + return *this; } -private: - /// Pointer to the mapping node object. (non-null) - BasicNodeType* mp_map {nullptr}; -}; + /// @brief Move constructs an iterator. + iterator(iterator&&) = default; -FK_YAML_DETAIL_NAMESPACE_END + /// @brief A move assignment operator of the iterator class. + iterator& operator=(iterator&&) = default; -#endif /* FK_YAML_DETAIL_MAP_RANGE_PROXY_HPP */ + /// @brief Destroys an iterator. + ~iterator() = default; -// #include + /// @brief An arrow operator of the iterator class. + /// @return pointer A pointer to the BasicNodeType object internally referenced by the actual iterator object. + pointer operator->() noexcept { + if (m_inner_iterator_type == iterator_t::SEQUENCE) { + return &(*(m_iterator_holder.sequence_iterator)); + } -// #include + // m_inner_iterator_type == iterator_t::MAPPING: + return &(m_iterator_holder.mapping_iterator->second); + } -// #include + /// @brief A dereference operator of the iterator class. + /// @return reference Reference to the Node object internally referenced by the actual iterator object. + reference operator*() const noexcept { + if (m_inner_iterator_type == iterator_t::SEQUENCE) { + return *(m_iterator_holder.sequence_iterator); + } -// #include + // m_inner_iterator_type == iterator_t::MAPPING: + return m_iterator_holder.mapping_iterator->second; + } -// #include + /// @brief A compound assignment operator by sum of the Iterator class. + /// @param i The difference from this Iterator object with which it moves forward. + /// @return Iterator& Reference to this Iterator object. + iterator& operator+=(difference_type i) noexcept { + switch (m_inner_iterator_type) { + case iterator_t::SEQUENCE: + std::advance(m_iterator_holder.sequence_iterator, i); + break; + case iterator_t::MAPPING: + std::advance(m_iterator_holder.mapping_iterator, i); + break; + } + return *this; + } -// #include -// _______ __ __ __ _____ __ __ __ -// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library -// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 -// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML -// -// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani -// SPDX-License-Identifier: MIT + /// @brief A plus operator of the iterator class. + /// @param i The difference from this iterator object. + /// @return iterator An iterator object which has been added @a i. + iterator operator+(difference_type i) const noexcept { + auto result = *this; + result += i; + return result; + } -#ifndef FK_YAML_DETAIL_NODE_REF_STORAGE_HPP -#define FK_YAML_DETAIL_NODE_REF_STORAGE_HPP + /// @brief An pre-increment operator of the iterator class. + /// @return iterator& Reference to this iterator object. + iterator& operator++() noexcept { + switch (m_inner_iterator_type) { + case iterator_t::SEQUENCE: + std::advance(m_iterator_holder.sequence_iterator, 1); + break; + case iterator_t::MAPPING: + std::advance(m_iterator_holder.mapping_iterator, 1); + break; + } + return *this; + } -#include -#include -#include + /// @brief A post-increment operator of the iterator class. + /// @return iterator An iterator object which has been incremented. + iterator operator++(int) & noexcept { + auto result = *this; + ++(*this); + return result; + } -// #include + /// @brief A compound assignment operator by difference of the iterator class. + /// @param i The difference from this iterator object with which it moves backward. + /// @return iterator& Reference to this iterator object. + iterator& operator-=(difference_type i) noexcept { + return operator+=(-i); + } -// #include + /// @brief A minus operator of the iterator class. + /// @param i The difference from this iterator object. + /// @return iterator An iterator object from which has been subtracted @ i. + iterator operator-(difference_type i) const noexcept { + auto result = *this; + result -= i; + return result; + } -// #include + /// @brief A pre-decrement operator of the iterator class. + /// @return iterator& Reference to this iterator object. + iterator& operator--() noexcept { + switch (m_inner_iterator_type) { + case iterator_t::SEQUENCE: + std::advance(m_iterator_holder.sequence_iterator, -1); + break; + case iterator_t::MAPPING: + std::advance(m_iterator_holder.mapping_iterator, -1); + break; + } + return *this; + } + + /// @brief A post-decrement operator of the iterator class + /// @return iterator An iterator object which has been decremented. + iterator operator--(int) & noexcept { + auto result = *this; + --(*this); + return result; + } + + /// @brief An equal-to operator of the iterator class. + /// @param rhs An iterator object to be compared with this iterator object. + /// @return true This iterator object is equal to the other. + /// @return false This iterator object is not equal to the other. + template < + typename Iterator, + enable_if_t< + disjunction, std::is_same>::value, int> = 0> + bool operator==(const Iterator& rhs) const { + if FK_YAML_UNLIKELY (m_inner_iterator_type != rhs.m_inner_iterator_type) { + throw fkyaml::exception("Cannot compare iterators of different container types."); + } + + if (m_inner_iterator_type == iterator_t::SEQUENCE) { + return (m_iterator_holder.sequence_iterator == rhs.m_iterator_holder.sequence_iterator); + } + // m_inner_iterator_type == iterator_t::MAPPING + return (m_iterator_holder.mapping_iterator == rhs.m_iterator_holder.mapping_iterator); + } -FK_YAML_DETAIL_NAMESPACE_BEGIN + /// @brief An not-equal-to operator of the iterator class. + /// @param rhs An iterator object to be compared with this iterator object. + /// @return true This iterator object is not equal to the other. + /// @return false This iterator object is equal to the other. + template < + typename Iterator, + enable_if_t< + disjunction, std::is_same>::value, int> = 0> + bool operator!=(const Iterator& rhs) const { + return !operator==(rhs); + } -/// @brief A temporal storage for basic_node class objects. -/// @note This class makes it easier to handle lvalue basic_node objects in basic_node ctor with std::initializer_list. -/// @tparam BasicNodeType A basic_node template instance type. -template -class node_ref_storage { - static_assert(is_basic_node::value, "node_ref_storage only accepts basic_node<...>"); + /// @brief A less-than operator of the iterator class. + /// @param rhs An iterator object to be compared with this iterator object. + /// @return true This iterator object is less than the other. + /// @return false This iterator object is not less than the other. + template < + typename Iterator, + enable_if_t< + disjunction, std::is_same>::value, int> = 0> + bool operator<(const Iterator& rhs) const { + if FK_YAML_UNLIKELY (m_inner_iterator_type != rhs.m_inner_iterator_type) { + throw fkyaml::exception("Cannot compare iterators of different container types."); + } - using node_type = BasicNodeType; + if FK_YAML_UNLIKELY (m_inner_iterator_type == iterator_t::MAPPING) { + throw fkyaml::exception("Cannot compare order of iterators of the mapping container type"); + } -public: - /// @brief Construct a new node ref storage object with an rvalue basic_node object. - /// @param n An rvalue basic_node object. - explicit node_ref_storage(node_type&& n) noexcept(std::is_nothrow_move_constructible::value) - : m_owned_value(std::move(n)) { + return (m_iterator_holder.sequence_iterator < rhs.m_iterator_holder.sequence_iterator); } - /// @brief Construct a new node ref storage object with an lvalue basic_node object. - /// @param n An lvalue basic_node object. - explicit node_ref_storage(const node_type& n) noexcept - : m_value_ref(&n) { + /// @brief A less-than-or-equal-to operator of the iterator class. + /// @param rhs An iterator object to be compared with this iterator object. + /// @return true This iterator object is either less than or equal to the other. + /// @return false This iterator object is neither less than nor equal to the other. + template < + typename Iterator, + enable_if_t< + disjunction, std::is_same>::value, int> = 0> + bool operator<=(const Iterator& rhs) const { + return !rhs.operator<(*this); } - /// @brief Construct a new node ref storage object with a std::initializer_list object. - /// @param init A std::initializer_list object. - node_ref_storage(std::initializer_list init) - : m_owned_value(init) { + /// @brief A greater-than operator of the iterator class. + /// @param rhs An iterator object to be compared with this iterator object. + /// @return true This iterator object is greater than the other. + /// @return false This iterator object is not greater than the other. + template < + typename Iterator, + enable_if_t< + disjunction, std::is_same>::value, int> = 0> + bool operator>(const Iterator& rhs) const { + return !operator<=(rhs); } - /// @brief Construct a new node ref storage object with variadic template arguments - /// @tparam Args Types of arguments to construct a basic_node object. - /// @param args Arguments to construct a basic_node object. - template ::value, int> = 0> - node_ref_storage(Args&&... args) - : m_owned_value(std::forward(args)...) { + /// @brief A greater-than-or-equal-to operator of the iterator class. + /// @param rhs An iterator object to be compared with this iterator object. + /// @return true This iterator object is either greater than or equal to the other. + /// @return false This iterator object is neither greater than nor equal to the other. + template < + typename Iterator, + enable_if_t< + disjunction, std::is_same>::value, int> = 0> + bool operator>=(const Iterator& rhs) const { + return !operator<(rhs); } - // allow only move construct/assignment - node_ref_storage(const node_ref_storage&) = delete; - node_ref_storage(node_ref_storage&&) = default; - node_ref_storage& operator=(const node_ref_storage&) = delete; - node_ref_storage& operator=(node_ref_storage&&) = default; +public: + /// @brief Get the type of the internal iterator implementation. + /// @return iterator_t The type of the internal iterator implementation. + iterator_t type() const noexcept { + return m_inner_iterator_type; + } - ~node_ref_storage() = default; + /// @brief Get the mapping key node of the current iterator. + /// @return The mapping key node of the current iterator. + const typename value_type::mapping_type::key_type& key() const { + if FK_YAML_UNLIKELY (m_inner_iterator_type == iterator_t::SEQUENCE) { + throw fkyaml::exception("Cannot retrieve key from non-mapping iterators."); + } -public: - /// @brief An arrow operator for node_ref_storage objects. - /// @return const node_type* A constant pointer to a basic_node object. - const node_type* operator->() const noexcept { - return m_value_ref ? m_value_ref : &m_owned_value; + return m_iterator_holder.mapping_iterator->first; } - /// @brief Releases a basic_node object internally held. - /// @return node_type A basic_node object internally held. - node_type release() const noexcept { - return m_value_ref ? *m_value_ref : std::move(m_owned_value); + /// @brief Get reference to the YAML node of the current iterator. + /// @return Reference to the YAML node of the current iterator. + reference value() const noexcept { + return operator*(); } private: - /// A storage for a basic_node object given with rvalue reference. - mutable node_type m_owned_value = nullptr; - /// A pointer to a basic_node object given with lvalue reference. - const node_type* m_value_ref = nullptr; + /// A type of the internally-held iterator. + iterator_t m_inner_iterator_type {iterator_t::SEQUENCE}; + /// A holder of actual iterators. + iterator_holder m_iterator_holder {}; }; +/// @brief Get reference to a mapping key node. +/// @tparam ValueType The iterator value type. +/// @tparam I The element index. +/// @param i An iterator object. +/// @return Reference to a mapping key node. +template = 0> +inline auto get(const iterator& i) -> decltype(i.key()) { + return i.key(); +} + +/// @brief Get reference to a mapping value node. +/// @tparam ValueType The iterator value type. +/// @tparam I The element index +/// @param i An iterator object. +/// @return Reference to a mapping value node. +template = 0> +inline auto get(const iterator& i) -> decltype(i.value()) { + return i.value(); +} + FK_YAML_DETAIL_NAMESPACE_END -#endif /* FK_YAML_DETAIL_NODE_REF_STORAGE_HPP */ +namespace std { -// #include -// _______ __ __ __ _____ __ __ __ -// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library -// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 -// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML -// -// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani -// SPDX-License-Identifier: MIT +#ifdef __clang__ +// clang emits warnings against mixed usage of class/struct for tuple_size/tuple_element. +// see also: https://groups.google.com/a/isocpp.org/g/std-discussion/c/QC-AMb5oO1w +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmismatched-tags" +#endif -#ifndef FK_YAML_DETAIL_OUTPUT_SERIALIZER_HPP -#define FK_YAML_DETAIL_OUTPUT_SERIALIZER_HPP +/// @brief Partial specialization of std::tuple_size for iterator class. +/// @tparam ValueType The iterator value type. +template +// NOLINTNEXTLINE(cert-dcl58-cpp) +struct tuple_size<::fkyaml::detail::iterator> : integral_constant {}; -#include -#include -#include -#include +/// @brief Partial specialization of std::tuple_element for iterator class. +/// @tparam ValueType The iterator value type. +/// @tparam I The element index. +template +// NOLINTNEXTLINE(cert-dcl58-cpp) +struct tuple_element> { + using type = decltype(get(std::declval<::fkyaml::detail::iterator>())); +}; -// #include +#ifdef __clang__ +#pragma clang diagnostic pop +#endif -// #include +} // namespace std + +#endif /* FK_YAML_DETAIL_ITERATOR_HPP */ + +// #include // _______ __ __ __ _____ __ __ __ // | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library // | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 @@ -10660,471 +10701,295 @@ FK_YAML_DETAIL_NAMESPACE_END // SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani // SPDX-License-Identifier: MIT -#ifndef FK_YAML_DETAIL_CONVERSIONS_TO_STRING_HPP -#define FK_YAML_DETAIL_CONVERSIONS_TO_STRING_HPP - -#include -#include -#include -#include -#include +#ifndef FK_YAML_DETAIL_MAP_RANGE_PROXY_HPP +#define FK_YAML_DETAIL_MAP_RANGE_PROXY_HPP // #include -// #include - -// #include - - -FK_YAML_DETAIL_NAMESPACE_BEGIN - -/// @brief Converts a ValueType object to a string YAML token. -/// @tparam ValueType A source value type. -/// @tparam CharType The type of characters for the conversion result. -/// @param s A resulting output string. -/// @param v A source value. -template -inline void to_string(ValueType v, std::basic_string& s) noexcept; - -/// @brief Specialization of to_string() for null values. -/// @param s A resulting string YAML token. -/// @param (unused) nullptr -template <> -inline void to_string(std::nullptr_t /*unused*/, std::string& s) noexcept { - s = "null"; -} - -/// @brief Specialization of to_string() for booleans. -/// @param s A resulting string YAML token. -/// @param v A boolean source value. -template <> -inline void to_string(bool v, std::string& s) noexcept { - s = v ? "true" : "false"; -} - -/// @brief Specialization of to_string() for integers. -/// @tparam IntegerType An integer type. -/// @param s A resulting string YAML token. -/// @param i An integer source value. -template -inline enable_if_t::value> to_string(IntegerType v, std::string& s) noexcept { - s = std::to_string(v); -} - -/// @brief Specialization of to_string() for floating point numbers. -/// @tparam FloatType A floating point number type. -/// @param s A resulting string YAML token. -/// @param f A floating point number source value. -template -inline enable_if_t::value> to_string(FloatType v, std::string& s) noexcept { - if (std::isnan(v)) { - s = ".nan"; - return; - } - - if (std::isinf(v)) { - if (v == std::numeric_limits::infinity()) { - s = ".inf"; - } - else { - s = "-.inf"; - } - return; - } +// #include - std::ostringstream oss; - oss << v; - s = oss.str(); - // If `v` is actually an integer and no scientific notation is used for serialization, ".0" must be appended. - // The result would cause a roundtrip issue otherwise. https://github.com/fktn-k/fkYAML/issues/405 - const std::size_t pos = s.find_first_of(".e"); - if (pos == std::string::npos) { - s += ".0"; - } -} +FK_YAML_DETAIL_NAMESPACE_BEGIN -FK_YAML_DETAIL_NAMESPACE_END +/// @brief A helper iterator class which wraps a mapping iterator object. +/// @tparam Iterator The base iterator type. +template +class map_iterator_proxy { +public: + /// @brief The type of the pointed-to elements by base iterators. + using value_type = Iterator; -#endif /* FK_YAML_DETAIL_CONVERSIONS_TO_STRING_HPP */ + /// @brief The type to represent difference between the pointed-to elements by base iterators. + using difference_type = std::ptrdiff_t; -// #include + /// @brief The type of the pointed-to element references by base iterators. + using reference = value_type&; -// #include + /// @brief The type of the pointed-to element pointers by base iterators. + using pointer = value_type*; -// #include + /// @brief The iterator category. + using iterator_category = std::forward_iterator_tag; -// #include + /// @brief Constructs a map_iterator_proxy object. + map_iterator_proxy() = default; -// #include + /// @brief Constructs a map_iterator_proxy object with an Iterator object. + /// @param i A base iterator object. + map_iterator_proxy(const Iterator& i) noexcept + : m_base_iterator(i) { + } -// #include + /// @brief Copy constructs a map_iterator_proxy object. + map_iterator_proxy(const map_iterator_proxy&) = default; + /// @brief Copy assigns a map_iterator_proxy object. + map_iterator_proxy& operator=(const map_iterator_proxy&) = default; -FK_YAML_DETAIL_NAMESPACE_BEGIN + /// @brief Move constructs a map_iterator_proxy object. + map_iterator_proxy(map_iterator_proxy&&) = default; -/// @brief A basic implementation of serialization feature for YAML nodes. -/// @tparam BasicNodeType A BasicNode template class instantiation. -template -class basic_serializer { - static_assert(detail::is_basic_node::value, "basic_serializer only accepts basic_node<...>"); + /// @brief Move assigns a map_iterator_proxy object. + map_iterator_proxy& operator=(map_iterator_proxy&&) = default; -public: - /// @brief Construct a new basic_serializer object. - basic_serializer() = default; + /// @brief Destructs a map_iterator_proxy object. + ~map_iterator_proxy() = default; - /// @brief Serialize the given Node value. - /// @param node A Node object to be serialized. - /// @return std::string A serialization result of the given Node value. - std::string serialize(const BasicNodeType& node) { - std::string str {}; - serialize_document(node, str); - return str; - } // LCOV_EXCL_LINE + /// @brief Get reference to the base iterator object. + /// @return Reference to the base iterator object. + reference operator*() noexcept { + return m_base_iterator; + } - std::string serialize_docs(const std::vector& docs) { - std::string str {}; + /// @brief Get pointer to the base iterator object. + /// @return Pointer to the base iterator object. + pointer operator->() noexcept { + return &m_base_iterator; + } - const auto size = static_cast(docs.size()); - for (uint32_t i = 0; i < size; i++) { - serialize_document(docs[i], str); - if (i + 1 < size) { - // Append the end-of-document marker for the next document. - str += "...\n"; - } - } + /// @brief Pre-increments the base iterator object. + /// @return Reference to this map_iterator_proxy object. + map_iterator_proxy& operator++() noexcept { + ++m_base_iterator; + return *this; + } - return str; - } // LCOV_EXCL_LINE + /// @brief Post-increments the base iterator object. + /// @return A map_iterator_proxy object with its base iterator incremented. + map_iterator_proxy operator++(int) & noexcept { + auto result = *this; + ++(*this); + return result; + } -private: - void serialize_document(const BasicNodeType& node, std::string& str) { - const bool dirs_serialized = serialize_directives(node, str); + /// @brief Check equality between map_iterator_proxy objects. + /// @param rhs A map_iterator_proxy object to compare with. + /// @return true if this map_iterator_proxy object is equal to `rhs`, false otherwise. + bool operator==(const map_iterator_proxy& rhs) const noexcept { + return m_base_iterator == rhs.m_base_iterator; + } - // the root node cannot be an alias node. - const bool root_has_props = node.is_anchor() || node.has_tag_name(); + /// @brief Check inequality between map_iterator_proxy objects. + /// @param rhs A map_iterator_proxy object to compare with. + /// @return true if this map_iterator_proxy object is not equal to `rhs`, false otherwise. + bool operator!=(const map_iterator_proxy& rhs) const noexcept { + return m_base_iterator != rhs.m_base_iterator; + } - if (root_has_props) { - if (dirs_serialized) { - str.back() = ' '; // replace the last LF with a white space - } - bool is_anchor_appended = try_append_anchor(node, false, str); - try_append_tag(node, is_anchor_appended, str); - str += "\n"; - } - serialize_node(node, 0, str); + /// @brief Get the mapping key node pointed by the base iterator. + /// @return Reference to the mapping key node. + typename Iterator::reference key() const { + return m_base_iterator.key(); } - /// @brief Serialize the directives if any is applied to the node. - /// @param node The target node. - /// @param str A string to hold serialization result. - /// @return bool true if any directive is serialized, false otherwise. - bool serialize_directives(const BasicNodeType& node, std::string& str) { - const auto& p_meta = node.mp_meta; - bool needs_directive_end = false; + /// @brief Get the mapping value node pointed by the base iterator. + /// @return Reference to the mapping value node. + typename Iterator::reference value() const noexcept { + return m_base_iterator.value(); + } - if (p_meta->is_version_specified) { - str += "%YAML "; - switch (p_meta->version) { - case yaml_version_type::VERSION_1_1: - str += "1.1\n"; - break; - case yaml_version_type::VERSION_1_2: - str += "1.2\n"; - break; - } - needs_directive_end = true; - } +private: + /// The base iterator object. + Iterator m_base_iterator {}; +}; - if (!p_meta->primary_handle_prefix.empty()) { - str += "%TAG ! "; - str += p_meta->primary_handle_prefix; - str += "\n"; - needs_directive_end = true; - } +/// @brief A helper struct which allows accessing node iterator member functions in range-based for loops. +/// @tparam BasicNodeType A basic_node template instance type. +template +class map_range_proxy { + static_assert( + is_basic_node::value, + "map_range_proxy only accepts a basic_node type as its template parameter."); - if (!p_meta->secondary_handle_prefix.empty()) { - str += "%TAG !! "; - str += p_meta->secondary_handle_prefix; - str += "\n"; - needs_directive_end = true; - } +public: + /// @brief The type of non-const iterators. + using iterator = map_iterator_proxy::value, typename BasicNodeType::const_iterator, + typename BasicNodeType::iterator>::type>; - if (!p_meta->named_handle_map.empty()) { - for (const auto& itr : p_meta->named_handle_map) { - str += "%TAG "; - str += itr.first; - str += " "; - str += itr.second; - str += "\n"; - } - needs_directive_end = true; - } + /// @brief The type of const iterators. + using const_iterator = map_iterator_proxy; - if (needs_directive_end) { - str += "---\n"; - } + /// @brief Constructs a map_range_proxy object with a BasicNodeType object. + /// @param map A mapping node object. + map_range_proxy(BasicNodeType& map) noexcept + : mp_map(&map) { + } - return needs_directive_end; + /// @brief Copy constructs a map_range_proxy object. + map_range_proxy(const map_range_proxy&) = default; + + /// @brief Copy assigns a map_range_proxy object. + /// @return Reference to this map_range_proxy object. + map_range_proxy& operator=(const map_range_proxy&) = default; + + /// @brief Move constructs a map_range_proxy object. + map_range_proxy(map_range_proxy&&) = default; + + /// @brief Move assigns a map_range_proxy object. + /// @return Reference to this map_range_proxy object. + map_range_proxy& operator=(map_range_proxy&&) = default; + + /// @brief Destructs a map_range_proxy object. + ~map_range_proxy() = default; + + /// @brief Get an iterator to the first element. + /// @return An iterator to the first element. + iterator begin() noexcept { + return {mp_map->begin()}; } - /// @brief Recursively serialize each Node object. - /// @param node A Node object to be serialized. - /// @param cur_indent The current indent width - /// @param str A string to hold serialization result. - void serialize_node(const BasicNodeType& node, const uint32_t cur_indent, std::string& str) { - switch (node.get_type()) { - case node_type::SEQUENCE: - if (node.size() == 0) { - str += "[]\n"; - return; - } - for (const auto& seq_item : node) { - insert_indentation(cur_indent, str); - str += "-"; + /// @brief Get a const iterator to the first element. + /// @return A const iterator to the first element. + const_iterator begin() const noexcept { + return {mp_map->cbegin()}; + } - const bool is_appended = try_append_alias(seq_item, true, str); - if (is_appended) { - str += "\n"; - continue; - } + /// @brief Get an iterator to the past-the-last element. + /// @return An iterator to the past-the-last element. + iterator end() noexcept { + return {mp_map->end()}; + } - try_append_anchor(seq_item, true, str); - try_append_tag(seq_item, true, str); + /// @brief Get a const iterator to the past-the-last element. + /// @return A const iterator to the past-the-last element. + const_iterator end() const noexcept { + return {mp_map->cend()}; + } - const bool is_scalar = seq_item.is_scalar(); - if (is_scalar) { - str += " "; - serialize_node(seq_item, cur_indent, str); - str += "\n"; - continue; - } +private: + /// Pointer to the mapping node object. (non-null) + BasicNodeType* mp_map {nullptr}; +}; - const bool is_empty = seq_item.empty(); - if (!is_empty) { - str += "\n"; - serialize_node(seq_item, cur_indent + 2, str); - continue; - } +FK_YAML_DETAIL_NAMESPACE_END - // an empty sequence or mapping - if (seq_item.is_sequence()) { - str += " []\n"; - } - else /*seq_item.is_mapping()*/ { - str += " {}\n"; - } - } - break; - case node_type::MAPPING: - if (node.size() == 0) { - str += "{}\n"; - return; - } - for (auto itr : node.map_items()) { - insert_indentation(cur_indent, str); +#endif /* FK_YAML_DETAIL_MAP_RANGE_PROXY_HPP */ - // serialize a mapping key node. - const auto& key_node = itr.key(); +// #include - bool is_appended = try_append_alias(key_node, false, str); - if (is_appended) { - // The trailing white space is necessary since anchor names can contain a colon (:) at its end. - str += " "; - } - else { - const bool is_anchor_appended = try_append_anchor(key_node, false, str); - const bool is_tag_appended = try_append_tag(key_node, is_anchor_appended, str); - if (is_anchor_appended || is_tag_appended) { - str += " "; - } +// #include - const bool is_container = !key_node.is_scalar(); - if (is_container) { - str += "? "; - } - const auto indent = static_cast(get_cur_indent(str)); - serialize_node(key_node, indent, str); - if (is_container) { - // a newline code is already inserted in the above serialize_node() call. - insert_indentation(indent - 2, str); - } - } +// #include - str += ":"; +// #include - // serialize a mapping value node. - const auto& value_node = itr.value(); +// #include - is_appended = try_append_alias(value_node, true, str); - if (is_appended) { - str += "\n"; - continue; - } +// #include +// _______ __ __ __ _____ __ __ __ +// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library +// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 +// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML +// +// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani +// SPDX-License-Identifier: MIT - try_append_anchor(value_node, true, str); - try_append_tag(value_node, true, str); +#ifndef FK_YAML_DETAIL_NODE_REF_STORAGE_HPP +#define FK_YAML_DETAIL_NODE_REF_STORAGE_HPP - const bool is_scalar = itr->is_scalar(); - if (is_scalar) { - str += " "; - serialize_node(value_node, cur_indent, str); - str += "\n"; - continue; - } +#include +#include +#include - const bool is_empty = itr->empty(); - if (is_empty) { - str += " "; - } - else { - str += "\n"; - } - serialize_node(value_node, cur_indent + 2, str); - } - break; - case node_type::NULL_OBJECT: - to_string(nullptr, m_tmp_str_buff); - str += m_tmp_str_buff; - break; - case node_type::BOOLEAN: - to_string(node.template get_value(), m_tmp_str_buff); - str += m_tmp_str_buff; - break; - case node_type::INTEGER: - to_string(node.template get_value(), m_tmp_str_buff); - str += m_tmp_str_buff; - break; - case node_type::FLOAT: - to_string(node.template get_value(), m_tmp_str_buff); - str += m_tmp_str_buff; - break; - case node_type::STRING: { - bool is_escaped = false; - auto str_val = get_string_node_value(node, is_escaped); +// #include - if (is_escaped) { - // There's no other token type with escapes than strings. - // Also, escapes must be in double-quoted strings. - str += '\"'; - str += str_val; - str += '\"'; - break; - } +// #include - // The next line is intentionally excluded from the LCOV coverage target since the next line is somehow - // misrecognized as it has a binary branch. Possibly begin() or end() has some conditional branch(es) - // internally. Confirmed with LCOV 1.14 on Ubuntu22.04. - const node_type type_if_plain = - scalar_scanner::scan(str_val.c_str(), str_val.c_str() + str_val.size()); // LCOV_EXCL_LINE +// #include - if (type_if_plain != node_type::STRING) { - // Surround a string value with double quotes to keep semantic equality. - // Without them, serialized values will become non-string. (e.g., "1" -> 1) - str += '\"'; - str += str_val; - str += '\"'; - } - else { - str += str_val; - } - break; - } - } - } - /// @brief Get the current indentation width. - /// @param s The target string object. - /// @return The current indentation width. - std::size_t get_cur_indent(const std::string& s) const noexcept { - const bool is_empty = s.empty(); - if (is_empty) { - return 0; - } +FK_YAML_DETAIL_NAMESPACE_BEGIN - const std::size_t last_lf_pos = s.rfind('\n'); - return (last_lf_pos != std::string::npos) ? s.size() - last_lf_pos - 1 : s.size(); - } +/// @brief A temporal storage for basic_node class objects. +/// @note This class makes it easier to handle lvalue basic_node objects in basic_node ctor with std::initializer_list. +/// @tparam BasicNodeType A basic_node template instance type. +template +class node_ref_storage { + static_assert(is_basic_node::value, "node_ref_storage only accepts basic_node<...>"); - /// @brief Insert indentation to the serialization result. - /// @param indent The indent width to be inserted. - /// @param str A string to hold serialization result. - void insert_indentation(const uint32_t indent, std::string& str) const noexcept { - if (indent == 0) { - return; - } + using node_type = BasicNodeType; - str.append(indent - get_cur_indent(str), ' '); +public: + /// @brief Construct a new node ref storage object with an rvalue basic_node object. + /// @param n An rvalue basic_node object. + explicit node_ref_storage(node_type&& n) noexcept(std::is_nothrow_move_constructible::value) + : m_owned_value(std::move(n)) { } - /// @brief Append an anchor property if it's available. Do nothing otherwise. - /// @param node The target node which is possibly an anchor node. - /// @param prepends_space Whether to prepend a space before an anchor property. - /// @param str A string to hold serialization result. - /// @return true if an anchor property has been appended, false otherwise. - bool try_append_anchor(const BasicNodeType& node, bool prepends_space, std::string& str) const { - if (node.is_anchor()) { - if (prepends_space) { - str += " "; - } - str += "&" + node.get_anchor_name(); - return true; - } - return false; + /// @brief Construct a new node ref storage object with an lvalue basic_node object. + /// @param n An lvalue basic_node object. + explicit node_ref_storage(const node_type& n) noexcept + : m_value_ref(&n) { } - /// @brief Append an alias property if it's available. Do nothing otherwise. - /// @param node The target node which is possibly an alias node. - /// @param prepends_space Whether to prepend a space before an alias property. - /// @param str A string to hold serialization result. - /// @return true if an alias property has been appended, false otherwise. - bool try_append_alias(const BasicNodeType& node, bool prepends_space, std::string& str) const { - if (node.is_alias()) { - if (prepends_space) { - str += " "; - } - str += "*" + node.get_anchor_name(); - return true; - } - return false; + /// @brief Construct a new node ref storage object with a std::initializer_list object. + /// @param init A std::initializer_list object. + node_ref_storage(std::initializer_list init) + : m_owned_value(init) { } - /// @brief Append a tag name if it's available. Do nothing otherwise. - /// @param[in] node The target node which possibly has a tag name. - /// @param[out] str A string to hold serialization result. - /// @return true if a tag name has been appended, false otherwise. - bool try_append_tag(const BasicNodeType& node, bool prepends_space, std::string& str) const { - if (node.has_tag_name()) { - if (prepends_space) { - str += " "; - } - str += node.get_tag_name(); - return true; - } - return false; + /// @brief Construct a new node ref storage object with variadic template arguments + /// @tparam Args Types of arguments to construct a basic_node object. + /// @param args Arguments to construct a basic_node object. + template ::value, int> = 0> + node_ref_storage(Args&&... args) + : m_owned_value(std::forward(args)...) { } - /// @brief Get a string value from the given node and, if necessary, escape its contents. - /// @param[in] node The target string YAML node. - /// @param[out] is_escaped Whether the contents of an output string has been escaped. - /// @return The (escaped) string node value. - typename BasicNodeType::string_type get_string_node_value(const BasicNodeType& node, bool& is_escaped) { - FK_YAML_ASSERT(node.is_string()); + // allow only move construct/assignment + node_ref_storage(const node_ref_storage&) = delete; + node_ref_storage(node_ref_storage&&) = default; + node_ref_storage& operator=(const node_ref_storage&) = delete; + node_ref_storage& operator=(node_ref_storage&&) = default; - const auto& s = node.as_str(); - return yaml_escaper::escape(s.c_str(), s.c_str() + s.size(), is_escaped); - } // LCOV_EXCL_LINE + ~node_ref_storage() = default; + +public: + /// @brief An arrow operator for node_ref_storage objects. + /// @return const node_type* A constant pointer to a basic_node object. + const node_type* operator->() const noexcept { + return m_value_ref ? m_value_ref : &m_owned_value; + } + + /// @brief Releases a basic_node object internally held. + /// @return node_type A basic_node object internally held. + node_type release() const noexcept { + return m_value_ref ? *m_value_ref : std::move(m_owned_value); + } private: - /// A temporal buffer for conversion from a scalar to a string. - std::string m_tmp_str_buff; + /// A storage for a basic_node object given with rvalue reference. + mutable node_type m_owned_value = nullptr; + /// A pointer to a basic_node object given with lvalue reference. + const node_type* m_value_ref = nullptr; }; FK_YAML_DETAIL_NAMESPACE_END -#endif /* FK_YAML_DETAIL_OUTPUT_SERIALIZER_HPP */ +#endif /* FK_YAML_DETAIL_NODE_REF_STORAGE_HPP */ -// #include +// #include // _______ __ __ __ _____ __ __ __ // | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library // | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 @@ -11133,313 +10998,490 @@ FK_YAML_DETAIL_NAMESPACE_END // SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani // SPDX-License-Identifier: MIT -#ifndef FK_YAML_DETAIL_REVERSE_ITERATOR_HPP -#define FK_YAML_DETAIL_REVERSE_ITERATOR_HPP +#ifndef FK_YAML_DETAIL_OUTPUT_SERIALIZER_HPP +#define FK_YAML_DETAIL_OUTPUT_SERIALIZER_HPP -#include +#include +#include +#include +#include // #include -// #include +// #include +// _______ __ __ __ _____ __ __ __ +// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library +// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 +// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML +// +// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani +// SPDX-License-Identifier: MIT +#ifndef FK_YAML_DETAIL_CONVERSIONS_TO_STRING_HPP +#define FK_YAML_DETAIL_CONVERSIONS_TO_STRING_HPP -FK_YAML_DETAIL_NAMESPACE_BEGIN +#include +#include +#include +#include +#include -/// @brief An iterator adapter class that reverses the direction of a given node iterator. -/// @tparam Iterator The base iterator type. -template -class reverse_iterator { - static_assert( - is_basic_node::value, - "reverse_iterator only accepts a basic_node type as the underlying iterator's value type"); +// #include -public: - /// @brief The base iterator type. - using iterator_type = Iterator; +// #include - /// @brief The base iterator category. - using iterator_category = typename Iterator::iterator_category; +// #include - /// @brief The type of the pointed-to elements by base iterators. - using value_type = typename Iterator::value_type; - /// @brief The type to represent differences between the pointed-to elements by the base iterators. - using difference_type = typename Iterator::difference_type; +FK_YAML_DETAIL_NAMESPACE_BEGIN - /// @brief The type of the pointed-to element pointers by base iterators. - using pointer = typename Iterator::pointer; +/// @brief Converts a ValueType object to a string YAML token. +/// @tparam ValueType A source value type. +/// @tparam CharType The type of characters for the conversion result. +/// @param s A resulting output string. +/// @param v A source value. +template +inline void to_string(ValueType v, std::basic_string& s) noexcept; - /// @brief The type of the pointed-to element references by base iterators. - using reference = typename Iterator::reference; +/// @brief Specialization of to_string() for null values. +/// @param s A resulting string YAML token. +/// @param (unused) nullptr +template <> +inline void to_string(std::nullptr_t /*unused*/, std::string& s) noexcept { + s = "null"; +} - /// @brief Constructs a reverse_iterator object. - reverse_iterator() = default; +/// @brief Specialization of to_string() for booleans. +/// @param s A resulting string YAML token. +/// @param v A boolean source value. +template <> +inline void to_string(bool v, std::string& s) noexcept { + s = v ? "true" : "false"; +} - /// @brief Copy constructs a reverse_iterator object. - reverse_iterator(const reverse_iterator&) = default; +/// @brief Specialization of to_string() for integers. +/// @tparam IntegerType An integer type. +/// @param s A resulting string YAML token. +/// @param i An integer source value. +template +inline enable_if_t::value> to_string(IntegerType v, std::string& s) noexcept { + s = std::to_string(v); +} - /// @brief Copy assignments a reverse_iterator object. - reverse_iterator& operator=(const reverse_iterator&) = default; +/// @brief Specialization of to_string() for floating point numbers. +/// @tparam FloatType A floating point number type. +/// @param s A resulting string YAML token. +/// @param f A floating point number source value. +template +inline enable_if_t::value> to_string(FloatType v, std::string& s) noexcept { + if (std::isnan(v)) { + s = ".nan"; + return; + } - /// @brief Move constructs a reverse_iterator object. - reverse_iterator(reverse_iterator&&) = default; + if (std::isinf(v)) { + if (v == std::numeric_limits::infinity()) { + s = ".inf"; + } + else { + s = "-.inf"; + } + return; + } - /// @brief Move assignments a reverse_iterator object. - reverse_iterator& operator=(reverse_iterator&&) = default; + std::ostringstream oss; + oss << v; + s = oss.str(); - /// @brief Constructs a reverse_iterator object with an underlying iterator object. - /// @param i A base iterator object. - reverse_iterator(const Iterator& i) noexcept - : m_current(i) { + // If `v` is actually an integer and no scientific notation is used for serialization, ".0" must be appended. + // The result would cause a roundtrip issue otherwise. https://github.com/fktn-k/fkYAML/issues/405 + const std::size_t pos = s.find_first_of(".e"); + if (pos == std::string::npos) { + s += ".0"; } +} - /// @brief Copy constructs a reverse_iterator object with a compatible reverse_iterator object. - /// @tparam U A compatible iterator type with Iterator. - /// @param other A compatible reverse_iterator object. - template >::value, int> = 0> - reverse_iterator(const reverse_iterator& other) noexcept - : m_current(other.base()) { - } +FK_YAML_DETAIL_NAMESPACE_END - /// @brief Copy assigns a reverse_iterator object with a compatible reverse_iterator object. - /// @tparam U A compatible iterator type with Iterator. - /// @param other A compatible reverse_iterator object. - /// @return Reference to this reverse_iterator object. - template >::value, int> = 0> - reverse_iterator& operator=(const reverse_iterator& other) noexcept { - m_current = other.base(); - return *this; - } +#endif /* FK_YAML_DETAIL_CONVERSIONS_TO_STRING_HPP */ - /// @brief Destructs a reverse_iterator object. - ~reverse_iterator() = default; +// #include - /// @brief Accesses the underlying iterator object. - /// @return The underlying iterator object. - Iterator base() const noexcept { - return m_current; - } +// #include - /// @brief Get reference to the pointed-to element. - /// @return Reference to the pointed-to element. - reference operator*() const noexcept { - Iterator tmp = m_current; - return *--tmp; - } +// #include - /// @brief Get pointer to the pointed-to element. - /// @return Pointer to the pointed-to element. - pointer operator->() const noexcept { - return &(operator*()); +// #include + +// #include + +// #include + + +FK_YAML_DETAIL_NAMESPACE_BEGIN + +/// @brief A basic implementation of serialization feature for YAML nodes. +/// @tparam BasicNodeType A BasicNode template class instantiation. +template +class basic_serializer { + static_assert(detail::is_basic_node::value, "basic_serializer only accepts basic_node<...>"); + +public: + /// @brief Construct a new basic_serializer object. + basic_serializer() = default; + + /// @brief Serialize the given Node value. + /// @param node A Node object to be serialized. + /// @return std::string A serialization result of the given Node value. + std::string serialize(const BasicNodeType& node) { + std::string str {}; + serialize_document(node, str); + return str; + } // LCOV_EXCL_LINE + + std::string serialize_docs(const std::vector& docs) { + std::string str {}; + + const auto size = static_cast(docs.size()); + for (uint32_t i = 0; i < size; i++) { + serialize_document(docs[i], str); + if (i + 1 < size) { + // Append the end-of-document marker for the next document. + str += "...\n"; + } + } + + return str; + } // LCOV_EXCL_LINE + +private: + void serialize_document(const BasicNodeType& node, std::string& str) { + const bool dirs_serialized = serialize_directives(node, str); + + // the root node cannot be an alias node. + const bool root_has_props = node.is_anchor() || node.has_tag_name(); + + if (root_has_props) { + if (dirs_serialized) { + str.back() = ' '; // replace the last LF with a white space + } + bool is_anchor_appended = try_append_anchor(node, false, str); + try_append_tag(node, is_anchor_appended, str); + str += "\n"; + } + serialize_node(node, 0, str); } - /// @brief Pre-increments the underlying iterator object. - /// @return Reference to this reverse_iterator object with its underlying iterator incremented. - reverse_iterator& operator++() noexcept { - --m_current; - return *this; - } + /// @brief Serialize the directives if any is applied to the node. + /// @param node The target node. + /// @param str A string to hold serialization result. + /// @return bool true if any directive is serialized, false otherwise. + bool serialize_directives(const BasicNodeType& node, std::string& str) { + const auto& p_meta = node.mp_meta; + bool needs_directive_end = false; - /// @brief Post-increments the underlying iterator object. - /// @return A reverse_iterator object with the underlying iterator as-is. - reverse_iterator operator++(int) & noexcept { - auto result = *this; - --m_current; - return result; - } + if (p_meta->is_version_specified) { + str += "%YAML "; + switch (p_meta->version) { + case yaml_version_type::VERSION_1_1: + str += "1.1\n"; + break; + case yaml_version_type::VERSION_1_2: + str += "1.2\n"; + break; + } + needs_directive_end = true; + } - /// @brief Pre-decrements the underlying iterator object. - /// @return Reference to this reverse_iterator with its underlying iterator decremented. - reverse_iterator& operator--() noexcept { - ++m_current; - return *this; - } + if (!p_meta->primary_handle_prefix.empty()) { + str += "%TAG ! "; + str += p_meta->primary_handle_prefix; + str += "\n"; + needs_directive_end = true; + } - /// @brief Post-decrements the underlying iterator object. - /// @return A reverse_iterator object with the underlying iterator as-is. - reverse_iterator operator--(int) & noexcept { - auto result = *this; - ++m_current; - return result; - } + if (!p_meta->secondary_handle_prefix.empty()) { + str += "%TAG !! "; + str += p_meta->secondary_handle_prefix; + str += "\n"; + needs_directive_end = true; + } - /// @brief Advances the underlying iterator object by `n`. - /// @param n The distance by which the underlying iterator is advanced. - /// @return A reverse_iterator object with the underlying iterator advanced by `n`. - reverse_iterator operator+(difference_type n) const noexcept { - return reverse_iterator(m_current - n); - } + if (!p_meta->named_handle_map.empty()) { + for (const auto& itr : p_meta->named_handle_map) { + str += "%TAG "; + str += itr.first; + str += " "; + str += itr.second; + str += "\n"; + } + needs_directive_end = true; + } - /// @brief Advances the underlying iterator object by `n`. - /// @param n The distance by which the underlying iterator is advanced. - /// @return Reference to this reverse_iterator object with the underlying iterator advanced by `n`. - reverse_iterator& operator+=(difference_type n) noexcept { - m_current -= n; - return *this; - } + if (needs_directive_end) { + str += "---\n"; + } - /// @brief Decrements the underlying iterator object by `n`. - /// @param n The distance by which the underlying iterator is decremented. - /// @return A reverse_iterator object with the underlying iterator decremented by `n`. - reverse_iterator operator-(difference_type n) const noexcept { - return reverse_iterator(m_current + n); + return needs_directive_end; } - /// @brief Decrements the underlying iterator object by `n`. - /// @param n The distance by which the underlying iterator is decremented. - /// @return Reference to this reverse_iterator object with the underlying iterator decremented by `n`. - reverse_iterator& operator-=(difference_type n) noexcept { - m_current += n; - return *this; - } + /// @brief Recursively serialize each Node object. + /// @param node A Node object to be serialized. + /// @param cur_indent The current indent width + /// @param str A string to hold serialization result. + void serialize_node(const BasicNodeType& node, const uint32_t cur_indent, std::string& str) { + switch (node.get_type()) { + case node_type::SEQUENCE: + if (node.size() == 0) { + str += "[]\n"; + return; + } + for (const auto& seq_item : node) { + insert_indentation(cur_indent, str); + str += "-"; - /// @brief Get the mapping key node of the underlying iterator. - /// @return The mapping key node of the underlying iterator. - auto key() const -> decltype(std::declval().key()) { - Iterator itr = --(base()); - return itr.key(); - } + const bool is_appended = try_append_alias(seq_item, true, str); + if (is_appended) { + str += "\n"; + continue; + } - /// @brief Get reference to the underlying iterator's value. - /// @return Reference to the underlying iterator's value. - reference value() noexcept { - Iterator itr = --(base()); - return *itr; - } + try_append_anchor(seq_item, true, str); + try_append_tag(seq_item, true, str); -private: - /// - Iterator m_current; -}; + const bool is_scalar = seq_item.is_scalar(); + if (is_scalar) { + str += " "; + serialize_node(seq_item, cur_indent, str); + str += "\n"; + continue; + } -/// @brief Check equality between reverse_iterator objects. -/// @tparam IteratorL Base iterator type for `lhs`. -/// @tparam IteratorR Base iterator type for `rhs`. -/// @param lhs A reverse_iterator object. -/// @param rhs A reverse_iterator object. -/// @return true if the two reverse_iterator objects are equal, false otherwise. -template -inline bool operator==(const reverse_iterator& lhs, const reverse_iterator& rhs) { - return lhs.base() == rhs.base(); -} + const bool is_empty = seq_item.empty(); + if (!is_empty) { + str += "\n"; + serialize_node(seq_item, cur_indent + 2, str); + continue; + } -/// @brief Check inequality between reverse_iterator objects. -/// @tparam IteratorL Base iterator type for `lhs`. -/// @tparam IteratorR Base iterator type for `rhs`. -/// @param lhs A reverse_iterator object. -/// @param rhs A reverse_iterator object. -/// @return true if the two reverse_iterator objects are not equal, false otherwise. -template -inline bool operator!=(const reverse_iterator& lhs, const reverse_iterator& rhs) { - return lhs.base() != rhs.base(); -} + // an empty sequence or mapping + if (seq_item.is_sequence()) { + str += " []\n"; + } + else /*seq_item.is_mapping()*/ { + str += " {}\n"; + } + } + break; + case node_type::MAPPING: + if (node.size() == 0) { + str += "{}\n"; + return; + } + for (auto itr : node.map_items()) { + insert_indentation(cur_indent, str); -/// @brief Check if `lhs` is less than `rhs`. -/// @tparam IteratorL Base iterator type for `lhs`. -/// @tparam IteratorR Base iterator type for `rhs`. -/// @param lhs A reverse_iterator object. -/// @param rhs A reverse_iterator object. -/// @return true if `lhs` is less than `rhs`, false otherwise. -template -inline bool operator<(const reverse_iterator& lhs, const reverse_iterator& rhs) { - return lhs.base() > rhs.base(); -} + // serialize a mapping key node. + const auto& key_node = itr.key(); -/// @brief Check if `lhs` is less than or equal to `rhs`. -/// @tparam IteratorL Base iterator type for `lhs`. -/// @tparam IteratorR Base iterator type for `rhs`. -/// @param lhs A reverse_iterator object. -/// @param rhs A reverse_iterator object. -/// @return true if `lhs` is less than or equal to `rhs`, false otherwise. -template -inline bool operator<=(const reverse_iterator& lhs, const reverse_iterator& rhs) { - return lhs.base() >= rhs.base(); -} + bool is_appended = try_append_alias(key_node, false, str); + if (is_appended) { + // The trailing white space is necessary since anchor names can contain a colon (:) at its end. + str += " "; + } + else { + const bool is_anchor_appended = try_append_anchor(key_node, false, str); + const bool is_tag_appended = try_append_tag(key_node, is_anchor_appended, str); + if (is_anchor_appended || is_tag_appended) { + str += " "; + } -/// @brief Check if `lhs` is greater than `rhs`. -/// @tparam IteratorL Base iterator type for `lhs`. -/// @tparam IteratorR Base iterator type for `rhs`. -/// @param lhs A reverse_iterator object. -/// @param rhs A reverse_iterator object. -/// @return true if `lhs` is greater than `rhs`, false otherwise. -template -inline bool operator>(const reverse_iterator& lhs, const reverse_iterator& rhs) { - return lhs.base() < rhs.base(); -} + const bool is_container = !key_node.is_scalar(); + if (is_container) { + str += "? "; + } + const auto indent = static_cast(get_cur_indent(str)); + serialize_node(key_node, indent, str); + if (is_container) { + // a newline code is already inserted in the above serialize_node() call. + insert_indentation(indent - 2, str); + } + } -/// @brief Check if `lhs` is greater than or equal to `rhs`. -/// @tparam IteratorL Base iterator type for `lhs`. -/// @tparam IteratorR Base iterator type for `rhs`. -/// @param lhs A reverse_iterator object. -/// @param rhs A reverse_iterator object. -/// @return true if `lhs` is greater than or equal to `rhs`, false otherwise. -template -inline bool operator>=(const reverse_iterator& lhs, const reverse_iterator& rhs) { - return lhs.base() <= rhs.base(); -} + str += ":"; + + // serialize a mapping value node. + const auto& value_node = itr.value(); + + is_appended = try_append_alias(value_node, true, str); + if (is_appended) { + str += "\n"; + continue; + } -FK_YAML_DETAIL_NAMESPACE_END + try_append_anchor(value_node, true, str); + try_append_tag(value_node, true, str); -#endif /* FK_YAML_DETAIL_REVERSE_ITERATOR_HPP */ + const bool is_scalar = itr->is_scalar(); + if (is_scalar) { + str += " "; + serialize_node(value_node, cur_indent, str); + str += "\n"; + continue; + } -// #include + const bool is_empty = itr->empty(); + if (is_empty) { + str += " "; + } + else { + str += "\n"; + } + serialize_node(value_node, cur_indent + 2, str); + } + break; + case node_type::NULL_OBJECT: + to_string(nullptr, m_tmp_str_buff); + str += m_tmp_str_buff; + break; + case node_type::BOOLEAN: + to_string(node.template get_value(), m_tmp_str_buff); + str += m_tmp_str_buff; + break; + case node_type::INTEGER: + to_string(node.template get_value(), m_tmp_str_buff); + str += m_tmp_str_buff; + break; + case node_type::FLOAT: + to_string(node.template get_value(), m_tmp_str_buff); + str += m_tmp_str_buff; + break; + case node_type::STRING: { + bool is_escaped = false; + auto str_val = get_string_node_value(node, is_escaped); -// #include -// _______ __ __ __ _____ __ __ __ -// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library -// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 -// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML -// -// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani -// SPDX-License-Identifier: MIT + if (is_escaped) { + // There's no other token type with escapes than strings. + // Also, escapes must be in double-quoted strings. + str += '\"'; + str += str_val; + str += '\"'; + break; + } -#ifndef FK_YAML_DETAIL_TYPES_YAML_VERSION_T_HPP -#define FK_YAML_DETAIL_TYPES_YAML_VERSION_T_HPP + // The next line is intentionally excluded from the LCOV coverage target since the next line is somehow + // misrecognized as it has a binary branch. Possibly begin() or end() has some conditional branch(es) + // internally. Confirmed with LCOV 1.14 on Ubuntu22.04. + const node_type type_if_plain = + scalar_scanner::scan(str_val.c_str(), str_val.c_str() + str_val.size()); // LCOV_EXCL_LINE -#include + if (type_if_plain != node_type::STRING) { + // Surround a string value with double quotes to keep semantic equality. + // Without them, serialized values will become non-string. (e.g., "1" -> 1) + str += '\"'; + str += str_val; + str += '\"'; + } + else { + str += str_val; + } + break; + } + } + } -// #include + /// @brief Get the current indentation width. + /// @param s The target string object. + /// @return The current indentation width. + std::size_t get_cur_indent(const std::string& s) const noexcept { + const bool is_empty = s.empty(); + if (is_empty) { + return 0; + } -// #include + const std::size_t last_lf_pos = s.rfind('\n'); + return (last_lf_pos != std::string::npos) ? s.size() - last_lf_pos - 1 : s.size(); + } + /// @brief Insert indentation to the serialization result. + /// @param indent The indent width to be inserted. + /// @param str A string to hold serialization result. + void insert_indentation(const uint32_t indent, std::string& str) const noexcept { + if (indent == 0) { + return; + } -FK_YAML_DETAIL_NAMESPACE_BEGIN + str.append(indent - get_cur_indent(str), ' '); + } -/// @brief Definition of YAML version types. -enum class yaml_version_t : std::uint8_t { - VER_1_1, //!< YAML version 1.1 - VER_1_2, //!< YAML version 1.2 -}; + /// @brief Append an anchor property if it's available. Do nothing otherwise. + /// @param node The target node which is possibly an anchor node. + /// @param prepends_space Whether to prepend a space before an anchor property. + /// @param str A string to hold serialization result. + /// @return true if an anchor property has been appended, false otherwise. + bool try_append_anchor(const BasicNodeType& node, bool prepends_space, std::string& str) const { + if (node.is_anchor()) { + if (prepends_space) { + str += " "; + } + str += "&" + node.get_anchor_name(); + return true; + } + return false; + } -inline yaml_version_t convert_from_yaml_version_type(yaml_version_type t) noexcept { - switch (t) { - case yaml_version_type::VERSION_1_1: - return yaml_version_t::VER_1_1; - case yaml_version_type::VERSION_1_2: - default: - return yaml_version_t::VER_1_2; + /// @brief Append an alias property if it's available. Do nothing otherwise. + /// @param node The target node which is possibly an alias node. + /// @param prepends_space Whether to prepend a space before an alias property. + /// @param str A string to hold serialization result. + /// @return true if an alias property has been appended, false otherwise. + bool try_append_alias(const BasicNodeType& node, bool prepends_space, std::string& str) const { + if (node.is_alias()) { + if (prepends_space) { + str += " "; + } + str += "*" + node.get_anchor_name(); + return true; + } + return false; } -} -inline yaml_version_type convert_to_yaml_version_type(yaml_version_t t) noexcept { - switch (t) { - case yaml_version_t::VER_1_1: - return yaml_version_type::VERSION_1_1; - case yaml_version_t::VER_1_2: - default: - return yaml_version_type::VERSION_1_2; + /// @brief Append a tag name if it's available. Do nothing otherwise. + /// @param[in] node The target node which possibly has a tag name. + /// @param[out] str A string to hold serialization result. + /// @return true if a tag name has been appended, false otherwise. + bool try_append_tag(const BasicNodeType& node, bool prepends_space, std::string& str) const { + if (node.has_tag_name()) { + if (prepends_space) { + str += " "; + } + str += node.get_tag_name(); + return true; + } + return false; } -} -FK_YAML_DETAIL_NAMESPACE_END + /// @brief Get a string value from the given node and, if necessary, escape its contents. + /// @param[in] node The target string YAML node. + /// @param[out] is_escaped Whether the contents of an output string has been escaped. + /// @return The (escaped) string node value. + typename BasicNodeType::string_type get_string_node_value(const BasicNodeType& node, bool& is_escaped) { + FK_YAML_ASSERT(node.is_string()); -#endif /* FK_YAML_DETAIL_TYPES_YAML_VERSION_T_HPP */ + const auto& s = node.as_str(); + return yaml_escaper::escape(s.c_str(), s.c_str() + s.size(), is_escaped); + } // LCOV_EXCL_LINE -// #include +private: + /// A temporal buffer for conversion from a scalar to a string. + std::string m_tmp_str_buff; +}; -// #include +FK_YAML_DETAIL_NAMESPACE_END -// #include +#endif /* FK_YAML_DETAIL_OUTPUT_SERIALIZER_HPP */ + +// #include // _______ __ __ __ _____ __ __ __ // | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library // | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 @@ -11448,990 +11490,988 @@ FK_YAML_DETAIL_NAMESPACE_END // SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani // SPDX-License-Identifier: MIT -#ifndef FK_YAML_NODE_VALUE_CONVERTER_HPP -#define FK_YAML_NODE_VALUE_CONVERTER_HPP +#ifndef FK_YAML_DETAIL_REVERSE_ITERATOR_HPP +#define FK_YAML_DETAIL_REVERSE_ITERATOR_HPP -#include +#include // #include -// #include -// _______ __ __ __ _____ __ __ __ -// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library -// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 -// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML -// -// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani -// SPDX-License-Identifier: MIT +// #include -#ifndef FK_YAML_DETAIL_CONVERSIONS_FROM_NODE_HPP -#define FK_YAML_DETAIL_CONVERSIONS_FROM_NODE_HPP -#include -#include -#include -#include -#include -#include +FK_YAML_DETAIL_NAMESPACE_BEGIN -// #include +/// @brief An iterator adapter class that reverses the direction of a given node iterator. +/// @tparam Iterator The base iterator type. +template +class reverse_iterator { + static_assert( + is_basic_node::value, + "reverse_iterator only accepts a basic_node type as the underlying iterator's value type"); -// #include +public: + /// @brief The base iterator type. + using iterator_type = Iterator; -// #include + /// @brief The base iterator category. + using iterator_category = typename Iterator::iterator_category; -// #include + /// @brief The type of the pointed-to elements by base iterators. + using value_type = typename Iterator::value_type; -// #include + /// @brief The type to represent differences between the pointed-to elements by the base iterators. + using difference_type = typename Iterator::difference_type; -// #include + /// @brief The type of the pointed-to element pointers by base iterators. + using pointer = typename Iterator::pointer; + /// @brief The type of the pointed-to element references by base iterators. + using reference = typename Iterator::reference; -#ifdef FK_YAML_HAS_CXX_17 -#include -#endif + /// @brief Constructs a reverse_iterator object. + reverse_iterator() = default; -FK_YAML_DETAIL_NAMESPACE_BEGIN + /// @brief Copy constructs a reverse_iterator object. + reverse_iterator(const reverse_iterator&) = default; -/////////////////// -// from_node // -/////////////////// + /// @brief Copy assignments a reverse_iterator object. + reverse_iterator& operator=(const reverse_iterator&) = default; + + /// @brief Move constructs a reverse_iterator object. + reverse_iterator(reverse_iterator&&) = default; + + /// @brief Move assignments a reverse_iterator object. + reverse_iterator& operator=(reverse_iterator&&) = default; -// utility type traits and functors + /// @brief Constructs a reverse_iterator object with an underlying iterator object. + /// @param i A base iterator object. + reverse_iterator(const Iterator& i) noexcept + : m_current(i) { + } -/// @brief Utility traits type alias to detect constructible associative container types from a mapping node, e.g., -/// std::map or std::unordered_map. -/// @tparam T A target type for detection. -template -using is_constructible_mapping_type = - conjunction, detect::has_mapped_type, detect::has_value_type>; + /// @brief Copy constructs a reverse_iterator object with a compatible reverse_iterator object. + /// @tparam U A compatible iterator type with Iterator. + /// @param other A compatible reverse_iterator object. + template >::value, int> = 0> + reverse_iterator(const reverse_iterator& other) noexcept + : m_current(other.base()) { + } -/// @brief Utility traits type alias to detect constructible container types from a sequence node, e.g., std::vector or -/// std::list. -/// @tparam BasicNodeType A basic_node template instance type. -/// @tparam T A target type for detection. -template -using is_constructible_sequence_type = conjunction< - negation>, detect::has_iterator, detect::is_iterator_traits, - detect::has_begin_end, negation>, - negation>>; + /// @brief Copy assigns a reverse_iterator object with a compatible reverse_iterator object. + /// @tparam U A compatible iterator type with Iterator. + /// @param other A compatible reverse_iterator object. + /// @return Reference to this reverse_iterator object. + template >::value, int> = 0> + reverse_iterator& operator=(const reverse_iterator& other) noexcept { + m_current = other.base(); + return *this; + } -/// @brief Utility traits type alias to detect a sequence container adapter type, e.g., std::stack or std::queue. -/// @tparam T A target type for detection. -template -using is_sequence_container_adapter = conjunction< - negation>, detect::has_container_type, detect::has_value_type, - negation>>; + /// @brief Destructs a reverse_iterator object. + ~reverse_iterator() = default; -/// @brief Helper struct for reserve() member function call switch for types which do not have reserve function. -/// @tparam ContainerType A container type. -template -struct call_reserve_if_available { - /// @brief Do nothing since ContainerType does not have reserve function. - static void call(ContainerType& /*unused*/, typename ContainerType::size_type /*unused*/) { + /// @brief Accesses the underlying iterator object. + /// @return The underlying iterator object. + Iterator base() const noexcept { + return m_current; } -}; -/// @brief Helper struct for reserve() member function call switch for types which have reserve function. -/// @tparam ContainerType A container type. -template -struct call_reserve_if_available::value>> { - /// @brief Call reserve function on the ContainerType object with a given size. - /// @param c A container object. - /// @param n A size to reserve. - static void call(ContainerType& c, typename ContainerType::size_type n) { - c.reserve(n); + /// @brief Get reference to the pointed-to element. + /// @return Reference to the pointed-to element. + reference operator*() const noexcept { + Iterator tmp = m_current; + return *--tmp; } -}; -// from_node() implementations + /// @brief Get pointer to the pointed-to element. + /// @return Pointer to the pointed-to element. + pointer operator->() const noexcept { + return &(operator*()); + } -/// @brief from_node function for C-style 1D arrays whose element type must be a basic_node template instance type or a -/// compatible type. -/// @tparam BasicNodeType A basic_node template instance type. -/// @tparam T Element type of C-style 1D array. -/// @tparam N Size of the array. -/// @param n A basic_node object. -/// @param array An array object. -template -inline auto from_node(const BasicNodeType& n, T (&array)[N]) - -> decltype(n.get_value_inplace(std::declval()), void()) { - if FK_YAML_UNLIKELY (!n.is_sequence()) { - throw type_error("The target node value type is not sequence type.", n.get_type()); + /// @brief Pre-increments the underlying iterator object. + /// @return Reference to this reverse_iterator object with its underlying iterator incremented. + reverse_iterator& operator++() noexcept { + --m_current; + return *this; } - // call get_value_inplace(), not get_value(), since the storage to fill the result into is already created. - for (std::size_t i = 0; i < N; i++) { - n.at(i).get_value_inplace(array[i]); + /// @brief Post-increments the underlying iterator object. + /// @return A reverse_iterator object with the underlying iterator as-is. + reverse_iterator operator++(int) & noexcept { + auto result = *this; + --m_current; + return result; } -} -/// @brief from_node function for C-style 2D arrays whose element type must be a basic_node template instance type or a -/// compatible type. -/// @tparam BasicNodeType A basic_node template instance type. -/// @tparam T Element type of C-style 2D array. -/// @tparam N0 Size of the outer dimension. -/// @tparam N1 Size of the inner dimension. -/// @param n A basic_node object. -/// @param array An array object. -template -inline auto from_node(const BasicNodeType& n, T (&array)[N0][N1]) - -> decltype(n.get_value_inplace(std::declval()), void()) { - if FK_YAML_UNLIKELY (!n.is_sequence()) { - throw type_error("The target node value type is not sequence type.", n.get_type()); + /// @brief Pre-decrements the underlying iterator object. + /// @return Reference to this reverse_iterator with its underlying iterator decremented. + reverse_iterator& operator--() noexcept { + ++m_current; + return *this; } - // call get_value_inplace(), not get_value(), since the storage to fill the result into is already created. - for (std::size_t i0 = 0; i0 < N0; i0++) { - for (std::size_t i1 = 0; i1 < N1; i1++) { - n.at(i0).at(i1).get_value_inplace(array[i0][i1]); - } + /// @brief Post-decrements the underlying iterator object. + /// @return A reverse_iterator object with the underlying iterator as-is. + reverse_iterator operator--(int) & noexcept { + auto result = *this; + ++m_current; + return result; } -} -/// @brief from_node function for C-style 2D arrays whose element type must be a basic_node template instance type or a -/// compatible type. -/// @tparam BasicNodeType A basic_node template instance type. -/// @tparam T Element type of C-style 2D array. -/// @tparam N0 Size of the outermost dimension. -/// @tparam N1 Size of the middle dimension. -/// @tparam N2 Size of the innermost dimension. -/// @param n A basic_node object. -/// @param array An array object. -template -inline auto from_node(const BasicNodeType& n, T (&array)[N0][N1][N2]) - -> decltype(n.get_value_inplace(std::declval()), void()) { - if FK_YAML_UNLIKELY (!n.is_sequence()) { - throw type_error("The target node value type is not sequence type.", n.get_type()); + /// @brief Advances the underlying iterator object by `n`. + /// @param n The distance by which the underlying iterator is advanced. + /// @return A reverse_iterator object with the underlying iterator advanced by `n`. + reverse_iterator operator+(difference_type n) const noexcept { + return reverse_iterator(m_current - n); } - // call get_value_inplace(), not get_value(), since the storage to fill the result into is already created. - for (std::size_t i0 = 0; i0 < N0; i0++) { - for (std::size_t i1 = 0; i1 < N1; i1++) { - for (std::size_t i2 = 0; i2 < N2; i2++) { - n.at(i0).at(i1).at(i2).get_value_inplace(array[i0][i1][i2]); - } - } + /// @brief Advances the underlying iterator object by `n`. + /// @param n The distance by which the underlying iterator is advanced. + /// @return Reference to this reverse_iterator object with the underlying iterator advanced by `n`. + reverse_iterator& operator+=(difference_type n) noexcept { + m_current -= n; + return *this; } -} -/// @brief from_node function for std::array objects whose element type must be a basic_node template instance type or a -/// compatible type. This function is necessary since insert function is not implemented for std::array. -/// @tparam BasicNodeType A basic_node template instance type. -/// @tparam T Element type of std::array. -/// @tparam N Size of std::array. -/// @param n A basic_node object. -/// @param arr A std::array object. -template -inline auto from_node(const BasicNodeType& n, std::array& arr) - -> decltype(n.get_value_inplace(std::declval()), void()) { - if FK_YAML_UNLIKELY (!n.is_sequence()) { - throw type_error("The target node value type is not sequence type.", n.get_type()); + /// @brief Decrements the underlying iterator object by `n`. + /// @param n The distance by which the underlying iterator is decremented. + /// @return A reverse_iterator object with the underlying iterator decremented by `n`. + reverse_iterator operator-(difference_type n) const noexcept { + return reverse_iterator(m_current + n); } - for (std::size_t i = 0; i < N; i++) { - // call get_value_inplace(), not get_value(), since the storage to fill the result into is already created. - n.at(i).get_value_inplace(arr.at(i)); + /// @brief Decrements the underlying iterator object by `n`. + /// @param n The distance by which the underlying iterator is decremented. + /// @return Reference to this reverse_iterator object with the underlying iterator decremented by `n`. + reverse_iterator& operator-=(difference_type n) noexcept { + m_current += n; + return *this; } -} -/// @brief from_node function for std::valarray objects whose element type must be a basic_node template instance type -/// or a compatible type. This function is necessary since insert function is not implemented for std::valarray. -/// @tparam BasicNodeType A basic_node template instance type. -/// @tparam T Element type of std::valarray. -/// @param n A basic_node object. -/// @param va A std::valarray object. -template -inline auto from_node(const BasicNodeType& n, std::valarray& va) - -> decltype(n.get_value_inplace(std::declval()), void()) { - if FK_YAML_UNLIKELY (!n.is_sequence()) { - throw type_error("The target node value type is not sequence type.", n.get_type()); + /// @brief Get the mapping key node of the underlying iterator. + /// @return The mapping key node of the underlying iterator. + auto key() const -> decltype(std::declval().key()) { + Iterator itr = --(base()); + return itr.key(); } - std::size_t count = n.size(); - va.resize(count); - for (std::size_t i = 0; i < count; i++) { - // call get_value_inplace(), not get_value(), since the storage to fill the result into is already created. - n.at(i).get_value_inplace(va[i]); + /// @brief Get reference to the underlying iterator's value. + /// @return Reference to the underlying iterator's value. + reference value() noexcept { + Iterator itr = --(base()); + return *itr; } + +private: + /// + Iterator m_current; +}; + +/// @brief Check equality between reverse_iterator objects. +/// @tparam IteratorL Base iterator type for `lhs`. +/// @tparam IteratorR Base iterator type for `rhs`. +/// @param lhs A reverse_iterator object. +/// @param rhs A reverse_iterator object. +/// @return true if the two reverse_iterator objects are equal, false otherwise. +template +inline bool operator==(const reverse_iterator& lhs, const reverse_iterator& rhs) { + return lhs.base() == rhs.base(); +} + +/// @brief Check inequality between reverse_iterator objects. +/// @tparam IteratorL Base iterator type for `lhs`. +/// @tparam IteratorR Base iterator type for `rhs`. +/// @param lhs A reverse_iterator object. +/// @param rhs A reverse_iterator object. +/// @return true if the two reverse_iterator objects are not equal, false otherwise. +template +inline bool operator!=(const reverse_iterator& lhs, const reverse_iterator& rhs) { + return lhs.base() != rhs.base(); +} + +/// @brief Check if `lhs` is less than `rhs`. +/// @tparam IteratorL Base iterator type for `lhs`. +/// @tparam IteratorR Base iterator type for `rhs`. +/// @param lhs A reverse_iterator object. +/// @param rhs A reverse_iterator object. +/// @return true if `lhs` is less than `rhs`, false otherwise. +template +inline bool operator<(const reverse_iterator& lhs, const reverse_iterator& rhs) { + return lhs.base() > rhs.base(); } -/// @brief from_node function for std::forward_list objects whose element type must be a basic_node template instance -/// type or a compatible type. This function is necessary since insert function is not implemented for -/// std::forward_list. -/// @tparam BasicNodeType A basic_node template instance type. -/// @tparam T Element type of std::forward_list. -/// @tparam Alloc Allocator type of std::forward_list. -/// @param n A basic_node object. -/// @param fl A std::forward_list object. -template -inline auto from_node(const BasicNodeType& n, std::forward_list& fl) - -> decltype(n.template get_value(), void()) { - if FK_YAML_UNLIKELY (!n.is_sequence()) { - throw type_error("The target node value is not sequence type.", n.get_type()); - } +/// @brief Check if `lhs` is less than or equal to `rhs`. +/// @tparam IteratorL Base iterator type for `lhs`. +/// @tparam IteratorR Base iterator type for `rhs`. +/// @param lhs A reverse_iterator object. +/// @param rhs A reverse_iterator object. +/// @return true if `lhs` is less than or equal to `rhs`, false otherwise. +template +inline bool operator<=(const reverse_iterator& lhs, const reverse_iterator& rhs) { + return lhs.base() >= rhs.base(); +} - fl.clear(); +/// @brief Check if `lhs` is greater than `rhs`. +/// @tparam IteratorL Base iterator type for `lhs`. +/// @tparam IteratorR Base iterator type for `rhs`. +/// @param lhs A reverse_iterator object. +/// @param rhs A reverse_iterator object. +/// @return true if `lhs` is greater than `rhs`, false otherwise. +template +inline bool operator>(const reverse_iterator& lhs, const reverse_iterator& rhs) { + return lhs.base() < rhs.base(); +} - // std::forward_list does not have insert function. - auto insert_pos_itr = fl.before_begin(); - for (const auto& elem : n) { - insert_pos_itr = fl.emplace_after(insert_pos_itr, elem.template get_value()); - } +/// @brief Check if `lhs` is greater than or equal to `rhs`. +/// @tparam IteratorL Base iterator type for `lhs`. +/// @tparam IteratorR Base iterator type for `rhs`. +/// @param lhs A reverse_iterator object. +/// @param rhs A reverse_iterator object. +/// @return true if `lhs` is greater than or equal to `rhs`, false otherwise. +template +inline bool operator>=(const reverse_iterator& lhs, const reverse_iterator& rhs) { + return lhs.base() <= rhs.base(); } -/// @brief from_node function for container objects of only keys or values, e.g., std::vector or std::set, whose element -/// type must be a basic_node template instance type or a compatible type. -/// @tparam BasicNodeType A basic_node template instance type. -/// @tparam CompatSeqType A container type. -/// @param n A basic_node object. -/// @param s A container object. -template < - typename BasicNodeType, typename CompatSeqType, - enable_if_t< - conjunction< - is_basic_node, is_constructible_sequence_type, - negation>>::value, - int> = 0> -inline auto from_node(const BasicNodeType& n, CompatSeqType& s) - -> decltype(n.template get_value(), void()) { - if FK_YAML_UNLIKELY (!n.is_sequence()) { - throw type_error("The target node value is not sequence type.", n.get_type()); - } +FK_YAML_DETAIL_NAMESPACE_END - s.clear(); +#endif /* FK_YAML_DETAIL_REVERSE_ITERATOR_HPP */ - // call reserve function first if it's available (like std::vector). - call_reserve_if_available::call(s, n.size()); +// #include - // transform a sequence node into a destination type object by calling insert function. - using std::end; - std::transform(n.begin(), n.end(), std::inserter(s, end(s)), [](const BasicNodeType& elem) { - return elem.template get_value(); - }); -} +// #include +// _______ __ __ __ _____ __ __ __ +// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library +// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 +// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML +// +// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani +// SPDX-License-Identifier: MIT -/// @brief from_node function for sequence container adapter objects, e.g., std::stack or std::queue, whose element type -/// must be either a basic_node template instance type or a compatible type. -/// @tparam BasicNodeType A basic_node template instance type. -/// @tparam SeqContainerAdapter A sequence container adapter type. -/// @param n A node object. -/// @param ca A sequence container adapter object. -template < - typename BasicNodeType, typename SeqContainerAdapter, - enable_if_t< - conjunction, is_sequence_container_adapter>::value, int> = 0> -inline auto from_node(const BasicNodeType& n, SeqContainerAdapter& ca) - -> decltype(n.template get_value(), ca.push(std::declval()), void()) { - if FK_YAML_UNLIKELY (!n.is_sequence()) { - throw type_error("The target node value is not sequence type.", n.get_type()); - } +#ifndef FK_YAML_DETAIL_TYPES_YAML_VERSION_T_HPP +#define FK_YAML_DETAIL_TYPES_YAML_VERSION_T_HPP - // clear existing elements manually since clear function is not implemented for container adapter classes. - while (!ca.empty()) { - ca.pop(); - } +#include - for (const auto& elem : n) { - // container adapter classes commonly have push function. - // emplace function cannot be used in case SeqContainerAdapter::container_type is std::vector in C++11. - ca.push(elem.template get_value()); - } -} +// #include -/// @brief from_node function for mappings whose key and value are of both compatible types. -/// @tparam BasicNodeType A basic_node template instance type. -/// @tparam CompatibleKeyType Mapping key type compatible with BasicNodeType. -/// @tparam CompatibleValueType Mapping value type compatible with BasicNodeType. -/// @tparam Compare Comparator type for mapping keys. -/// @tparam Allocator Allocator type for destination mapping object. -/// @param n A node object. -/// @param m Mapping container object to store converted key/value objects. -template ::value, int> = 0> -inline auto from_node(const BasicNodeType& n, CompatMapType& m) - -> decltype( - std::declval().template get_value(), - std::declval().template get_value(), - m.emplace(std::declval(), std::declval()), - void()) { - if FK_YAML_UNLIKELY (!n.is_mapping()) { - throw type_error("The target node value type is not mapping type.", n.get_type()); - } +// #include - m.clear(); - call_reserve_if_available::call(m, n.size()); - for (const auto& pair : n.as_map()) { - m.emplace( - pair.first.template get_value(), - pair.second.template get_value()); - } -} +FK_YAML_DETAIL_NAMESPACE_BEGIN -/// @brief from_node function for nullptr. -/// @tparam BasicNodeType A basic_node template instance type. -/// @param n A node object. -/// @param null Storage for a null value. -template ::value, int> = 0> -inline void from_node(const BasicNodeType& n, std::nullptr_t& null) { - // to ensure the target node value type is null. - if FK_YAML_UNLIKELY (!n.is_null()) { - throw type_error("The target node value type is not null type.", n.get_type()); +/// @brief Definition of YAML version types. +enum class yaml_version_t : std::uint8_t { + VER_1_1, //!< YAML version 1.1 + VER_1_2, //!< YAML version 1.2 +}; + +inline yaml_version_t convert_from_yaml_version_type(yaml_version_type t) noexcept { + switch (t) { + case yaml_version_type::VERSION_1_1: + return yaml_version_t::VER_1_1; + case yaml_version_type::VERSION_1_2: + default: + return yaml_version_t::VER_1_2; } - null = nullptr; } -/// @brief from_node function for booleans. -/// @tparam BasicNodeType A basic_node template instance type. -/// @param n A node object. -/// @param b Storage for a boolean value. -template ::value, int> = 0> -inline void from_node(const BasicNodeType& n, bool& b) { - switch (n.get_type()) { - case node_type::NULL_OBJECT: - // nullptr is converted to false just as C++ implicitly does. - b = false; - break; - case node_type::BOOLEAN: - b = static_cast(n.as_bool()); - break; - case node_type::INTEGER: - // true: non-zero, false: zero - b = (n.as_int() != 0); - break; - case node_type::FLOAT: - // true: non-zero, false: zero - using float_type = typename BasicNodeType::float_number_type; - b = (n.as_float() != static_cast(0.)); - break; - case node_type::SEQUENCE: - case node_type::MAPPING: - case node_type::STRING: +inline yaml_version_type convert_to_yaml_version_type(yaml_version_t t) noexcept { + switch (t) { + case yaml_version_t::VER_1_1: + return yaml_version_type::VERSION_1_1; + case yaml_version_t::VER_1_2: default: - throw type_error("The target node value type is not compatible with boolean type.", n.get_type()); + return yaml_version_type::VERSION_1_2; } } -/// @brief Helper struct for node-to-int conversion. -/// @tparam BasicNodeType A basic_node template instance type. -/// @tparam IntType Target integer value type (same as BasicNodeType::integer_type) -template < - typename BasicNodeType, typename IntType, bool = std::is_same::value> -struct from_node_int_helper { - /// @brief Convert node's integer value to the target integer type. - /// @param n A node object. - /// @return An integer value converted from the node's integer value. - static IntType convert(const BasicNodeType& n) { - return n.as_int(); - } -}; +FK_YAML_DETAIL_NAMESPACE_END + +#endif /* FK_YAML_DETAIL_TYPES_YAML_VERSION_T_HPP */ + +// #include + +// #include + +// #include +// _______ __ __ __ _____ __ __ __ +// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library +// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 +// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML +// +// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani +// SPDX-License-Identifier: MIT + +#ifndef FK_YAML_NODE_VALUE_CONVERTER_HPP +#define FK_YAML_NODE_VALUE_CONVERTER_HPP + +#include + +// #include + +// #include +// _______ __ __ __ _____ __ __ __ +// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library +// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 +// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML +// +// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani +// SPDX-License-Identifier: MIT + +#ifndef FK_YAML_DETAIL_CONVERSIONS_FROM_NODE_HPP +#define FK_YAML_DETAIL_CONVERSIONS_FROM_NODE_HPP + +#include +#include +#include +#include +#include +#include -/// @brief Helper struct for node-to-int conversion if IntType is not the node's integer value type. -/// @tparam BasicNodeType A basic_node template instance type. -/// @tparam IntType Target integer value type (different from BasicNodeType::integer_type) -template -struct from_node_int_helper { - /// @brief Convert node's integer value to non-uint64_t integer types. - /// @param n A node object. - /// @return An integer value converted from the node's integer value. - static IntType convert(const BasicNodeType& n) { - using node_int_type = typename BasicNodeType::integer_type; - const node_int_type tmp_int = n.as_int(); +// #include - // under/overflow check. - if (std::is_same::value) { - if FK_YAML_UNLIKELY (tmp_int < 0) { - throw exception("Integer value underflow detected."); - } - } - else { - if FK_YAML_UNLIKELY (tmp_int < static_cast(std::numeric_limits::min())) { - throw exception("Integer value underflow detected."); - } - if FK_YAML_UNLIKELY (static_cast(std::numeric_limits::max()) < tmp_int) { - throw exception("Integer value overflow detected."); - } - } +// #include - return static_cast(tmp_int); - } -}; +// #include -/// @brief from_node function for integers. -/// @note If node's value is null, boolean, or float, such a value is converted into an integer internally. -/// @tparam BasicNodeType A basic_node template instance type. -/// @tparam IntegerType An integer value type. -/// @param n A node object. -/// @param i Storage for an integer value. -template < - typename BasicNodeType, typename IntegerType, - enable_if_t, is_non_bool_integral>::value, int> = 0> -inline void from_node(const BasicNodeType& n, IntegerType& i) { - switch (n.get_type()) { - case node_type::NULL_OBJECT: - // nullptr is interpreted as 0 - i = static_cast(0); - break; - case node_type::BOOLEAN: - i = static_cast(n.as_bool()) ? static_cast(1) : static_cast(0); - break; - case node_type::INTEGER: - i = from_node_int_helper::convert(n); - break; - case node_type::FLOAT: { - // int64_t should be safe to express the integer part of possible floating point types. - const auto tmp_int = static_cast(n.as_float()); +// #include - // under/overflow check. - if (std::is_same::value) { - if FK_YAML_UNLIKELY (tmp_int < 0) { - throw exception("Integer value underflow detected."); - } - } - else { - if FK_YAML_UNLIKELY (tmp_int < static_cast(std::numeric_limits::min())) { - throw exception("Integer value underflow detected."); - } - if FK_YAML_UNLIKELY (static_cast(std::numeric_limits::max()) < tmp_int) { - throw exception("Integer value overflow detected."); - } - } +// #include - i = static_cast(tmp_int); - break; - } - case node_type::SEQUENCE: - case node_type::MAPPING: - case node_type::STRING: - default: - throw type_error("The target node value type is not compatible with integer type.", n.get_type()); - } -} +// #include -/// @brief Helper struct for node-to-float conversion if FloatType is the node's floating point value type. -/// @tparam BasicNodeType A basic_node template instance type. -/// @tparam FloatType Target floating point value type (same as the BasicNodeType::float_number_type) -template < - typename BasicNodeType, typename FloatType, - bool = std::is_same::value> -struct from_node_float_helper { - /// @brief Convert node's floating point value to the target floating point type. - /// @param n A node object. - /// @return A floating point value converted from the node's floating point value. - static FloatType convert(const BasicNodeType& n) { - return n.as_float(); - } -}; -/// @brief Helper struct for node-to-float conversion if IntType is not the node's floating point value type. -/// @tparam BasicNodeType A basic_node template instance type. -/// @tparam FloatType Target floating point value type (different from BasicNodeType::float_number_type) -template -struct from_node_float_helper { - /// @brief Convert node's floating point value to the target floating point type. - /// @param n A node object. - /// @return A floating point value converted from the node's floating point value. - static FloatType convert(const BasicNodeType& n) { - using node_float_type = typename BasicNodeType::float_number_type; - auto tmp_float = n.as_float(); +#ifdef FK_YAML_HAS_CXX_17 +#include +#endif - // check if the value is an infinite number (either positive or negative) - if (std::isinf(tmp_float)) { - if (tmp_float == std::numeric_limits::infinity()) { - return std::numeric_limits::infinity(); - } +FK_YAML_DETAIL_NAMESPACE_BEGIN - return static_cast(-1.) * std::numeric_limits::infinity(); - } +/////////////////// +// from_node // +/////////////////// - // check if the value is not a number - if (std::isnan(tmp_float)) { - return std::numeric_limits::quiet_NaN(); - } +// utility type traits and functors - // check if the value is expressible as FloatType. - if FK_YAML_UNLIKELY (tmp_float < std::numeric_limits::lowest()) { - throw exception("Floating point value underflow detected."); - } - if FK_YAML_UNLIKELY (std::numeric_limits::max() < tmp_float) { - throw exception("Floating point value overflow detected."); - } +/// @brief Utility traits type alias to detect constructible associative container types from a mapping node, e.g., +/// std::map or std::unordered_map. +/// @tparam T A target type for detection. +template +using is_constructible_mapping_type = + conjunction, detect::has_mapped_type, detect::has_value_type>; - return static_cast(tmp_float); +/// @brief Utility traits type alias to detect constructible container types from a sequence node, e.g., std::vector or +/// std::list. +/// @tparam BasicNodeType A basic_node template instance type. +/// @tparam T A target type for detection. +template +using is_constructible_sequence_type = conjunction< + negation>, detect::has_iterator, detect::is_iterator_traits, + detect::has_begin_end, negation>, + negation>>; + +/// @brief Utility traits type alias to detect a sequence container adapter type, e.g., std::stack or std::queue. +/// @tparam T A target type for detection. +template +using is_sequence_container_adapter = conjunction< + negation>, detect::has_container_type, detect::has_value_type, + negation>>; + +/// @brief Helper struct for reserve() member function call switch for types which do not have reserve function. +/// @tparam ContainerType A container type. +template +struct call_reserve_if_available { + /// @brief Do nothing since ContainerType does not have reserve function. + static void call(ContainerType& /*unused*/, typename ContainerType::size_type /*unused*/) { } }; -/// @brief from_node function for floating point values. -/// @note If node's value is null, boolean, or integer, such a value is converted into a floating point internally. -/// @tparam BasicNodeType A basic_node template instance type. -/// @tparam FloatType A floating point value type. -/// @param n A node object. -/// @param f Storage for a float point value. -template < - typename BasicNodeType, typename FloatType, - enable_if_t, std::is_floating_point>::value, int> = 0> -inline void from_node(const BasicNodeType& n, FloatType& f) { - switch (n.get_type()) { - case node_type::NULL_OBJECT: - // nullptr is interpreted as 0.0 - f = static_cast(0.); - break; - case node_type::BOOLEAN: - f = static_cast(n.as_bool()) ? static_cast(1.) : static_cast(0.); - break; - case node_type::INTEGER: - f = static_cast(n.as_int()); - break; - case node_type::FLOAT: - f = from_node_float_helper::convert(n); - break; - case node_type::SEQUENCE: - case node_type::MAPPING: - case node_type::STRING: - default: - throw type_error("The target node value type is not compatible with float number type.", n.get_type()); +/// @brief Helper struct for reserve() member function call switch for types which have reserve function. +/// @tparam ContainerType A container type. +template +struct call_reserve_if_available::value>> { + /// @brief Call reserve function on the ContainerType object with a given size. + /// @param c A container object. + /// @param n A size to reserve. + static void call(ContainerType& c, typename ContainerType::size_type n) { + c.reserve(n); } -} +}; -/// @brief from_node function for BasicNodeType::string_type objects. +// from_node() implementations + +/// @brief from_node function for C-style 1D arrays whose element type must be a basic_node template instance type or a +/// compatible type. /// @tparam BasicNodeType A basic_node template instance type. +/// @tparam T Element type of C-style 1D array. +/// @tparam N Size of the array. /// @param n A basic_node object. -/// @param s A string node value object. -template ::value, int> = 0> -inline void from_node(const BasicNodeType& n, typename BasicNodeType::string_type& s) { - if FK_YAML_UNLIKELY (!n.is_string()) { - throw type_error("The target node value type is not string type.", n.get_type()); +/// @param array An array object. +template +inline auto from_node(const BasicNodeType& n, T (&array)[N]) + -> decltype(n.get_value_inplace(std::declval()), void()) { + if FK_YAML_UNLIKELY (!n.is_sequence()) { + throw type_error("The target node value type is not sequence type.", n.get_type()); + } + + // call get_value_inplace(), not get_value(), since the storage to fill the result into is already created. + for (std::size_t i = 0; i < N; i++) { + n.at(i).get_value_inplace(array[i]); } - s = n.as_str(); } -/// @brief from_node function for compatible string type. +/// @brief from_node function for C-style 2D arrays whose element type must be a basic_node template instance type or a +/// compatible type. /// @tparam BasicNodeType A basic_node template instance type. -/// @tparam CompatibleStringType A compatible string type. +/// @tparam T Element type of C-style 2D array. +/// @tparam N0 Size of the outer dimension. +/// @tparam N1 Size of the inner dimension. /// @param n A basic_node object. -/// @param s A compatible string object. -template < - typename BasicNodeType, typename CompatibleStringType, - enable_if_t< - conjunction< - is_basic_node, - negation>, - disjunction< - std::is_constructible, - std::is_assignable>>::value, - int> = 0> -inline void from_node(const BasicNodeType& n, CompatibleStringType& s) { - if FK_YAML_UNLIKELY (!n.is_string()) { - throw type_error("The target node value type is not string type.", n.get_type()); +/// @param array An array object. +template +inline auto from_node(const BasicNodeType& n, T (&array)[N0][N1]) + -> decltype(n.get_value_inplace(std::declval()), void()) { + if FK_YAML_UNLIKELY (!n.is_sequence()) { + throw type_error("The target node value type is not sequence type.", n.get_type()); + } + + // call get_value_inplace(), not get_value(), since the storage to fill the result into is already created. + for (std::size_t i0 = 0; i0 < N0; i0++) { + for (std::size_t i1 = 0; i1 < N1; i1++) { + n.at(i0).at(i1).get_value_inplace(array[i0][i1]); + } } - s = n.as_str(); } -/// @brief from_node function for std::pair objects whose element types must be either a basic_node template instance -/// type or a compatible type. +/// @brief from_node function for C-style 2D arrays whose element type must be a basic_node template instance type or a +/// compatible type. /// @tparam BasicNodeType A basic_node template instance type. -/// @tparam T The first type of the std::pair. -/// @tparam U The second type of the std::pair. +/// @tparam T Element type of C-style 2D array. +/// @tparam N0 Size of the outermost dimension. +/// @tparam N1 Size of the middle dimension. +/// @tparam N2 Size of the innermost dimension. /// @param n A basic_node object. -/// @param p A std::pair object. -template ::value, int> = 0> -inline auto from_node(const BasicNodeType& n, std::pair& p) - -> decltype(std::declval().template get_value(), std::declval().template get_value(), void()) { +/// @param array An array object. +template +inline auto from_node(const BasicNodeType& n, T (&array)[N0][N1][N2]) + -> decltype(n.get_value_inplace(std::declval()), void()) { if FK_YAML_UNLIKELY (!n.is_sequence()) { throw type_error("The target node value type is not sequence type.", n.get_type()); } // call get_value_inplace(), not get_value(), since the storage to fill the result into is already created. - n.at(0).get_value_inplace(p.first); - n.at(1).get_value_inplace(p.second); -} - -/// @brief concrete implementation of from_node function for std::tuple objects. -/// @tparam BasicNodeType A basic_node template instance type. -/// @tparam ...Types The value types of std::tuple. -/// @tparam ...Idx Index sequence values for std::tuples value types. -/// @param n A basic_node object -/// @param _ Index sequence values (unused). -/// @return A std::tuple object converted from the sequence node values. -template -inline std::tuple from_node_tuple_impl(const BasicNodeType& n, index_sequence /*unused*/) { - return std::make_tuple(n.at(Idx).template get_value()...); + for (std::size_t i0 = 0; i0 < N0; i0++) { + for (std::size_t i1 = 0; i1 < N1; i1++) { + for (std::size_t i2 = 0; i2 < N2; i2++) { + n.at(i0).at(i1).at(i2).get_value_inplace(array[i0][i1][i2]); + } + } + } } -/// @brief from_node function for std::tuple objects whose value types must all be either a basic_node template instance -/// type or a compatible type +/// @brief from_node function for std::array objects whose element type must be a basic_node template instance type or a +/// compatible type. This function is necessary since insert function is not implemented for std::array. /// @tparam BasicNodeType A basic_node template instance type. -/// @tparam ...Types Value types of std::tuple. +/// @tparam T Element type of std::array. +/// @tparam N Size of std::array. /// @param n A basic_node object. -/// @param t A std::tuple object. -template ::value, int> = 0> -inline void from_node(const BasicNodeType& n, std::tuple& t) { +/// @param arr A std::array object. +template +inline auto from_node(const BasicNodeType& n, std::array& arr) + -> decltype(n.get_value_inplace(std::declval()), void()) { if FK_YAML_UNLIKELY (!n.is_sequence()) { throw type_error("The target node value type is not sequence type.", n.get_type()); } - // Types... must be explicitly specified; the return type would otherwise be std::tuple with no value types. - t = from_node_tuple_impl(n, index_sequence_for {}); + for (std::size_t i = 0; i < N; i++) { + // call get_value_inplace(), not get_value(), since the storage to fill the result into is already created. + n.at(i).get_value_inplace(arr.at(i)); + } } -#ifdef FK_YAML_HAS_CXX_17 - -/// @brief from_node function for std::optional objects whose value type must be either a basic_node template instance -/// type or a compatible type. +/// @brief from_node function for std::valarray objects whose element type must be a basic_node template instance type +/// or a compatible type. This function is necessary since insert function is not implemented for std::valarray. /// @tparam BasicNodeType A basic_node template instance type. -/// @tparam T A value type of the std::optional. +/// @tparam T Element type of std::valarray. /// @param n A basic_node object. -/// @param o A std::optional object. -template ::value, int> = 0> -inline auto from_node(const BasicNodeType& n, std::optional& o) -> decltype(n.template get_value(), void()) { - try { - o.emplace(n.template get_value()); +/// @param va A std::valarray object. +template +inline auto from_node(const BasicNodeType& n, std::valarray& va) + -> decltype(n.get_value_inplace(std::declval()), void()) { + if FK_YAML_UNLIKELY (!n.is_sequence()) { + throw type_error("The target node value type is not sequence type.", n.get_type()); } - catch (const std::exception& /*unused*/) { - // Any exception derived from std::exception is interpreted as a conversion failure in some way - // since user-defined from_node function may throw a different object from a fkyaml::type_error. - // and std::exception is usually the base class of user-defined exception types. - o = std::nullopt; + + std::size_t count = n.size(); + va.resize(count); + for (std::size_t i = 0; i < count; i++) { + // call get_value_inplace(), not get_value(), since the storage to fill the result into is already created. + n.at(i).get_value_inplace(va[i]); } } -#endif // defined(FK_YAML_HAS_CXX_17) - -/// @brief A function object to call from_node functions. -/// @note User-defined specialization is available by providing implementation **OUTSIDE** fkyaml namespace. -struct from_node_fn { - /// @brief Call from_node function suitable for the given T type. - /// @tparam BasicNodeType A basic_node template instance type. - /// @tparam T A target value type assigned from the basic_node object. - /// @param n A basic_node object. - /// @param val A target object assigned from the basic_node object. - /// @return decltype(from_node(n, std::forward(val))) void by default. User can set it to some other type. - template - auto operator()(const BasicNodeType& n, T&& val) const - noexcept(noexcept(from_node(n, std::forward(val)))) -> decltype(from_node(n, std::forward(val))) { - return from_node(n, std::forward(val)); +/// @brief from_node function for std::forward_list objects whose element type must be a basic_node template instance +/// type or a compatible type. This function is necessary since insert function is not implemented for +/// std::forward_list. +/// @tparam BasicNodeType A basic_node template instance type. +/// @tparam T Element type of std::forward_list. +/// @tparam Alloc Allocator type of std::forward_list. +/// @param n A basic_node object. +/// @param fl A std::forward_list object. +template +inline auto from_node(const BasicNodeType& n, std::forward_list& fl) + -> decltype(n.template get_value(), void()) { + if FK_YAML_UNLIKELY (!n.is_sequence()) { + throw type_error("The target node value is not sequence type.", n.get_type()); } -}; - -FK_YAML_DETAIL_NAMESPACE_END - -FK_YAML_NAMESPACE_BEGIN - -#ifndef FK_YAML_HAS_CXX_17 -// anonymous namespace to hold `from_node` functor. -// see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html for why it's needed. -namespace // NOLINT(cert-dcl59-cpp,fuchsia-header-anon-namespaces,google-build-namespaces) -{ -#endif - -/// @brief A global object to represent ADL friendly from_node functor. -// NOLINTNEXTLINE(misc-definitions-in-headers) -FK_YAML_INLINE_VAR constexpr const auto& from_node = detail::static_const::value; - -#ifndef FK_YAML_HAS_CXX_17 -} // namespace -#endif - -FK_YAML_NAMESPACE_END - -#endif /* FK_YAML_DETAIL_CONVERSIONS_FROM_NODE_HPP */ - -// #include -// _______ __ __ __ _____ __ __ __ -// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library -// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 -// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML -// -// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani -// SPDX-License-Identifier: MIT - -#ifndef FK_YAML_DETAIL_CONVERSIONS_TO_NODE_HPP -#define FK_YAML_DETAIL_CONVERSIONS_TO_NODE_HPP -#include - -// #include - -// #include + fl.clear(); -// #include + // std::forward_list does not have insert function. + auto insert_pos_itr = fl.before_begin(); + for (const auto& elem : n) { + insert_pos_itr = fl.emplace_after(insert_pos_itr, elem.template get_value()); + } +} -// #include +/// @brief from_node function for container objects of only keys or values, e.g., std::vector or std::set, whose element +/// type must be a basic_node template instance type or a compatible type. +/// @tparam BasicNodeType A basic_node template instance type. +/// @tparam CompatSeqType A container type. +/// @param n A basic_node object. +/// @param s A container object. +template < + typename BasicNodeType, typename CompatSeqType, + enable_if_t< + conjunction< + is_basic_node, is_constructible_sequence_type, + negation>>::value, + int> = 0> +inline auto from_node(const BasicNodeType& n, CompatSeqType& s) + -> decltype(n.template get_value(), void()) { + if FK_YAML_UNLIKELY (!n.is_sequence()) { + throw type_error("The target node value is not sequence type.", n.get_type()); + } -// #include + s.clear(); -// #include + // call reserve function first if it's available (like std::vector). + call_reserve_if_available::call(s, n.size()); -// #include + // transform a sequence node into a destination type object by calling insert function. + using std::end; + std::transform(n.begin(), n.end(), std::inserter(s, end(s)), [](const BasicNodeType& elem) { + return elem.template get_value(); + }); +} +/// @brief from_node function for sequence container adapter objects, e.g., std::stack or std::queue, whose element type +/// must be either a basic_node template instance type or a compatible type. +/// @tparam BasicNodeType A basic_node template instance type. +/// @tparam SeqContainerAdapter A sequence container adapter type. +/// @param n A node object. +/// @param ca A sequence container adapter object. +template < + typename BasicNodeType, typename SeqContainerAdapter, + enable_if_t< + conjunction, is_sequence_container_adapter>::value, int> = 0> +inline auto from_node(const BasicNodeType& n, SeqContainerAdapter& ca) + -> decltype(n.template get_value(), ca.push(std::declval()), void()) { + if FK_YAML_UNLIKELY (!n.is_sequence()) { + throw type_error("The target node value is not sequence type.", n.get_type()); + } -FK_YAML_DETAIL_NAMESPACE_BEGIN + // clear existing elements manually since clear function is not implemented for container adapter classes. + while (!ca.empty()) { + ca.pop(); + } -/////////////////////////////////// -// external_node_constructor // -/////////////////////////////////// + for (const auto& elem : n) { + // container adapter classes commonly have push function. + // emplace function cannot be used in case SeqContainerAdapter::container_type is std::vector in C++11. + ca.push(elem.template get_value()); + } +} -/// @brief The external constructor template for basic_node objects. -/// @note All the non-specialized instantiations results in compilation error since such instantiations are not -/// supported. -/// @warning All the specialization must call n.m_value.destroy() first in the construct function to avoid -/// memory leak. -/// @tparam node_type The resulting YAML node value type. -template -struct external_node_constructor { - template - static void sequence(BasicNodeType& n, Args&&... args) { - destroy(n); - n.m_attrs |= node_attr_bits::seq_bit; - n.m_value.p_seq = create_object(std::forward(args)...); +/// @brief from_node function for mappings whose key and value are of both compatible types. +/// @tparam BasicNodeType A basic_node template instance type. +/// @tparam CompatibleKeyType Mapping key type compatible with BasicNodeType. +/// @tparam CompatibleValueType Mapping value type compatible with BasicNodeType. +/// @tparam Compare Comparator type for mapping keys. +/// @tparam Allocator Allocator type for destination mapping object. +/// @param n A node object. +/// @param m Mapping container object to store converted key/value objects. +template ::value, int> = 0> +inline auto from_node(const BasicNodeType& n, CompatMapType& m) + -> decltype( + std::declval().template get_value(), + std::declval().template get_value(), + m.emplace(std::declval(), std::declval()), + void()) { + if FK_YAML_UNLIKELY (!n.is_mapping()) { + throw type_error("The target node value type is not mapping type.", n.get_type()); } - template - static void mapping(BasicNodeType& n, Args&&... args) { - destroy(n); - n.m_attrs |= node_attr_bits::map_bit; - n.m_value.p_map = create_object(std::forward(args)...); - } + m.clear(); + call_reserve_if_available::call(m, n.size()); - static void null_scalar(BasicNodeType& n, std::nullptr_t) { - destroy(n); - n.m_attrs |= node_attr_bits::null_bit; - n.m_value.p_map = nullptr; + for (const auto& pair : n.as_map()) { + m.emplace( + pair.first.template get_value(), + pair.second.template get_value()); } +} - static void boolean_scalar(BasicNodeType& n, const typename BasicNodeType::boolean_type b) { - destroy(n); - n.m_attrs |= node_attr_bits::bool_bit; - n.m_value.boolean = b; +/// @brief from_node function for nullptr. +/// @tparam BasicNodeType A basic_node template instance type. +/// @param n A node object. +/// @param null Storage for a null value. +template ::value, int> = 0> +inline void from_node(const BasicNodeType& n, std::nullptr_t& null) { + // to ensure the target node value type is null. + if FK_YAML_UNLIKELY (!n.is_null()) { + throw type_error("The target node value type is not null type.", n.get_type()); } + null = nullptr; +} - static void integer_scalar(BasicNodeType& n, const typename BasicNodeType::integer_type i) { - destroy(n); - n.m_attrs |= node_attr_bits::int_bit; - n.m_value.integer = i; +/// @brief from_node function for booleans. +/// @tparam BasicNodeType A basic_node template instance type. +/// @param n A node object. +/// @param b Storage for a boolean value. +template ::value, int> = 0> +inline void from_node(const BasicNodeType& n, bool& b) { + switch (n.get_type()) { + case node_type::NULL_OBJECT: + // nullptr is converted to false just as C++ implicitly does. + b = false; + break; + case node_type::BOOLEAN: + b = static_cast(n.as_bool()); + break; + case node_type::INTEGER: + // true: non-zero, false: zero + b = (n.as_int() != 0); + break; + case node_type::FLOAT: + // true: non-zero, false: zero + using float_type = typename BasicNodeType::float_number_type; + b = (n.as_float() != static_cast(0.)); + break; + case node_type::SEQUENCE: + case node_type::MAPPING: + case node_type::STRING: + default: + throw type_error("The target node value type is not compatible with boolean type.", n.get_type()); } +} - static void float_scalar(BasicNodeType& n, const typename BasicNodeType::float_number_type f) { - destroy(n); - n.m_attrs |= node_attr_bits::float_bit; - n.m_value.float_val = f; +/// @brief Helper struct for node-to-int conversion. +/// @tparam BasicNodeType A basic_node template instance type. +/// @tparam IntType Target integer value type (same as BasicNodeType::integer_type) +template < + typename BasicNodeType, typename IntType, bool = std::is_same::value> +struct from_node_int_helper { + /// @brief Convert node's integer value to the target integer type. + /// @param n A node object. + /// @return An integer value converted from the node's integer value. + static IntType convert(const BasicNodeType& n) { + return n.as_int(); } +}; - template - static void string_scalar(BasicNodeType& n, Args&&... args) { - destroy(n); - n.m_attrs |= node_attr_bits::string_bit; - n.m_value.p_str = create_object(std::forward(args)...); - } +/// @brief Helper struct for node-to-int conversion if IntType is not the node's integer value type. +/// @tparam BasicNodeType A basic_node template instance type. +/// @tparam IntType Target integer value type (different from BasicNodeType::integer_type) +template +struct from_node_int_helper { + /// @brief Convert node's integer value to non-uint64_t integer types. + /// @param n A node object. + /// @return An integer value converted from the node's integer value. + static IntType convert(const BasicNodeType& n) { + using node_int_type = typename BasicNodeType::integer_type; + const node_int_type tmp_int = n.as_int(); -private: - static void destroy(BasicNodeType& n) { - n.m_value.destroy(n.m_attrs & node_attr_mask::value); - n.m_attrs &= ~node_attr_mask::value; + // under/overflow check. + if (std::is_same::value) { + // Nodes marked with uint_bit store a uint64_t bit pattern in a signed field. + // Recover the original unsigned value without a sign check. + if (n.is_uint()) { + return static_cast(tmp_int); + } + if FK_YAML_UNLIKELY (tmp_int < 0) { + throw exception("Integer value underflow detected."); + } + } + else { + if FK_YAML_UNLIKELY (tmp_int < static_cast(std::numeric_limits::min())) { + throw exception("Integer value underflow detected."); + } + if FK_YAML_UNLIKELY (static_cast(std::numeric_limits::max()) < tmp_int) { + throw exception("Integer value overflow detected."); + } + } + + return static_cast(tmp_int); } }; -///////////////// -// to_node // -///////////////// - -/// @brief to_node function for BasicNodeType::sequence_type objects. +/// @brief from_node function for integers. +/// @note If node's value is null, boolean, or float, such a value is converted into an integer internally. /// @tparam BasicNodeType A basic_node template instance type. -/// @tparam T A sequence node value type. -/// @param n A basic_node object. -/// @param s A sequence node value object. +/// @tparam IntegerType An integer value type. +/// @param n A node object. +/// @param i Storage for an integer value. template < - typename BasicNodeType, typename T, - enable_if_t< - conjunction< - is_basic_node, - std::is_same>>::value, - int> = 0> -inline void to_node(BasicNodeType& n, T&& s) noexcept { - external_node_constructor::sequence(n, std::forward(s)); + typename BasicNodeType, typename IntegerType, + enable_if_t, is_non_bool_integral>::value, int> = 0> +inline void from_node(const BasicNodeType& n, IntegerType& i) { + switch (n.get_type()) { + case node_type::NULL_OBJECT: + // nullptr is interpreted as 0 + i = static_cast(0); + break; + case node_type::BOOLEAN: + i = static_cast(n.as_bool()) ? static_cast(1) : static_cast(0); + break; + case node_type::INTEGER: + i = from_node_int_helper::convert(n); + break; + case node_type::FLOAT: { + // int64_t should be safe to express the integer part of possible floating point types. + const auto tmp_int = static_cast(n.as_float()); + + // under/overflow check. + if (std::is_same::value) { + if FK_YAML_UNLIKELY (tmp_int < 0) { + throw exception("Integer value underflow detected."); + } + } + else { + if FK_YAML_UNLIKELY (tmp_int < static_cast(std::numeric_limits::min())) { + throw exception("Integer value underflow detected."); + } + if FK_YAML_UNLIKELY (static_cast(std::numeric_limits::max()) < tmp_int) { + throw exception("Integer value overflow detected."); + } + } + + i = static_cast(tmp_int); + break; + } + case node_type::SEQUENCE: + case node_type::MAPPING: + case node_type::STRING: + default: + throw type_error("The target node value type is not compatible with integer type.", n.get_type()); + } } -/// @brief to_node function for compatible sequence types. -/// @note This overload is enabled when -/// * both begin()/end() functions are callable on a `CompatSeqType` object -/// * CompatSeqType doesn't have `mapped_type` (mapping-like type) -/// * BasicNodeType::string_type cannot be constructed from a CompatSeqType object (string-like type) +/// @brief Helper struct for node-to-float conversion if FloatType is the node's floating point value type. /// @tparam BasicNodeType A basic_node template instance type. -/// @tparam CompatSeqType A container type. -/// @param n A basic_node object. -/// @param s A container object. +/// @tparam FloatType Target floating point value type (same as the BasicNodeType::float_number_type) template < - typename BasicNodeType, typename CompatSeqType, - enable_if_t< - conjunction< - is_basic_node, - negation>>, - negation>, detect::has_begin_end, - negation, detect::has_mapped_type>>, - negation>>::value, - int> = 0> -// NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward) -inline void to_node(BasicNodeType& n, CompatSeqType&& s) { - using std::begin; - using std::end; - external_node_constructor::sequence(n, begin(s), end(s)); -} + typename BasicNodeType, typename FloatType, + bool = std::is_same::value> +struct from_node_float_helper { + /// @brief Convert node's floating point value to the target floating point type. + /// @param n A node object. + /// @return A floating point value converted from the node's floating point value. + static FloatType convert(const BasicNodeType& n) { + return n.as_float(); + } +}; -/// @brief to_node function for std::pair objects. +/// @brief Helper struct for node-to-float conversion if IntType is not the node's floating point value type. /// @tparam BasicNodeType A basic_node template instance type. -/// @tparam T The first type of std::pair. -/// @tparam U The second type of std::pair. -/// @param n A basic_node object. -/// @param p A std::pair object. -template -inline void to_node(BasicNodeType& n, const std::pair& p) { - n = {p.first, p.second}; -} +/// @tparam FloatType Target floating point value type (different from BasicNodeType::float_number_type) +template +struct from_node_float_helper { + /// @brief Convert node's floating point value to the target floating point type. + /// @param n A node object. + /// @return A floating point value converted from the node's floating point value. + static FloatType convert(const BasicNodeType& n) { + using node_float_type = typename BasicNodeType::float_number_type; + auto tmp_float = n.as_float(); + + // check if the value is an infinite number (either positive or negative) + if (std::isinf(tmp_float)) { + if (tmp_float == std::numeric_limits::infinity()) { + return std::numeric_limits::infinity(); + } + + return static_cast(-1.) * std::numeric_limits::infinity(); + } + + // check if the value is not a number + if (std::isnan(tmp_float)) { + return std::numeric_limits::quiet_NaN(); + } -/// @brief concrete implementation of to_node function for std::tuple objects. -/// @tparam BasicNodeType A basic_node template instance type. -/// @tparam ...Types The value types of std::tuple. -/// @tparam ...Idx Index sequence values for std::tuple value types. -/// @param n A basic_node object. -/// @param t A std::tuple object. -/// @param _ An index sequence. (unused) -template -inline void to_node_tuple_impl(BasicNodeType& n, const std::tuple& t, index_sequence /*unused*/) { - n = {std::get(t)...}; -} + // check if the value is expressible as FloatType. + if FK_YAML_UNLIKELY (tmp_float < std::numeric_limits::lowest()) { + throw exception("Floating point value underflow detected."); + } + if FK_YAML_UNLIKELY (std::numeric_limits::max() < tmp_float) { + throw exception("Floating point value overflow detected."); + } -/// @brief to_node function for std::tuple objects with no value types. -/// @note This implementation is needed since calling `to_node_tuple_impl()` with an empty tuple creates a null node. + return static_cast(tmp_float); + } +}; + +/// @brief from_node function for floating point values. +/// @note If node's value is null, boolean, or integer, such a value is converted into a floating point internally. /// @tparam BasicNodeType A basic_node template instance type. -/// @param n A basic_node object. -/// @param _ A std::tuple object. (unused) -template -inline void to_node(BasicNodeType& n, const std::tuple<>& /*unused*/) { - n = BasicNodeType::sequence(); +/// @tparam FloatType A floating point value type. +/// @param n A node object. +/// @param f Storage for a float point value. +template < + typename BasicNodeType, typename FloatType, + enable_if_t, std::is_floating_point>::value, int> = 0> +inline void from_node(const BasicNodeType& n, FloatType& f) { + switch (n.get_type()) { + case node_type::NULL_OBJECT: + // nullptr is interpreted as 0.0 + f = static_cast(0.); + break; + case node_type::BOOLEAN: + f = static_cast(n.as_bool()) ? static_cast(1.) : static_cast(0.); + break; + case node_type::INTEGER: + f = static_cast(n.as_int()); + break; + case node_type::FLOAT: + f = from_node_float_helper::convert(n); + break; + case node_type::SEQUENCE: + case node_type::MAPPING: + case node_type::STRING: + default: + throw type_error("The target node value type is not compatible with float number type.", n.get_type()); + } } -/// @brief to_node function for std::tuple objects with at least one value type. +/// @brief from_node function for BasicNodeType::string_type objects. /// @tparam BasicNodeType A basic_node template instance type. -/// @tparam ...FirstType The first value types of std::tuple. -/// @tparam ...RestTypes The rest value types of std::tuple. (maybe empty) /// @param n A basic_node object. -/// @param t A std::tuple object. -template -inline void to_node(BasicNodeType& n, const std::tuple& t) { - to_node_tuple_impl(n, t, index_sequence_for {}); +/// @param s A string node value object. +template ::value, int> = 0> +inline void from_node(const BasicNodeType& n, typename BasicNodeType::string_type& s) { + if FK_YAML_UNLIKELY (!n.is_string()) { + throw type_error("The target node value type is not string type.", n.get_type()); + } + s = n.as_str(); } -/// @brief to_node function for BasicNodeType::mapping_type objects. +/// @brief from_node function for compatible string type. /// @tparam BasicNodeType A basic_node template instance type. -/// @tparam T A mapping node value type. +/// @tparam CompatibleStringType A compatible string type. /// @param n A basic_node object. -/// @param m A mapping node value object. +/// @param s A compatible string object. template < - typename BasicNodeType, typename T, + typename BasicNodeType, typename CompatibleStringType, enable_if_t< conjunction< - is_basic_node, std::is_same>>::value, + is_basic_node, + negation>, + disjunction< + std::is_constructible, + std::is_assignable>>::value, int> = 0> -inline void to_node(BasicNodeType& n, T&& m) noexcept { - external_node_constructor::mapping(n, std::forward(m)); +inline void from_node(const BasicNodeType& n, CompatibleStringType& s) { + if FK_YAML_UNLIKELY (!n.is_string()) { + throw type_error("The target node value type is not string type.", n.get_type()); + } + s = n.as_str(); } -/// @brief to_node function for compatible mapping types. -/// @note This overload is enabled when -/// * both begin()/end() functions are callable on a `CompatMapType` object -/// * CompatMapType has both `key_type` and `mapped_type` +/// @brief from_node function for std::pair objects whose element types must be either a basic_node template instance +/// type or a compatible type. /// @tparam BasicNodeType A basic_node template instance type. -/// @tparam CompatMapType A container type. +/// @tparam T The first type of the std::pair. +/// @tparam U The second type of the std::pair. /// @param n A basic_node object. -/// @param m A container object. -template < - typename BasicNodeType, typename CompatMapType, - enable_if_t< - conjunction< - is_basic_node, negation>, - negation>>, - detect::has_begin_end, detect::has_key_type, - detect::has_mapped_type>::value, - int> = 0> -inline void to_node(BasicNodeType& n, CompatMapType&& m) { - external_node_constructor::mapping(n); - auto& map = n.as_map(); - for (const auto& pair : std::forward(m)) { - map.emplace(pair.first, pair.second); +/// @param p A std::pair object. +template ::value, int> = 0> +inline auto from_node(const BasicNodeType& n, std::pair& p) + -> decltype(std::declval().template get_value(), std::declval().template get_value(), void()) { + if FK_YAML_UNLIKELY (!n.is_sequence()) { + throw type_error("The target node value type is not sequence type.", n.get_type()); } -} -/// @brief to_node function for null objects. -/// @tparam BasicNodeType A mapping node value type. -/// @tparam NullType This must be std::nullptr_t type -template ::value, int> = 0> -inline void to_node(BasicNodeType& n, std::nullptr_t /*unused*/) { - external_node_constructor::null_scalar(n, nullptr); + // call get_value_inplace(), not get_value(), since the storage to fill the result into is already created. + n.at(0).get_value_inplace(p.first); + n.at(1).get_value_inplace(p.second); } -/// @brief to_node function for BasicNodeType::boolean_type objects. +/// @brief concrete implementation of from_node function for std::tuple objects. /// @tparam BasicNodeType A basic_node template instance type. -/// @tparam T A boolean scalar node value type. -/// @param n A basic_node object. -/// @param b A boolean scalar node value object. -template ::value, int> = 0> -inline void to_node(BasicNodeType& n, typename BasicNodeType::boolean_type b) noexcept { - external_node_constructor::boolean_scalar(n, b); +/// @tparam ...Types The value types of std::tuple. +/// @tparam ...Idx Index sequence values for std::tuples value types. +/// @param n A basic_node object +/// @param _ Index sequence values (unused). +/// @return A std::tuple object converted from the sequence node values. +template +inline std::tuple from_node_tuple_impl(const BasicNodeType& n, index_sequence /*unused*/) { + return std::make_tuple(n.at(Idx).template get_value()...); } -/// @brief to_node function for integers. +/// @brief from_node function for std::tuple objects whose value types must all be either a basic_node template instance +/// type or a compatible type /// @tparam BasicNodeType A basic_node template instance type. -/// @tparam T An integer type. +/// @tparam ...Types Value types of std::tuple. /// @param n A basic_node object. -/// @param i An integer object. -template < - typename BasicNodeType, typename T, - enable_if_t, is_non_bool_integral>::value, int> = 0> -inline void to_node(BasicNodeType& n, T i) noexcept { - external_node_constructor::integer_scalar(n, i); -} +/// @param t A std::tuple object. +template ::value, int> = 0> +inline void from_node(const BasicNodeType& n, std::tuple& t) { + if FK_YAML_UNLIKELY (!n.is_sequence()) { + throw type_error("The target node value type is not sequence type.", n.get_type()); + } -/// @brief to_node function for floating point numbers. -/// @tparam BasicNodeType A basic_node template instance type. -/// @tparam T A floating point number type. -/// @param n A basic_node object. -/// @param f A floating point number object. -template < - typename BasicNodeType, typename T, - enable_if_t, std::is_floating_point>::value, int> = 0> -inline void to_node(BasicNodeType& n, T f) noexcept { - external_node_constructor::float_scalar(n, f); + // Types... must be explicitly specified; the return type would otherwise be std::tuple with no value types. + t = from_node_tuple_impl(n, index_sequence_for {}); } -/// @brief to_node function for compatible strings. +#ifdef FK_YAML_HAS_CXX_17 + +/// @brief from_node function for std::optional objects whose value type must be either a basic_node template instance +/// type or a compatible type. /// @tparam BasicNodeType A basic_node template instance type. -/// @tparam T A compatible string type. +/// @tparam T A value type of the std::optional. /// @param n A basic_node object. -/// @param s A compatible string object. -template < - typename BasicNodeType, typename T, - enable_if_t< - conjunction< - is_basic_node, negation>, - std::is_constructible>::value, - int> = 0> -inline void to_node(BasicNodeType& n, T&& s) { - external_node_constructor::string_scalar(n, std::forward(s)); +/// @param o A std::optional object. +template ::value, int> = 0> +inline auto from_node(const BasicNodeType& n, std::optional& o) -> decltype(n.template get_value(), void()) { + try { + o.emplace(n.template get_value()); + } + catch (const std::exception& /*unused*/) { + // Any exception derived from std::exception is interpreted as a conversion failure in some way + // since user-defined from_node function may throw a different object from a fkyaml::type_error. + // and std::exception is usually the base class of user-defined exception types. + o = std::nullopt; + } } -/// @brief A function object to call to_node functions. +#endif // defined(FK_YAML_HAS_CXX_17) + +/// @brief A function object to call from_node functions. /// @note User-defined specialization is available by providing implementation **OUTSIDE** fkyaml namespace. -struct to_node_fn { - /// @brief Call to_node function suitable for the given T type. +struct from_node_fn { + /// @brief Call from_node function suitable for the given T type. /// @tparam BasicNodeType A basic_node template instance type. - /// @tparam T A target value type assigned to the basic_node object. + /// @tparam T A target value type assigned from the basic_node object. /// @param n A basic_node object. - /// @param val A target object assigned to the basic_node object. - /// @return decltype(to_node(n, std::forward(val))) void by default. User can set it to some other type. + /// @param val A target object assigned from the basic_node object. + /// @return decltype(from_node(n, std::forward(val))) void by default. User can set it to some other type. template - auto operator()(BasicNodeType& n, T&& val) const - noexcept(noexcept(to_node(n, std::forward(val)))) -> decltype(to_node(n, std::forward(val))) { - return to_node(n, std::forward(val)); + auto operator()(const BasicNodeType& n, T&& val) const + noexcept(noexcept(from_node(n, std::forward(val)))) -> decltype(from_node(n, std::forward(val))) { + return from_node(n, std::forward(val)); } }; @@ -12440,15 +12480,15 @@ FK_YAML_DETAIL_NAMESPACE_END FK_YAML_NAMESPACE_BEGIN #ifndef FK_YAML_HAS_CXX_17 -// anonymous namespace to hold `to_node` functor. +// anonymous namespace to hold `from_node` functor. // see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html for why it's needed. namespace // NOLINT(cert-dcl59-cpp,fuchsia-header-anon-namespaces,google-build-namespaces) { #endif -/// @brief A global object to represent ADL friendly to_node functor. +/// @brief A global object to represent ADL friendly from_node functor. // NOLINTNEXTLINE(misc-definitions-in-headers) -FK_YAML_INLINE_VAR constexpr const auto& to_node = detail::static_const::value; +FK_YAML_INLINE_VAR constexpr const auto& from_node = detail::static_const::value; #ifndef FK_YAML_HAS_CXX_17 } // namespace @@ -12456,7 +12496,9 @@ FK_YAML_INLINE_VAR constexpr const auto& to_node = detail::static_const FK_YAML_NAMESPACE_BEGIN @@ -14122,6 +14164,36 @@ class basic_node { throw fkyaml::type_error("The node value is not a boolean.", get_type()); } + /// @brief Checks if the node is an integer that was parsed from a uint64_t value exceeding INT64_MAX. + /// @return true if the node holds an unsigned integer, false otherwise. + bool is_uint() const noexcept { + return resolve_reference().is_uint_impl(); + } + + /// @brief Returns the integer node value as an unsigned 64-bit integer. + /// This is valid both for nodes where integer_type is unsigned and for nodes where a large + /// positive decimal scalar (> INT64_MAX) was stored with the uint_bit flag set. + /// @throw fkyaml::type_error if the node is not a compatible integer. + /// @return The node value as uint64_t. + uint64_t as_uint() const { + const basic_node& act_node = resolve_reference(); + if FK_YAML_LIKELY (act_node.is_integer_impl()) { + // When integer_type is unsigned the stored value IS the uint64_t directly. + if (std::is_unsigned::value) { + return static_cast(act_node.m_value.integer); + } + // When integer_type is signed, only uint_bit-marked nodes carry a uint64_t. + if (act_node.m_attrs & detail::node_attr_bits::uint_bit) { + return static_cast(act_node.m_value.integer); + } + // Signed values in the non-negative range can be returned safely. + if (act_node.m_value.integer >= static_cast(0)) { + return static_cast(act_node.m_value.integer); + } + } + throw fkyaml::type_error("The node value cannot be represented as an unsigned integer.", get_type()); + } + /// @brief Returns reference to the integer node value. /// @throw fkyaml::type_error The node value is not an integer. /// @return Reference to the integer node value. @@ -14129,18 +14201,30 @@ class basic_node { integer_type& as_int() { basic_node& act_node = resolve_reference(); if FK_YAML_LIKELY (act_node.is_integer_impl()) { + if FK_YAML_UNLIKELY (act_node.is_uint_impl()) { + throw fkyaml::type_error( + "The integer value exceeds INT64_MAX and cannot be returned as a signed integer. " + "Use as_uint() instead.", + get_type()); + } return act_node.m_value.integer; } throw fkyaml::type_error("The node value is not an integer.", get_type()); } /// @brief Returns reference to the integer node value. - /// @throw fkyaml::type_error The node value is not an integer. + /// @throw fkyaml::type_error The node value is not an integer, or exceeds INT64_MAX. /// @return Constant reference to the integer node value. /// @sa https://fktn-k.github.io/fkYAML/api/basic_node/as_int/ const integer_type& as_int() const { const basic_node& act_node = resolve_reference(); if FK_YAML_LIKELY (act_node.is_integer_impl()) { + if FK_YAML_UNLIKELY (act_node.is_uint_impl()) { + throw fkyaml::type_error( + "The integer value exceeds INT64_MAX and cannot be returned as a signed integer. " + "Use as_uint() instead.", + get_type()); + } return act_node.m_value.integer; } throw fkyaml::type_error("The node value is not an integer.", get_type()); @@ -14418,6 +14502,12 @@ class basic_node { return m_attrs & detail::node_attr_bits::int_bit; } + bool is_uint_impl() const noexcept { + // Both int_bit and uint_bit must be set: this node stores a uint64_t value + // whose bit pattern was placed into the signed integer_type field. + return (m_attrs & detail::node_attr_bits::int_bit) && (m_attrs & detail::node_attr_bits::uint_bit); + } + bool is_float_number_impl() const noexcept { return m_attrs & detail::node_attr_bits::float_bit; } diff --git a/tests/unit_test/CMakeLists.txt b/tests/unit_test/CMakeLists.txt index bca79e0e..ae2795b2 100644 --- a/tests/unit_test/CMakeLists.txt +++ b/tests/unit_test/CMakeLists.txt @@ -226,7 +226,9 @@ add_executable( test_iterator_class.cpp test_lexical_analyzer_class.cpp test_node_attrs.cpp + test_node_attrs_uint.cpp test_node_class.cpp + test_node_class_uint.cpp test_node_ref_storage_class.cpp test_node_type.cpp test_ordered_map_class.cpp @@ -234,6 +236,7 @@ add_executable( test_reverse_iterator_class.cpp test_scalar_conv.cpp test_scalar_parser_class.cpp + test_scalar_parser_class_uint.cpp test_scalar_scanner_class.cpp test_serializer_class.cpp test_str_view_class.cpp diff --git a/tests/unit_test/test_node_attrs_uint.cpp b/tests/unit_test/test_node_attrs_uint.cpp new file mode 100644 index 00000000..b360ef35 --- /dev/null +++ b/tests/unit_test/test_node_attrs_uint.cpp @@ -0,0 +1,61 @@ +// _______ __ __ __ _____ __ __ __ +// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library (supporting code) +// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 +// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML +// +// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani +// SPDX-License-Identifier: MIT + +#include + +#include + +TEST_CASE("NodeAttrs_UintBit_IsNonZero") { + REQUIRE(fkyaml::detail::node_attr_bits::uint_bit != 0u); +} + +TEST_CASE("NodeAttrs_UintBit_LivesInStyleArea") { + // uint_bit must live in 0x00FF0000 so it is orthogonal to the value-type area. + REQUIRE((fkyaml::detail::node_attr_bits::uint_bit & fkyaml::detail::node_attr_mask::style) != 0u); + REQUIRE((fkyaml::detail::node_attr_bits::uint_bit & fkyaml::detail::node_attr_mask::value) == 0u); + REQUIRE((fkyaml::detail::node_attr_bits::uint_bit & fkyaml::detail::node_attr_mask::anchoring) == 0u); +} + +TEST_CASE("NodeAttrs_UintBit_NotInScalarBits") { + // scalar_bits is a type-classification mask. uint_bit is a metadata annotation + // on an INTEGER node, not a separate type. It must not pollute scalar_bits. + REQUIRE((fkyaml::detail::node_attr_bits::uint_bit & fkyaml::detail::node_attr_bits::scalar_bits) == 0u); +} + +TEST_CASE("NodeAttrs_UintBit_InteractsWithIntBit") { + fkyaml::detail::node_attr_t attrs = 0u; + + SECTION("both bits can be set simultaneously") { + attrs |= fkyaml::detail::node_attr_bits::int_bit; + attrs |= fkyaml::detail::node_attr_bits::uint_bit; + REQUIRE((attrs & fkyaml::detail::node_attr_bits::int_bit) != 0u); + REQUIRE((attrs & fkyaml::detail::node_attr_bits::uint_bit) != 0u); + } + + SECTION("clearing uint_bit leaves int_bit intact") { + attrs = fkyaml::detail::node_attr_bits::int_bit | fkyaml::detail::node_attr_bits::uint_bit; + attrs &= ~fkyaml::detail::node_attr_bits::uint_bit; + REQUIRE((attrs & fkyaml::detail::node_attr_bits::int_bit) != 0u); + REQUIRE((attrs & fkyaml::detail::node_attr_bits::uint_bit) == 0u); + } +} + +TEST_CASE("NodeAttrs_UintBit_FromNodeTypeNeverSetsIt") { + // from_node_type has no knowledge of uint_bit; it must not accidentally set it. + fkyaml::detail::node_attr_t bits = fkyaml::detail::node_attr_bits::from_node_type(fkyaml::node_type::INTEGER); + REQUIRE(bits == fkyaml::detail::node_attr_bits::int_bit); + REQUIRE((bits & fkyaml::detail::node_attr_bits::uint_bit) == 0u); +} + +TEST_CASE("NodeAttrs_UintBit_ToNodeTypeStillReturnsInteger") { + // uint_bit lives outside the value mask, so type classification must be unchanged + // when both int_bit and uint_bit are present. + const fkyaml::detail::node_attr_t attrs = + fkyaml::detail::node_attr_bits::int_bit | fkyaml::detail::node_attr_bits::uint_bit; + REQUIRE(fkyaml::detail::node_attr_bits::to_node_type(attrs) == fkyaml::node_type::INTEGER); +} diff --git a/tests/unit_test/test_node_class_uint.cpp b/tests/unit_test/test_node_class_uint.cpp new file mode 100644 index 00000000..f3a50e4c --- /dev/null +++ b/tests/unit_test/test_node_class_uint.cpp @@ -0,0 +1,139 @@ +// _______ __ __ __ _____ __ __ __ +// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library (supporting code) +// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 +// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML +// +// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani +// SPDX-License-Identifier: MIT + +#include +#include +#include + +#include + +#include + +TEST_CASE("Node_IsUint_ReturnsFalseForNonIntegerTypes") { + SECTION("null") { + fkyaml::node n; + REQUIRE(n.is_uint() == false); + } + + SECTION("boolean") { + REQUIRE(fkyaml::node::deserialize("v: true")["v"].is_uint() == false); + } + + SECTION("float") { + REQUIRE(fkyaml::node::deserialize("v: 3.14")["v"].is_uint() == false); + } + + SECTION("string") { + REQUIRE(fkyaml::node::deserialize("v: hello")["v"].is_uint() == false); + } +} + +TEST_CASE("Node_IsUint_ReturnsFalseWithinSignedRange") { + auto input = GENERATE( + std::string("-9223372036854775808"), // INT64_MIN + std::string("-1"), + std::string("0"), + std::string("1"), + std::string("9223372036854775807")); // INT64_MAX + REQUIRE(fkyaml::node::deserialize("v: " + input)["v"].is_uint() == false); +} + +TEST_CASE("Node_IsUint_ReturnsTrueAboveSignedRange") { + auto input = GENERATE( + std::string("9223372036854775808"), // INT64_MAX + 1 + std::string("15745692345339290292"), // xxHash value from the bug report + std::string("18446744073709551615")); // UINT64_MAX + REQUIRE(fkyaml::node::deserialize("v: " + input)["v"].is_uint() == true); +} + +TEST_CASE("Node_AsUint_ReturnsCorrectValueForUInt64RangeNodes") { + using test_data_t = std::pair; + auto test_data = GENERATE( + test_data_t {"9223372036854775808", UINT64_C(9223372036854775808)}, + test_data_t {"15745692345339290292", UINT64_C(15745692345339290292)}, + test_data_t {"18446744073709551615", UINT64_C(18446744073709551615)}); + REQUIRE(fkyaml::node::deserialize("v: " + test_data.first)["v"].as_uint() == test_data.second); +} + +TEST_CASE("Node_AsUint_SucceedsForNonNegativeSignedIntegers") { + using test_data_t = std::pair; + auto test_data = GENERATE( + test_data_t {"0", 0ULL}, + test_data_t {"100", 100ULL}, + test_data_t {"9223372036854775807", static_cast(INT64_MAX)}); + REQUIRE(fkyaml::node::deserialize("v: " + test_data.first)["v"].as_uint() == test_data.second); +} + +TEST_CASE("Node_AsUint_ThrowsForNegativeIntegers") { + auto input = GENERATE(std::string("-1"), std::string("-42")); + REQUIRE_THROWS_AS(fkyaml::node::deserialize("v: " + input)["v"].as_uint(), fkyaml::type_error); +} + +TEST_CASE("Node_AsUint_ThrowsForNonIntegerNodes") { + SECTION("null") { + REQUIRE_THROWS_AS(fkyaml::node().as_uint(), fkyaml::type_error); + } + + SECTION("boolean") { + REQUIRE_THROWS_AS(fkyaml::node::deserialize("v: true")["v"].as_uint(), fkyaml::type_error); + } + + SECTION("float") { + REQUIRE_THROWS_AS(fkyaml::node::deserialize("v: 3.14")["v"].as_uint(), fkyaml::type_error); + } + + SECTION("string") { + REQUIRE_THROWS_AS(fkyaml::node::deserialize("v: hello")["v"].as_uint(), fkyaml::type_error); + } +} + +TEST_CASE("Node_GetValueUInt64_RoundTrips") { + using test_data_t = std::pair; + auto test_data = GENERATE( + test_data_t {"0", 0ULL}, + test_data_t {"15745692345339290292", UINT64_C(15745692345339290292)}, + test_data_t {"18446744073709551615", UINT64_MAX}); + REQUIRE(fkyaml::node::deserialize("v: " + test_data.first)["v"].get_value() == test_data.second); +} + +TEST_CASE("Node_GetValueUInt64_ThrowsForNegativeValue") { + REQUIRE_THROWS_AS(fkyaml::node::deserialize("v: -5")["v"].get_value(), fkyaml::exception); +} + +TEST_CASE("Node_AsInt_UnaffectedByUint64Change") { + // Regression: existing signed-integer behaviour must be preserved. + using test_data_t = std::pair; + auto test_data = GENERATE( + test_data_t {"-9223372036854775808", INT64_MIN}, + test_data_t {"-42", -42}, + test_data_t {"0", 0}, + test_data_t {"1234", 1234}, + test_data_t {"9223372036854775807", INT64_MAX}); + REQUIRE(fkyaml::node::deserialize("v: " + test_data.first)["v"].as_int() == test_data.second); +} + +TEST_CASE("Node_AsInt_ThrowsForUintFlaggedNode") { + // as_int() must throw type_error when uint_bit is set, because the stored + // bit-pattern is not representable as a signed int64_t. + auto input = GENERATE( + std::string("9223372036854775808"), // INT64_MAX + 1 + std::string("15745692345339290292"), // xxHash value from the bug report + std::string("18446744073709551615")); // UINT64_MAX + REQUIRE_THROWS_AS(fkyaml::node::deserialize("v: " + input)["v"].as_int(), fkyaml::type_error); +} + +TEST_CASE("Node_UintBit_ClearedOnReassignment") { + // After assigning a new signed integer value the uint_bit must be cleared. + fkyaml::node n = fkyaml::node::deserialize("v: 15745692345339290292")["v"]; + REQUIRE(n.is_uint() == true); + + n = fkyaml::node(static_cast(42)); + REQUIRE(n.is_integer() == true); + REQUIRE(n.is_uint() == false); + REQUIRE(n.as_int() == 42); +} diff --git a/tests/unit_test/test_scalar_parser_class_uint.cpp b/tests/unit_test/test_scalar_parser_class_uint.cpp new file mode 100644 index 00000000..bb8f73f9 --- /dev/null +++ b/tests/unit_test/test_scalar_parser_class_uint.cpp @@ -0,0 +1,66 @@ +// _______ __ __ __ _____ __ __ __ +// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library (supporting code) +// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 +// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML +// +// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani +// SPDX-License-Identifier: MIT + +#include + +#include + +#include + +TEST_CASE("ScalarParser_UInt64_LargeDecimalParsedAsInteger") { + // Values above INT64_MAX must be stored as INTEGER nodes, not demoted to STRING. + auto input = GENERATE( + std::string("9223372036854775808"), // INT64_MAX + 1 + std::string("15745692345339290292"), // xxHash value from the bug report + std::string("18446744073709551615")); // UINT64_MAX + auto node = fkyaml::node::deserialize("v: " + input)["v"]; + REQUIRE(node.is_integer() == true); + REQUIRE(node.is_string() == false); +} + +TEST_CASE("ScalarParser_UInt64_LargeDecimalSetsUintBit") { + // The uint_bit flag must be set for any value that overflows int64_t. + auto input = GENERATE( + std::string("9223372036854775808"), std::string("15745692345339290292"), std::string("18446744073709551615")); + auto node = fkyaml::node::deserialize("v: " + input)["v"]; + REQUIRE(node.is_uint() == true); +} + +TEST_CASE("ScalarParser_UInt64_AsUintRecoversPreciseValue") { + using test_data_t = std::pair; + auto test_data = GENERATE( + test_data_t {"9223372036854775808", UINT64_C(9223372036854775808)}, + test_data_t {"15745692345339290292", UINT64_C(15745692345339290292)}, + test_data_t {"18446744073709551615", UINT64_C(18446744073709551615)}); + auto node = fkyaml::node::deserialize("v: " + test_data.first)["v"]; + REQUIRE(node.as_uint() == test_data.second); + REQUIRE(node.get_value() == test_data.second); +} + +TEST_CASE("ScalarParser_UInt64_SignedRangeNoUintBit") { + // Values within int64_t range must NOT have uint_bit set. + auto input = GENERATE( + std::string("-1"), + std::string("0"), + std::string("42"), + std::string("9223372036854775807")); // INT64_MAX + auto node = fkyaml::node::deserialize("v: " + input)["v"]; + REQUIRE(node.is_integer() == true); + REQUIRE(node.is_uint() == false); +} + +TEST_CASE("ScalarParser_UInt64_ExplicitIntTagOnOverflowThrows") { + // An explicit !!int tag must not silently demote to string on overflow. + REQUIRE_THROWS_AS(fkyaml::node::deserialize("v: !!int 99999999999999999999999"), fkyaml::parse_error); +} + +TEST_CASE("ScalarParser_UInt64_BeyondUint64MaxBecomesString") { + // Without a tag, a decimal that exceeds UINT64_MAX falls back to string. + auto node = fkyaml::node::deserialize("v: 99999999999999999999999")["v"]; + REQUIRE(node.is_string() == true); +} From 0d0b683b7611d892621b51532afedfa852754efa Mon Sep 17 00:00:00 2001 From: sndth <75499293+sndth@users.noreply.github.com> Date: Wed, 4 Mar 2026 23:55:48 +0100 Subject: [PATCH 2/4] Prevent get_value() crash caused by as_int() throwing on uint_bit-marked nodes --- include/fkYAML/detail/conversions/from_node.hpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/include/fkYAML/detail/conversions/from_node.hpp b/include/fkYAML/detail/conversions/from_node.hpp index a8378b15..e29a4f83 100644 --- a/include/fkYAML/detail/conversions/from_node.hpp +++ b/include/fkYAML/detail/conversions/from_node.hpp @@ -372,15 +372,17 @@ struct from_node_int_helper { /// @return An integer value converted from the node's integer value. static IntType convert(const BasicNodeType& n) { using node_int_type = typename BasicNodeType::integer_type; + + // as_int() throws for uint_bit-marked nodes (value > INT64_MAX stored as a signed + // bit-pattern). Short-circuit before calling as_int() so get_value() works. + if (std::is_same::value && n.is_uint()) { + return static_cast(n.as_uint()); + } + const node_int_type tmp_int = n.as_int(); // under/overflow check. if (std::is_same::value) { - // Nodes marked with uint_bit store a uint64_t bit pattern in a signed field. - // Recover the original unsigned value without a sign check. - if (n.is_uint()) { - return static_cast(tmp_int); - } if FK_YAML_UNLIKELY (tmp_int < 0) { throw exception("Integer value underflow detected."); } From 9562f88da2e809db5b95ed236aaf9a465dc25c63 Mon Sep 17 00:00:00 2001 From: sndth <75499293+sndth@users.noreply.github.com> Date: Wed, 4 Mar 2026 23:58:40 +0100 Subject: [PATCH 3/4] Run amalgamation --- single_include/fkYAML/node.hpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/single_include/fkYAML/node.hpp b/single_include/fkYAML/node.hpp index b931068c..01f37d9f 100644 --- a/single_include/fkYAML/node.hpp +++ b/single_include/fkYAML/node.hpp @@ -12193,15 +12193,17 @@ struct from_node_int_helper { /// @return An integer value converted from the node's integer value. static IntType convert(const BasicNodeType& n) { using node_int_type = typename BasicNodeType::integer_type; + + // as_int() throws for uint_bit-marked nodes (value > INT64_MAX stored as a signed + // bit-pattern). Short-circuit before calling as_int() so get_value() works. + if (std::is_same::value && n.is_uint()) { + return static_cast(n.as_uint()); + } + const node_int_type tmp_int = n.as_int(); // under/overflow check. if (std::is_same::value) { - // Nodes marked with uint_bit store a uint64_t bit pattern in a signed field. - // Recover the original unsigned value without a sign check. - if (n.is_uint()) { - return static_cast(tmp_int); - } if FK_YAML_UNLIKELY (tmp_int < 0) { throw exception("Integer value underflow detected."); } From 7287dd9367fa6134314e1712d6cd4c0651024f1b Mon Sep 17 00:00:00 2001 From: sndth <75499293+sndth@users.noreply.github.com> Date: Thu, 5 Mar 2026 00:03:13 +0100 Subject: [PATCH 4/4] Use explicit partial specialization for uint64_t in from_node_int_helper to fix MSVC build --- .../fkYAML/detail/conversions/from_node.hpp | 45 ++++++++++--------- single_include/fkYAML/node.hpp | 45 ++++++++++--------- 2 files changed, 46 insertions(+), 44 deletions(-) diff --git a/include/fkYAML/detail/conversions/from_node.hpp b/include/fkYAML/detail/conversions/from_node.hpp index e29a4f83..8de5b41b 100644 --- a/include/fkYAML/detail/conversions/from_node.hpp +++ b/include/fkYAML/detail/conversions/from_node.hpp @@ -362,38 +362,39 @@ struct from_node_int_helper { } }; -/// @brief Helper struct for node-to-int conversion if IntType is not the node's integer value type. +/// @brief Partial specialization for uint64_t when integer_type != uint64_t (the common int64_t case). +/// This must be declared BEFORE the generic specialization so the compiler always +/// prefers it for uint64_t. Using a hardcoded 'false' (not a value-dependent expression) avoids +/// the MSVC ambiguity that arises when std::is_same<...>::value is used as a template argument. /// @tparam BasicNodeType A basic_node template instance type. -/// @tparam IntType Target integer value type (different from BasicNodeType::integer_type) +template +struct from_node_int_helper { + /// @brief Convert node's integer value to uint64_t via as_uint(). + /// @param n A node object. + /// @return The node value as uint64_t. + static uint64_t convert(const BasicNodeType& n) { + return n.as_uint(); + } +}; + +/// @brief Helper struct for node-to-int conversion if IntType is not the node's integer value type +/// and IntType is not uint64_t (covered by the explicit specialization above). +/// @tparam BasicNodeType A basic_node template instance type. +/// @tparam IntType Target integer value type (different from BasicNodeType::integer_type, not uint64_t) template struct from_node_int_helper { - /// @brief Convert node's integer value to non-uint64_t integer types. + /// @brief Convert node's integer value to a narrower signed/unsigned integer type. /// @param n A node object. /// @return An integer value converted from the node's integer value. static IntType convert(const BasicNodeType& n) { using node_int_type = typename BasicNodeType::integer_type; - - // as_int() throws for uint_bit-marked nodes (value > INT64_MAX stored as a signed - // bit-pattern). Short-circuit before calling as_int() so get_value() works. - if (std::is_same::value && n.is_uint()) { - return static_cast(n.as_uint()); - } - const node_int_type tmp_int = n.as_int(); - // under/overflow check. - if (std::is_same::value) { - if FK_YAML_UNLIKELY (tmp_int < 0) { - throw exception("Integer value underflow detected."); - } + if FK_YAML_UNLIKELY (tmp_int < static_cast(std::numeric_limits::min())) { + throw exception("Integer value underflow detected."); } - else { - if FK_YAML_UNLIKELY (tmp_int < static_cast(std::numeric_limits::min())) { - throw exception("Integer value underflow detected."); - } - if FK_YAML_UNLIKELY (static_cast(std::numeric_limits::max()) < tmp_int) { - throw exception("Integer value overflow detected."); - } + if FK_YAML_UNLIKELY (static_cast(std::numeric_limits::max()) < tmp_int) { + throw exception("Integer value overflow detected."); } return static_cast(tmp_int); diff --git a/single_include/fkYAML/node.hpp b/single_include/fkYAML/node.hpp index 01f37d9f..7753c6fd 100644 --- a/single_include/fkYAML/node.hpp +++ b/single_include/fkYAML/node.hpp @@ -12183,38 +12183,39 @@ struct from_node_int_helper { } }; -/// @brief Helper struct for node-to-int conversion if IntType is not the node's integer value type. +/// @brief Partial specialization for uint64_t when integer_type != uint64_t (the common int64_t case). +/// This must be declared BEFORE the generic specialization so the compiler always +/// prefers it for uint64_t. Using a hardcoded 'false' (not a value-dependent expression) avoids +/// the MSVC ambiguity that arises when std::is_same<...>::value is used as a template argument. /// @tparam BasicNodeType A basic_node template instance type. -/// @tparam IntType Target integer value type (different from BasicNodeType::integer_type) +template +struct from_node_int_helper { + /// @brief Convert node's integer value to uint64_t via as_uint(). + /// @param n A node object. + /// @return The node value as uint64_t. + static uint64_t convert(const BasicNodeType& n) { + return n.as_uint(); + } +}; + +/// @brief Helper struct for node-to-int conversion if IntType is not the node's integer value type +/// and IntType is not uint64_t (covered by the explicit specialization above). +/// @tparam BasicNodeType A basic_node template instance type. +/// @tparam IntType Target integer value type (different from BasicNodeType::integer_type, not uint64_t) template struct from_node_int_helper { - /// @brief Convert node's integer value to non-uint64_t integer types. + /// @brief Convert node's integer value to a narrower signed/unsigned integer type. /// @param n A node object. /// @return An integer value converted from the node's integer value. static IntType convert(const BasicNodeType& n) { using node_int_type = typename BasicNodeType::integer_type; - - // as_int() throws for uint_bit-marked nodes (value > INT64_MAX stored as a signed - // bit-pattern). Short-circuit before calling as_int() so get_value() works. - if (std::is_same::value && n.is_uint()) { - return static_cast(n.as_uint()); - } - const node_int_type tmp_int = n.as_int(); - // under/overflow check. - if (std::is_same::value) { - if FK_YAML_UNLIKELY (tmp_int < 0) { - throw exception("Integer value underflow detected."); - } + if FK_YAML_UNLIKELY (tmp_int < static_cast(std::numeric_limits::min())) { + throw exception("Integer value underflow detected."); } - else { - if FK_YAML_UNLIKELY (tmp_int < static_cast(std::numeric_limits::min())) { - throw exception("Integer value underflow detected."); - } - if FK_YAML_UNLIKELY (static_cast(std::numeric_limits::max()) < tmp_int) { - throw exception("Integer value overflow detected."); - } + if FK_YAML_UNLIKELY (static_cast(std::numeric_limits::max()) < tmp_int) { + throw exception("Integer value overflow detected."); } return static_cast(tmp_int);